소스: sql-src/mid_10_group_having/01_group_having.sql. 직원(emp) 6명(경영 1·개발 2·영업 3)과 주문(orders) 10건을 준비했습니다.
SELECT
e.name
, COUNT(*) AS order_cnt
, COUNT(DISTINCT o.cust) AS cust_cnt
, SUM(o.amt) AS total_amt
, AVG(o.amt) AS avg_amt
, MIN(o.amt) AS min_amt
, MAX(o.amt) AS max_amt
FROM emp e
JOIN orders o ON o.emp_id = e.id
GROUP BY e.name
ORDER BY e.name;NAME | ORDER_CNT | CUST_CNT | TOTAL_AMT | AVG_AMT | MIN_AMT | MAX_AMT
-------+-----------+----------+-----------+---------+---------+--------
강사원 | 1 | 1 | 400 | 400.0 | 400 | 400
박과장 | 2 | 1 | 600 | 300.0 | 100 | 500
이부장 | 4 | 2 | 1000 | 250.0 | 150 | 350
정사원 | 2 | 1 | 500 | 250.0 | 250 | 250
조사원 | 1 | 1 | 150 | 150.0 | 150 | 150
(5행)이부장은 주문 4건 중 고객이 2명(김민준·이서연)뿐이라 CUST_CNT 가 2입니다. COUNT(*) 는 행 수 그대로 4지만, COUNT(DISTINCT o.cust) 는 중복 고객을 빼고 세어 값이 달라집니다.
SELECT
e.dept
, o.status
, COUNT(*) AS cnt
, SUM(o.amt) AS total_amt
FROM emp e
JOIN orders o ON o.emp_id = e.id
GROUP BY e.dept, o.status
ORDER BY e.dept, o.status;DEPT | STATUS | CNT | TOTAL_AMT
-----+--------+-----+----------
개발 | 완료 | 5 | 1400
개발 | 취소 | 1 | 200
영업 | 대기 | 1 | 250
영업 | 완료 | 3 | 800
(4행)GROUP BY e.dept, o.status 처럼 컬럼을 여러 개 나열하면 두 컬럼의 조합이 같은 행끼리 한 그룹이 됩니다. 개발 부서만 봐도 완료·취소 상태가 다른 그룹으로 나뉘어 집계됩니다.
SELECT
e.dept
, COUNT(*) AS cnt
, SUM(o.amt) AS total_amt
FROM emp e
JOIN orders o ON o.emp_id = e.id
WHERE o.status = '완료'
GROUP BY e.dept
ORDER BY e.dept;DEPT | CNT | TOTAL_AMT
-----+-----+----------
개발 | 5 | 1400
영업 | 3 | 800
(2행)WHERE o.status = '완료' 가 그룹을 짓기 전에 취소·대기 건을 먼저 빼버려서, 개발 부서 합계가 완료 건만 반영한 1400 입니다. WHERE 없이 HAVING SUM(o.amt) > 700 만 쓰면 취소·대기 건까지 포함한 합계(개발 1600, 영업 1050)에 조건을 대입하므로 값 자체가 달라집니다.
SELECT
e.dept
, COUNT(*) AS cnt
, SUM(o.amt) AS total_amt
FROM emp e
JOIN orders o ON o.emp_id = e.id
WHERE o.status = '완료'
GROUP BY e.dept
HAVING COUNT(*) >= 2
ORDER BY e.dept;DEPT | CNT | TOTAL_AMT
-----+-----+----------
개발 | 5 | 1400
영업 | 3 | 800
(2행)WHERE 가 먼저 완료 건만 남기고, 그렇게 만들어진 그룹의 건수(COUNT(*))가 2 이상인지 HAVING 이 다시 확인합니다. 2.2절 평가 순서대로 WHERE 가 먼저, HAVING 이 나중입니다.
SELECT
COUNT(*) AS cnt
, SUM(amt) AS total_amt
FROM orders
HAVING SUM(amt) > 2000;CNT | TOTAL_AMT
----+----------
10 | 2650
(1행)GROUP BY 가 없으니 orders 전체가 하나의 그룹이고, 합계 2650 이 조건(2000 초과)을 만족해 1행이 나옵니다. 조건을 3000 초과로 바꾸면 "값이 0인 1행"이 아니라 "행 자체가 없음(0행)"이 됩니다. HAVING 조건을 만족하지 않는 그룹은 결과에서 아예 빠지기 때문입니다.
SELECT
COUNT(*) AS cnt
, SUM(amt) AS total_amt
FROM empty_sales;CNT | TOTAL_AMT
----+----------
0 | NULL
(1행)SELECT
dept
, COUNT(*) AS cnt
FROM empty_sales
GROUP BY dept;DEPT | CNT
-----+----
(0행)empty_sales 는 행이 하나도 없는 테이블입니다. GROUP BY 없이 조회하면 COUNT(*) 는 0, SUM 은 NULL 인 1행이 나오지만, GROUP BY dept 를 붙이면 나눌 행이 아예 없어 0행입니다. 2.4절에서 설명한 차이가 그대로 재현됩니다.