모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

11강 / 전체 24강

HAVING

9분 읽기 조회 0

HAVING으로 GROUP BY 집계 결과에 조건을 걸어 원하는 그룹만 필터링하고, WHERE와의 실행 순서 차이를 이해합니다.

🎯 학습 목표

이 강을 마치면 집계 결과에 조건을 거는 HAVING을 사용할 수 있고, WHERE와 HAVING의 차이를 명확하게 이해할 수 있습니다. "평균 가격이 20,000원 이상인 카테고리", "주문이 3건 이상인 회원"처럼 집계 이후의 조건을 정확하게 표현할 수 있게 됩니다. WHERE와 HAVING을 적절히 조합해 강력한 필터링 쿼리를 작성할 수 있습니다.

💡 HAVING이 필요한 이유

이 섹션에서는 WHERE로 해결할 수 없는 문제와 HAVING이 등장한 배경을 살펴보겠습니다.

10강에서 GROUP BY로 카테고리별 도서 통계를 구했습니다. 이제 "카테고리별 평균 가격이 20,000원 이상인 카테고리만 보고 싶다"는 요구사항이 생겼다고 가정합니다. 직관적으로 WHERE를 쓰고 싶겠지만, 이것은 동작하지 않습니다.

-- 오류 또는 의도와 다른 결과! AVG(price)를 WHERE에 쓸 수 없음
SELECT category, COUNT(*), AVG(price)
FROM books
WHERE AVG(price) >= 20000   -- 오류!
GROUP BY category;

실행 순서를 떠올려보면 이유가 명확합니다. WHERE는 GROUP BY보다 먼저 실행됩니다. WHERE가 실행되는 시점에는 아직 그룹이 만들어지지 않았기 때문에, 그룹의 평균 가격이라는 개념 자체가 존재하지 않습니다. WHERE에서 집계 함수를 사용하면 오류가 발생합니다.

이 문제를 해결하기 위해 SQL에는 GROUP BY 이후에 실행되는 조건 절인 HAVING이 있습니다. HAVING은 그룹이 완성된 후 집계 결과에 조건을 걸어 특정 그룹만 남깁니다.

📊 HAVING 기본 사용법

이 섹션에서는 HAVING의 기본 문법과 다양한 활용 예를 살펴보겠습니다.

HAVING은 GROUP BY 다음에 위치합니다. 전체 실행 순서는 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT입니다.

-- 카테고리별 평균 가격이 20,000원 이상인 카테고리만
SELECT
  category,
  COUNT(*)              AS 도서수,
  ROUND(AVG(price), 0)  AS 평균가격
FROM books
GROUP BY category
HAVING AVG(price) >= 20000
ORDER BY 평균가격 DESC;

결과에는 IT 카테고리(평균 약 27,667원)만 나타납니다. 경제(평균 16,500원)와 소설(평균 13,500원)은 조건을 만족하지 않아 제외됩니다. 참고로 HAVING 절에서는 SELECT 별칭 대신 원래 집계 함수 표현식을 써야 합니다(SELECT보다 먼저 실행되기 때문).

도서가 2권 이상인 카테고리만 조회합니다.

SELECT
  category,
  COUNT(*) AS 도서수
FROM books
GROUP BY category
HAVING COUNT(*) >= 2
ORDER BY 도서수 DESC;

IT 3권, 경제 2권, 소설 2권 세 카테고리 모두 2권 이상이므로 전부 나타납니다. 만약 HAVING COUNT(*) >= 3을 쓰면 IT 카테고리만 나타납니다.

주문이 2건 이상인 회원 번호를 구합니다.

SELECT
  member_id         AS 회원번호,
  COUNT(*)          AS 주문건수,
  SUM(quantity)     AS 총주문수량
FROM orders
GROUP BY member_id
HAVING COUNT(*) >= 2
ORDER BY 주문건수 DESC;

2건 이상 주문한 회원 목록이 나타납니다. 주문 건수가 가장 많은 회원이 맨 위에 표시됩니다.

⚖️ WHERE vs HAVING — 언제 무엇을 쓰나

이 섹션에서는 WHERE와 HAVING의 차이와 올바른 사용 기준을 살펴보겠습니다.

WHERE와 HAVING은 모두 조건을 거는 절이지만 적용되는 시점과 대상이 다릅니다.

구분WHEREHAVING
실행 시점GROUP BY 전GROUP BY 후
조건 대상개별 행그룹(집계 결과)
집계 함수 사용불가가능
용도집계 전 행 제외집계 후 그룹 제외

다음 예시로 차이를 명확히 이해합니다. "IT 카테고리에서 가격이 20,000원 이상인 도서"와 "카테고리 평균 가격이 20,000원 이상인 카테고리"는 의미가 완전히 다릅니다.

-- WHERE: 개별 행 조건 — IT 카테고리 + 가격 20,000 이상인 도서만 집계
SELECT category, COUNT(*), AVG(price)
FROM books
WHERE price >= 20000
GROUP BY category;

-- HAVING: 그룹 조건 — 평균 가격이 20,000 이상인 카테고리만 남김
SELECT category, COUNT(*), ROUND(AVG(price), 0)
FROM books
GROUP BY category
HAVING AVG(price) >= 20000;

첫 번째 쿼리는 20,000원 미만 도서를 먼저 제외한 후 카테고리별로 집계합니다. IT에서 22,000, 28,000, 33,000원 도서 3권이 집계됩니다(13,000원 채식주의자 같은 소설은 WHERE에서 제외). 두 번째 쿼리는 모든 도서를 카테고리별로 집계한 후, 그 평균이 20,000원 이상인 카테고리만 결과에 포함합니다.

실무 가이드라인: 개별 행을 필터링하려면 WHERE, 집계 결과를 필터링하려면 HAVING을 사용합니다. 집계 함수가 조건에 포함되어 있으면 반드시 HAVING을 사용합니다.

🔗 WHERE + GROUP BY + HAVING 조합

이 섹션에서는 WHERE와 HAVING을 동시에 사용하는 실전 패턴을 살펴보겠습니다.

WHERE와 HAVING은 함께 사용할 수 있습니다. WHERE로 먼저 집계 대상 행을 줄이고, HAVING으로 집계 결과를 추가로 필터링합니다.

-- 취소되지 않은 주문 중, 총 주문 수량이 3개 이상인 회원
SELECT
  member_id         AS 회원번호,
  COUNT(*)          AS 주문건수,
  SUM(quantity)     AS 총주문수량
FROM orders
WHERE status != 'CANCEL'
GROUP BY member_id
HAVING SUM(quantity) >= 3
ORDER BY 총주문수량 DESC;

실행 순서는 다음과 같습니다. 첫째, WHERE status != 'CANCEL'로 취소 주문을 제외합니다. 둘째, 남은 행을 member_id로 그룹화합니다. 셋째, HAVING SUM(quantity) >= 3으로 총 주문 수량이 3개 미만인 그룹을 제외합니다. 넷째, 남은 그룹을 SELECT로 표현하고 ORDER BY로 정렬합니다.

또 다른 예시: 2024년 4월 이후에 주문한 고객 중, 2건 이상 주문한 고객을 구합니다.

SELECT
  member_id,
  COUNT(*)       AS 주문건수,
  MIN(order_date) AS 첫주문일,
  MAX(order_date) AS 마지막주문일
FROM orders
WHERE order_date >= '2024-04-01'
GROUP BY member_id
HAVING COUNT(*) >= 2
ORDER BY 주문건수 DESC;

WHERE로 4월 이후 주문만 남기고, 그 중 2건 이상 주문한 회원만 결과에 포함됩니다.

⚠️ HAVING 사용 시 주의사항

이 섹션에서는 HAVING 사용 시 자주 실수하는 패턴을 살펴보겠습니다.

첫 번째 주의사항은 집계 함수 없이 HAVING을 쓰는 잘못된 패턴입니다. 집계 함수 없이 HAVING에 일반 열 조건을 쓰면 동작은 하지만 WHERE를 쓰는 것이 올바른 방법입니다.

-- 동작은 하지만 잘못된 방법 (WHERE를 써야 함)
SELECT category, COUNT(*)
FROM books
GROUP BY category
HAVING category = 'IT';

-- 올바른 방법 (개별 행 필터이므로 WHERE 사용)
SELECT category, COUNT(*)
FROM books
WHERE category = 'IT'
GROUP BY category;

첫 번째 방법은 모든 행을 집계한 후 IT만 남기므로 비효율적입니다. 두 번째는 IT가 아닌 행을 집계 전에 제외하므로 더 효율적입니다. 성능 측면에서 집계 전에 걸 수 있는 조건은 최대한 WHERE에 두는 것이 좋습니다.

두 번째는 HAVING에서 SELECT 별칭 사용 불가 문제입니다. HAVING은 SELECT보다 먼저 실행되므로 SELECT에서 정의한 별칭을 HAVING에서 쓸 수 없습니다. 원래 집계 함수 표현식을 반복해서 써야 합니다.

-- 오류: 별칭 '평균가격'을 HAVING에서 쓸 수 없음
SELECT category, ROUND(AVG(price), 0) AS 평균가격
FROM books
GROUP BY category
HAVING 평균가격 >= 20000;  -- 오류!

-- 올바른 방법: 집계 함수 표현식 직접 사용
SELECT category, ROUND(AVG(price), 0) AS 평균가격
FROM books
GROUP BY category
HAVING AVG(price) >= 20000;

단, 일부 DBMS(MySQL, PostgreSQL의 특정 버전)는 HAVING에서 SELECT 별칭을 허용하기도 합니다. 하지만 이식성을 위해 원래 표현식을 사용하는 것이 안전합니다.

📝 핵심 요약

이번 강에서 배운 내용을 정리하겠습니다.

절실행 순서조건 대상집계 함수
WHEREGROUP BY 전개별 행사용 불가
HAVINGGROUP BY 후그룹(집계 결과)사용 가능

완전한 집계 쿼리의 절 순서는 다음과 같습니다. SELECT … FROM … WHERE … GROUP BY … HAVING … ORDER BY … LIMIT. 실행 순서는 작성 순서와 달리 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT 입니다. 이 순서를 외우면 어느 절에서 무엇을 쓸 수 있는지 헷갈리지 않습니다.

WHERE, GROUP BY, HAVING, ORDER BY를 모두 자유롭게 조합할 수 있으면 단일 테이블에서 원하는 거의 모든 통계 쿼리를 작성할 수 있습니다. 다음 12강부터는 여러 테이블의 데이터를 결합하는 JOIN을 배웁니다. "회원 이름 + 주문 내역 + 도서 제목"처럼 여러 테이블에 흩어진 정보를 하나의 결과로 합치는 SQL의 핵심 기능을 익히게 됩니다.

관련 주제

  • HAVING
  • WHERE와 HAVING 차이
  • 집계 결과 필터링
  • SQL 실행 순서
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • SQL·데이터베이스 입문 24강
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

SQL·데이터베이스 입문 24강

이 강의가 속한 시리즈는 총 24강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...