현재 선택한 SQL 과정

SQLD 이론 학습

이론 목록으로 돌아가기

GROUP BY와 HAVING

행 필터·그룹 생성·집계·HAVING의 순서와 확장 그룹 함수, GROUPING_ID, NULL·빈 입력 처리 차이를 이해합니다.

예상 읽기 7

핵심 요약

행을 그룹으로 묶는 기준, NULL 그룹, 집계 함수와 HAVING의 역할을 구분한다.

핵심 질문

  1. GROUP BY와 HAVING에서 반드시 구분해야 할 개념과 결과 규칙은 무엇인가?
  2. 0건·1건·여러 건과 NULL·동점·중복 데이터에서 결과가 어떻게 달라지는가?
  3. 비슷해 보이는 문법과 결과가 같아지는 조건, 달라지는 조건은 무엇인가?
  4. 작은 샘플 데이터를 이용해 결과를 실수 없이 예측하는 순서는 무엇인가?

학습 목표

  • 집계 전 행 필터와 집계 후 그룹 필터를 구분한다.
  • GROUP BY가 있는 SELECT 목록의 유효성을 판단한다.

개념 지도

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
원본 행 필터 → 그룹 생성 → 집계 → HAVING 그룹 필터 → 결과 출력

핵심 내용

집계 함수는 여러 행을 하나의 값으로 요약한다. SUM, AVG, MIN, MAX, COUNT가 대표적이며 대부분 NULL을 제외한다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT deptno,
       COUNT(*) AS emp_cnt,
       AVG(sal) AS avg_sal
FROM emp
WHERE status = 'ACTIVE'
GROUP BY deptno
HAVING COUNT(*) >= 5;

WHERE는 그룹을 만들기 전에 행을 줄이고, HAVING은 그룹 결과를 거른다. SELECT 목록의 일반 컬럼은 GROUP BY 기준에 포함되어야 한다. GROUP BY의 NULL 값들은 하나의 그룹으로 묶인다.

조건부 집계는 CASE와 집계 함수를 결합한다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SUM(CASE WHEN status = 'DONE' THEN amount ELSE 0 END)

COUNT(*), COUNT(1)은 행 수를 세는 목적에서 같은 결과를 내며, 특정 컬럼의 NULL 제외 건수가 필요할 때 COUNT(col)을 쓴다.

흔한 오해와 주의점

  • HAVING을 GROUP BY가 있을 때만 문법적으로 쓸 수 있다고 단정하지 않는다. 다만 의미상 그룹 필터다.
  • AVG는 NULL을 0으로 바꿔 평균에 포함하지 않는다.
  • SELECT에 그룹 기준이 아닌 일반 컬럼을 임의로 추가할 수 없다.

문항 풀이 보강: 확장 그룹 함수

ROLLUP·CUBE·GROUPING SETS

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
ROLLUP(A, B)       → (A,B), (A), ()
CUBE(A, B)         → (A,B), (A), (B), ()
GROUPING SETS((A,B), (A), ()) → 명시한 상세·소계·총계 그룹만

()는 전체 총계를 뜻한다. (A,B)는 두 컬럼을 하나의 그룹 조합으로 묶는다. GROUPING SETS((A,B))는 일반 GROUP BY A,B와 같은 상세 그룹만 만들며 소계나 총계를 자동 추가하지 않는다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT region_id,
       TO_CHAR(use_dt, 'YYYY.MM') use_month,
       SUM(amount),
       GROUPING(region_id) g_region
FROM usage
GROUP BY ROLLUP(region_id, TO_CHAR(use_dt, 'YYYY.MM'));

소계 행에서 그룹 대상 컬럼은 NULL로 표시된다. 원본 데이터의 실제 NULL과 소계 때문에 생긴 NULL을 구분할 때 GROUPING(col)을 쓴다. 집계된 행이면 1, 일반 상세 그룹이면 0이다.

중복 그룹 세트

GROUPING SETS(A, (B,C), (B,C))처럼 같은 그룹 세트를 두 번 쓰면 같은 집계 행도 두 번 생성될 수 있다. 원하는 집합을 한 번씩 정확히 나열한다.

GROUPING과 GROUPING_ID

ROLLUP·CUBE·GROUPING SETS가 만든 소계 행에서는 집계 때문에 생긴 NULL과 원본 데이터의 NULL을 구분해야 합니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT deptno,
       job,
       SUM(sal) AS total_sal,
       GROUPING(deptno) AS g_dept,
       GROUPING(job)    AS g_job,
       GROUPING_ID(deptno, job) AS gid
FROM   emp
GROUP  BY ROLLUP(deptno, job);
  • GROUPING(expr) = 1: 해당 컬럼의 NULL은 소계·총계 행을 만들며 생긴 것
  • GROUPING(expr) = 0: 상세 그룹 값이며 실제 NULL일 수도 있음
  • GROUPING_ID: 여러 GROUPING 결과를 비트값으로 묶어 소계 수준을 식별
SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
CASE
  WHEN GROUPING(deptno) = 1 THEN '전체 부서'
  WHEN deptno IS NULL      THEN '부서 미지정'
  ELSE TO_CHAR(deptno)
END

NVL(deptno, '합계')만 사용하면 원본 NULL 그룹과 합계 행을 혼동할 수 있습니다.

입력 행이 0건일 때

GROUP BY가 없는 집계 Query는 입력이 0건이어도 한 행을 반환할 수 있습니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT COUNT(*), SUM(sal)
FROM   emp
WHERE  1 = 0;

개념 결과는 COUNT(*) = 0, SUM(sal) = NULL입니다. 반면 GROUP BY deptno가 있으면 생성할 그룹이 없어 0행을 반환합니다.

문제에 적용하는 순서

  1. 최종 결과에서 필요한 상세·소계·총계 조합을 집합 표기로 적는다.
  2. 그 조합이 계층이면 ROLLUP, 모든 조합이면 CUBE, 임의 조합이면 GROUPING SETS를 고른다.
  3. 표시용 NULL은 GROUPING 함수로 판별한다.

JOIN 뒤 횟수 집계와 HAVING 표현

구매 이력이 있는 고객 중 구매가 3회 이상인 고객은 먼저 구매정보와 INNER JOIN하고 고객별로 묶은 뒤 건수를 검사한다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT c.name, c.grade
FROM customer c
INNER JOIN purchase p
  ON p.customer_id = c.customer_id
GROUP BY c.name, c.grade
HAVING COUNT(p.purchase_id) >= 3;

구매 “횟수”는 식별자의 합계가 아니라 COUNT로 센다. HAVING COUNT(p.purchase_id)는 그 값을 SELECT 목록에 출력하지 않아도 사용할 수 있다. 논리적으로 HAVING이 그룹을 거른 뒤 SELECT 결과를 만들기 때문이다.


결과를 검증하는 순서

  1. 각 Query Block이 만드는 한 행의 의미를 먼저 적습니다.
  2. 조건을 적용하기 전 원본 행과 적용 후 남는 행을 작은 표로 그립니다.
  3. NULL 비교가 TRUE, FALSE, UNKNOWN 중 무엇인지 구분합니다.
  4. 중복 제거, 그룹화, 정렬과 행 제한이 적용되는 순서를 확인합니다.
  5. 데이터가 0건·1건·여러 건일 때도 같은 규칙이 성립하는지 검증합니다.

실무와 시험에서 함께 확인할 항목

  • ORDER BY가 없다면 결과 순서를 가정하지 않습니다.
  • 문자열·숫자·날짜 비교에서는 데이터 타입과 명시적 형변환을 확인합니다.
  • 같은 결과처럼 보이는 SQL도 NULL과 중복이 있을 때 달라질 수 있습니다.
  • 문법을 외우기 전에 샘플 데이터 3~5행으로 결과를 직접 계산합니다.

마지막 점검

  • 작성 순서가 아니라 SQL의 논리적 처리 순서로 결과를 계산합니다.
  • NULL을 0이나 빈 값과 같은 것으로 취급하지 않습니다.
  • ORDER BY가 없는 결과 순서와 DISTINCT 없는 중복 제거를 가정하지 않습니다.
  • 비슷한 문법은 0건·다건·NULL 데이터를 넣어 결과가 정말 같은지 확인합니다.

복습 문제

  1. 부서별 인원수가 5명 이상인 부서를 거르는 절은?
  2. NULL 급여를 0으로 포함한 평균과 제외한 평균은 같은가?
  3. 샘플 데이터 3행으로 결과를 직접 계산할 수 있는가?
  4. NULL이 포함될 때 결과가 달라지는 지점은 어디인가?