SQL GROUP BY + HAVING — 집계 쿼리 제대로 쓰는 법

한줄 요약: GROUP BY와 HAVING을 제대로 이해하면, 복잡한 집계 조건도 단 한 줄의 쿼리로 깔끔하게 해결할 수 있습니다.

“주문 건수가 5개 이상인 고객만 뽑아줘” — 이 말 듣고 WHERE 쓰다가 에러 나신 적 있으신가요? 처음엔 저도 똑같이 헤맸습니다. 😅

GROUP BY와 HAVING, 왜 알아야 할까요?

실무에서 데이터를 다루다 보면 단순히 행을 조회하는 것 이상이 필요합니다.
“부서별 평균 연봉은?” “월별 매출 합계는?” “구매 횟수가 3번 이상인 고객은?” 같은 질문들이죠.
이런 질문에 답하려면 GROUP BY로 데이터를 묶고, HAVING으로 묶인 그룹에 조건을 거는 방법을 알아야 합니다.

GROUP BY는 쉽게 말해 “같은 값끼리 한 팀으로 묶어라”는 명령입니다.
예를 들어 전국 학생 성적표가 있을 때, 학교별로 묶어서 평균을 내는 것과 같습니다.
그리고 HAVING은 그렇게 묶인 팀(그룹) 중에서 “평균이 80점 이상인 학교만 보여줘”처럼 조건을 추가하는 역할입니다.

⚠️ 여기서 중요한 포인트! WHERE는 그룹을 만들기 에 개별 행에 조건을 걸고, HAVING은 그룹을 만든 에 그룹 전체에 조건을 겁니다.
이 차이를 모르면 쿼리가 에러 나거나 엉뚱한 결과가 나와서 한참 고생하게 됩니다.
실무에서 이 둘을 혼동하는 경우가 정말 많으니 꼭 기억해 두세요.

실전 예제 — 주문 데이터로 바로 써먹기

아래 예제는 실제 쇼핑몰 DB 구조를 기반으로 만든 예제입니다. 복사해서 바로 테스트해 보세요!


-- 샘플 테이블 생성 및 데이터 삽입
CREATE TABLE orders (
    order_id    INT,
    customer_id INT,
    product     VARCHAR(50),
    amount      INT,       -- 주문 금액
    order_date  DATE
);

INSERT INTO orders VALUES
(1, 101, '노트북',   1200000, '2024-01-05'),
(2, 102, '마우스',     35000, '2024-01-07'),
(3, 101, '키보드',    85000, '2024-01-10'),
(4, 103, '모니터',   450000, '2024-01-12'),
(5, 102, '웹캠',      75000, '2024-02-01'),
(6, 101, '헤드셋',   120000, '2024-02-03'),
(7, 103, '노트북',  1200000, '2024-02-05'),
(8, 104, '마우스',    35000, '2024-02-10'),
(9, 102, '키보드',    85000, '2024-02-15'),
(10,101, '모니터',   450000, '2024-03-01');

-- ① 기본: 고객별 총 주문 금액 집계
SELECT
    customer_id,
    COUNT(*)        AS 주문건수,      -- 주문 횟수
    SUM(amount)     AS 총주문금액,    -- 금액 합계
    AVG(amount)     AS 평균주문금액   -- 금액 평균
FROM orders
GROUP BY customer_id
ORDER BY 총주문금액 DESC;

-- ② HAVING: 총 주문금액이 500,000원 이상인 고객만 조회
SELECT
    customer_id,
    COUNT(*)    AS 주문건수,
    SUM(amount) AS 총주문금액
FROM orders
GROUP BY customer_id
HAVING SUM(amount) >= 500000   -- 그룹 집계 후 조건 적용
ORDER BY 총주문금액 DESC;

-- ③ WHERE + GROUP BY + HAVING 조합
--    2024년 1월 이후 주문 중, 주문 횟수 2건 이상인 고객
SELECT
    customer_id,
    COUNT(*)    AS 주문건수,
    SUM(amount) AS 총주문금액
FROM orders
WHERE order_date >= '2024-01-01'   -- 행 필터링 (그룹화 전)
GROUP BY customer_id
HAVING COUNT(*) >= 2               -- 그룹 필터링 (그룹화 후)
ORDER BY 주문건수 DESC;
  

① 번 쿼리는 GROUP BY customer_id로 고객별로 데이터를 묶은 뒤, COUNT(*)로 주문 건수, SUM(amount)로 총액, AVG(amount)로 평균을 한 번에 뽑아냅니다.
② 번 쿼리가 핵심인데, HAVING SUM(amount) >= 500000으로 그룹화된 결과 중 조건에 맞는 그룹만 필터링합니다. WHERE로는 이 작업이 불가능합니다.
③ 번은 실무에서 가장 자주 쓰는 패턴으로, WHERE로 먼저 원하는 행을 추린 뒤 → GROUP BY로 묶고 → HAVING으로 그룹 조건을 거는 3단계 흐름을 보여줍니다. 이 흐름을 외워두면 대부분의 집계 쿼리를 작성할 수 있습니다.



자주 하는 실수 — 이것만 조심하면 됩니다

GROUP BY와 HAVING을 처음 배울 때 누구나 한 번씩은 빠지는 함정들이 있습니다. 미리 알아두면 삽질을 줄일 수 있습니다.

  • 잘못된 방법: 집계 함수 결과에 WHERE를 사용하는 것입니다. 예를 들어 WHERE COUNT(*) >= 2처럼 쓰면 SQL은 에러를 뱉습니다. WHERE는 GROUP BY가 실행되기 전에 처리되기 때문에, 아직 집계가 이루어지지 않은 상태에서 집계 결과를 조건으로 쓸 수 없습니다.
  • 올바른 방법: 집계 함수 조건은 반드시 HAVING에 작성합니다. HAVING COUNT(*) >= 2처럼 GROUP BY 뒤에 위치시켜야 합니다. SQL 실행 순서는 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY 순이라는 것을 기억하면 헷갈리지 않습니다.
  • 꿀팁: ⚠️ SELECT 절의 별칭(alias)을 HAVING에서 쓸 수 없는 DB가 많습니다. 예를 들어 SELECT SUM(amount) AS 총액 ... HAVING 총액 >= 500000은 MySQL에서는 되지만 PostgreSQL이나 Oracle에서는 에러가 납니다. 이식성을 위해 HAVING에는 HAVING SUM(amount) >= 500000처럼 집계 함수를 직접 쓰는 습관을 들이세요.

퀴즈로 확인해 보세요!

아래 쿼리에서 잘못된 부분은 무엇일까요? 스스로 찾아보고 고쳐보세요. 😊


-- ❓ 이 쿼리의 문제점은?
SELECT
    product,
    SUM(amount) AS 총매출
FROM orders
WHERE SUM(amount) >= 100000
GROUP BY product;
  

정답은 WHERE 절에 집계 함수를 사용한 것입니다.
WHERE SUM(amount) >= 100000HAVING SUM(amount) >= 100000으로 바꾸고, GROUP BY 뒤에 위치시켜야 올바르게 동작합니다.
이 퀴즈 하나만 기억해도 실무에서 같은 실수를 반복하지 않을 수 있습니다!

정리하며

오늘은 SQL에서 집계 쿼리의 핵심인 GROUP BYHAVING을 살펴봤습니다.
핵심은 딱 하나입니다. 개별 행에 조건을 걸 땐 WHERE, 그룹에 조건을 걸 땐 HAVING입니다.
SQL 실행 순서(FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY)를 머릿속에 새겨두면, 어떤 집계 쿼리도 논리적으로 작성할 수 있습니다.

처음엔 낯설게 느껴지더라도, 오늘 예제처럼 실제 데이터를 가지고 직접 쿼리를 짜보는 게 가장 빠른 길입니다.
부서별 매출, 월별 가입자 수, 카테고리별 평균 평점 등 주변의 데이터로 직접 연습해 보세요.
익숙해지면 복잡한 리포트 쿼리도 무섭지 않게 됩니다!

  • 이 글의 핵심: GROUP BY는 데이터를 그룹으로 묶고, HAVING은 그 그룹에 조건을 거는 역할이며, 집계 함수 조건은 반드시 HAVING에 써야 합니다.
  • 다음 단계: JOIN과 GROUP BY 조합, 서브쿼리와 HAVING, ROLLUP / CUBE로 소계·합계 자동 생성하는 방법을 공부해 보세요.

참 쉽죠?

댓글 남기기