이번 문서의 목표: 이 파일을 다 읽으면 GROUPING SETS, ROLLUP, CUBE가 컬럼 몇 개를 넣었을 때 각각 정확히 몇 개의 집계 조합을 만드는지 계산할 수 있고, GROUPING 함수로 소계·총계 행을 구분해 표시할 수 있게 된다.
왜 GROUP BY만으로는 부족한가
15편에서 배운 GROUP BY는 지정한 컬럼 조합 하나로만 그룹을 나눕니다. 그런데 실무 보고서는 흔히 이런 형태를 요구합니다.
“부서·직급별 급여 합계도 보고 싶고, 부서별 소계도 보고 싶고, 전체 총계도 한 표에 같이 보고 싶다.”
GROUP BY 부서, 직급 하나로는 이 세 가지 수준을 동시에 낼 수 없습니다. 예전에는 이걸 얻으려고 GROUP BY 부서, 직급, GROUP BY 부서, 전체 집계 이렇게 세 개의 쿼리를 UNION ALL로 이어 붙였습니다. SQL 표준은 이 반복을 줄이기 위해 GROUP BY를 확장한 세 가지 구문 — GROUPING SETS, ROLLUP, CUBE를 제공합니다.
쉽게 말하면: 셋 다 “여러 개의
GROUP BY를 한 번에 실행해서 결과를 이어 붙인 것”입니다. 차이는 어떤 조합들을 만들어낼지에 있습니다.
작은 예시 — 공통으로 사용할 입력 테이블
부서와 직급별 급여 데이터입니다(단위: 백만 원). 이 데이터를 세 구문에 똑같이 적용해 결과를 비교하겠습니다.
| 부서 | 직급 | 급여 |
|---|---|---|
| 영업 | 사원 | 40 |
| 영업 | 대리 | 55 |
| 기획 | 사원 | 42 |
| 기획 | 대리 | 58 |
1. GROUPING SETS — 원하는 조합만 골라 지정
GROUPING SETS는 이름 그대로 “집계할 집합(set)들을 직접 나열”하는 구문입니다. 나열한 조합만 정확히 생성됩니다. 자동으로 추가되는 조합이 없습니다.
SQL
SELECT 부서, 직급, SUM(급여) AS 급여합계
FROM 급여
GROUP BY GROUPING SETS ((부서, 직급), (부서), ());결과 테이블
| 부서 | 직급 | 급여합계 |
|---|---|---|
| 영업 | 사원 | 40 |
| 영업 | 대리 | 55 |
| 기획 | 사원 | 42 |
| 기획 | 대리 | 58 |
| 영업 | NULL | 95 |
| 기획 | NULL | 100 |
NULL | NULL | 195 |
결과 해석: (부서, 직급) 조합 4행, (부서) 소계 2행, () 총계 1행이 나와 총 7행입니다. 여기서 NULL은 원본 데이터의 NULL이 아니라 “이 컬럼은 이 행에서 집계 대상이 아니다”라는 표시로 만들어진 NULL입니다.
자주 틀리는 점: GROUPING SETS가 전체 합계 ()를 자동으로 포함해준다고 착각하는 오답이 자주 나옵니다. 실제로는 명시한 조합만 정확히 생성되므로, 전체 합계가 필요하면 ()를 목록에 직접 넣어야 합니다. 반대로 GROUPING SETS(부서, 직급)처럼 단일 컬럼만 나열하면 (부서)별 집계와 (직급)별 집계, 딱 두 조합만 나오고, 이는 GROUP BY 부서 결과와 GROUP BY 직급 결과를 UNION ALL로 합친 것과 같습니다. 또한 나열 순서를 바꿔도(GROUPING SETS((부서),(직급)) vs GROUPING SETS((직급),(부서))) 만들어지는 집계 조합의 구성 자체는 동일합니다. 순서는 결과가 나오는 정렬 순서에만 영향을 줄 뿐입니다(원하는 정렬이 필요하면 ORDER BY를 별도로 써야 합니다).
2. ROLLUP — 계층적으로 소계를 쌓아 올리기
ROLLUP은 “둘둘 말다, 계층적으로 쌓다”라는 뜻 그대로, 지정한 컬럼 순서를 계층(위에서 아래로 부서 → 직급 순서라면, 큰 범주부터 작은 범주로 내려가는 구조)으로 보고, 오른쪽 컬럼부터 하나씩 제거하며 소계를 만들어 올라갑니다.
SQL
SELECT 부서, 직급, SUM(급여) AS 급여합계
FROM 급여
GROUP BY ROLLUP(부서, 직급);결과 테이블
| 부서 | 직급 | 급여합계 |
|---|---|---|
| 영업 | 사원 | 40 |
| 영업 | 대리 | 55 |
| 기획 | 사원 | 42 |
| 기획 | 대리 | 58 |
| 영업 | NULL | 95 |
| 기획 | NULL | 100 |
NULL | NULL | 195 |
결과 해석: ROLLUP(부서, 직급)은 (부서, 직급) → (부서) → (), 딱 이 세 단계만 만듭니다. (직급) 단독 소계(부서 상관없이 직급별로만 묶은 값)는 만들어지지 않습니다. 이 예시에서는 우연히 GROUPING SETS 예시와 같은 결과가 나왔지만, 이는 GROUPING SETS((부서,직급),(부서),())를 직접 지정한 것과 ROLLUP(부서,직급)이 자동으로 만든 조합이 같기 때문입니다. ROLLUP은 이 계층 구조를 자동으로 만들어준다는 점이 다릅니다.
자주 틀리는 점: ROLLUP(부서, 직급)이 (직급) 단독 소계까지 만든다고 착각합니다. ROLLUP은 컬럼 순서에 민감해서, 나열한 순서의 오른쪽부터 하나씩 제거하는 계층만 만듭니다. (직급) 단독처럼 “왼쪽만 없앤” 조합은 ROLLUP이 아니라 아래에서 볼 CUBE에서만 나옵니다. 또한 ROLLUP은 GROUP BY의 확장 문법이므로 GROUP BY 없이 단독으로 쓸 수 없습니다.
ROLLUP이 만드는 조합 개수 — n+1개
컬럼이 n개일 때, ROLLUP은 오른쪽부터 한 개씩 제거해나가며 n+1개의 조합을 만듭니다. 컬럼이 2개(부서, 직급)이므로 2+1=3개의 조합, 즉 (부서,직급), (부서), ()가 나오는 것입니다. 컬럼이 3개(부서, 직급, 사번)라면 4개의 조합 (부서,직급,사번) → (부서,직급) → (부서) → ()가 나옵니다.
3. CUBE — 가능한 모든 조합을 전부 생성
CUBE는 이름 그대로 “정육면체”처럼 모든 축의 조합을 빠짐없이 만듭니다. 컬럼을 켜고 끄는 모든 경우의 수를 대칭적으로 생성한다고 이해하면 됩니다.
SQL
SELECT 부서, 직급, SUM(급여) AS 급여합계
FROM 급여
GROUP BY CUBE(부서, 직급);결과 테이블 — 전부 나열
| 부서 | 직급 | 급여합계 | 이 행이 의미하는 조합 |
|---|---|---|---|
| 영업 | 사원 | 40 | (부서, 직급) |
| 영업 | 대리 | 55 | (부서, 직급) |
| 기획 | 사원 | 42 | (부서, 직급) |
| 기획 | 대리 | 58 | (부서, 직급) |
| 영업 | NULL | 95 | (부서) 단독 |
| 기획 | NULL | 100 | (부서) 단독 |
NULL | 사원 | 82 | (직급) 단독 |
NULL | 대리 | 113 | (직급) 단독 |
NULL | NULL | 195 | 전체 () |
결과 해석: ROLLUP에는 없던 (직급) 단독 소계 두 행(NULL, 사원, 82와 NULL, 대리, 113)이 CUBE에서는 추가로 생겼습니다. 사원 직급 전체(부서 무관)의 합계는 영업 40 + 기획 42 = 82이고, 대리 직급 전체는 영업 55 + 기획 58 = 113입니다. CUBE는 부서만 없앤 조합, 직급만 없앤 조합, 둘 다 없앤 조합, 둘 다 남긴 조합까지 네 가지 경우의 수를 전부 만들어냅니다.
CUBE가 만드는 조합 개수 — 2의 n제곱 개
컬럼 하나마다 “이 컬럼을 집계에 포함할 것인가, 뺄 것인가” 두 가지 선택이 있고, 이 선택들이 서로 독립적으로 조합됩니다. 컬럼이 n개면 경우의 수는 2 × 2 × … (n번) = 2의 n제곱 개입니다.
컬럼이 2개(부서, 직급)인 이번 예시로 직접 세어 보면 다음과 같습니다.
- 부서 포함, 직급 포함 →
(부서, 직급) - 부서 포함, 직급 제외 →
(부서) - 부서 제외, 직급 포함 →
(직급) - 부서 제외, 직급 제외 →
()
정확히 4개, 위 결과 테이블에서 확인한 조합 종류(4종류, 9행)와 일치합니다. 컬럼이 3개라면 2의 3제곱인 8개, 4개라면 16개로 조합 수가 매우 빠르게 늘어납니다.
자주 틀리는 점: ROLLUP(부서, 직급)과 CUBE(부서, 직급)의 결과 행 수가 같다고 착각하는 오답이 나옵니다. 컬럼이 2개일 때 ROLLUP은 3개 조합(상세 4행 + 소계 2행 + 총계 1행 = 7행), CUBE는 4개 조합(상세 4행 + 부서소계 2행 + 직급소계 2행 + 총계 1행 = 9행)으로, CUBE의 결과 행이 항상 ROLLUP보다 많거나 같습니다. 또한 CUBE(A,B)는 A와 B의 순서를 바꿔도(CUBE(B,A)) 만들어내는 조합의 집합 자체는 동일합니다 — ROLLUP과 달리 CUBE는 대칭이기 때문입니다.
4. 세 구문 조합 개수 한눈에 비교
컬럼이 n개일 때 각 구문이 만드는 조합 개수를 정리하면 다음과 같습니다.
| 구문 | 조합 개수 공식 | 컬럼 2개(부서, 직급)일 때 | 특징 |
|---|---|---|---|
GROUPING SETS | 명시한 개수만큼 | 명시하기 나름(예: 3개 지정 시 3개) | 자동 추가 없음, 순서 무관 |
ROLLUP | n+1개 | 3개 | 오른쪽부터 제거, 계층적, 순서에 민감 |
CUBE | 2의 n제곱 개 | 4개 | 모든 조합, 순서 무관(대칭) |
5. GROUPING 함수 — 소계 행인지, 원본 NULL인지 구분하기
ROLLUP이나 CUBE가 만든 소계·총계 행에서 컬럼 값이 NULL로 나오는 것을 앞에서 계속 봤습니다. 그런데 원본 데이터에 진짜 NULL이 있었다면, 그 NULL과 집계가 만든 NULL을 어떻게 구분할까요? 이때 쓰는 것이 GROUPING 함수입니다.
GROUPING(컬럼)은 해당 행에서 그 컬럼이 소계·총계로 묶여 값이 사라진 것이면 1, 실제 값(원본 NULL 포함)으로 그룹된 것이면 0을 반환합니다.
SQL — 소계 행에 라벨 붙이기
SELECT
CASE WHEN GROUPING(부서) = 1 THEN '전체' ELSE 부서 END AS 부서,
CASE WHEN GROUPING(직급) = 1 THEN '소계' ELSE 직급 END AS 직급,
SUM(급여) AS 급여합계,
GROUPING(부서) AS 부서그룹핑,
GROUPING(직급) AS 직급그룹핑
FROM 급여
GROUP BY ROLLUP(부서, 직급);결과 테이블
| 부서 | 직급 | 급여합계 | 부서그룹핑 | 직급그룹핑 |
|---|---|---|---|---|
| 영업 | 사원 | 40 | 0 | 0 |
| 영업 | 대리 | 55 | 0 | 0 |
| 기획 | 사원 | 42 | 0 | 0 |
| 기획 | 대리 | 58 | 0 | 0 |
| 영업 | 소계 | 95 | 0 | 1 |
| 기획 | 소계 | 100 | 0 | 1 |
| 전체 | 소계 | 195 | 1 | 1 |
결과 해석: 상세 행은 두 GROUPING 값이 모두 0입니다(두 컬럼 다 실제 값). 부서별 소계 행은 직급그룹핑만 1입니다(직급이 집계로 사라짐). 총계 행은 둘 다 1입니다. 이렇게 GROUPING 값의 조합으로 “이 행이 어느 수준의 집계인가”를 정확히 구분할 수 있습니다. 여러 컬럼의 GROUPING 값을 하나의 정수로 합쳐주는 GROUPING_ID 함수도 있어, 조건이 복잡할 때는 이쪽이 더 간결합니다.
자주 틀리는 점: GROUPING 함수가 “원본 데이터에 NULL이 있으면 에러를 낸다”거나 “ROLLUP에만 쓸 수 있다”고 오해합니다. GROUPING은 단지 0 또는 1을 반환하는 평범한 함수이고, ROLLUP뿐 아니라 CUBE, GROUPING SETS에서도 똑같이 쓸 수 있습니다. GROUPING 함수가 존재하는 이유 자체가 “원본 데이터의 진짜 NULL”과 “집계로 인해 생긴 NULL”을 구분하기 위해서입니다.
직접 해보기
00편에서 만든 실습 환경에 이미 있는 급여 테이블(부서·직급·급여, 4행)로 바로 진행할 수 있습니다.
GROUP BY, ROLLUP, CUBE가 같은 두 컬럼(부서, 직급)에서 정말 다른 개수의 조합을 만드는지, 결과를 눈으로 세어 보는 대신 행 수 자체를 직접 비교해 봅니다.
SELECT
(SELECT COUNT(*) FROM (SELECT 부서, 직급 FROM 급여 GROUP BY 부서, 직급)) AS group_by_행수,
(SELECT COUNT(*) FROM (SELECT 부서, 직급 FROM 급여 GROUP BY ROLLUP(부서, 직급))) AS rollup_행수,
(SELECT COUNT(*) FROM (SELECT 부서, 직급 FROM 급여 GROUP BY CUBE(부서, 직급))) AS cube_행수
FROM DUAL;무엇을 보아야 하나: 세 값이 서로 다르게 나오는지, 그리고 그 차이가 본문에서 계산한 조합 개수 공식(GROUP BY는 4, ROLLUP은 n+1, CUBE는 2의 n제곱)과 정확히 맞아떨어지는지 확인하세요.
왜 이걸 해보나:
ROLLUP과CUBE의 행 수가 같다고 착각하는 오답이 시험에 자주 나옵니다. 공식을 외우는 것과 실제 행 수를 세어 눈으로 확인하는 것은 다릅니다.
이번에는 GROUPING 함수로 소계·총계 행만 따로 걸러내 봅니다.
SELECT 부서, 직급, SUM(급여) AS 급여합계
FROM 급여
GROUP BY ROLLUP(부서, 직급)
HAVING GROUPING(부서) = 1 AND GROUPING(직급) = 1;무엇을 보아야 하나: 몇 행이 나오는지, 그리고 그 행의 급여합계가 본문 5절에서 본 전체 총계 값과 일치하는지 확인하세요. HAVING 조건의 두 = 1을 하나씩 = 0으로 바꿔가며 다시 실행해 보면 상세 행과 소계 행도 각각 골라낼 수 있습니다.
왜 이걸 해보나:
GROUPING값의 조합으로 “이 행이 상세인지 소계인지 총계인지”를 구분하는 것은 보고서 쿼리에서 실제로 쓰이는 패턴이자, 시험에서HAVING GROUPING(...)조건을 해석하라는 문제로 그대로 나옵니다.
핵심 정리
- 세 구문 모두 여러
GROUP BY결과를 한 번에 합쳐 낸다는 점은 같지만, 어떤 조합을 만드는지가 다르다. GROUPING SETS는 나열한 조합만 정확히 만든다. 자동으로 추가되는 조합이 없고, 순서는 결과 구성에 영향을 주지 않는다.ROLLUP(n개 컬럼)은 오른쪽부터 계층적으로 제거하며 n+1개의 조합을 만든다. 컬럼 순서에 민감하다.CUBE(n개 컬럼)은 가능한 모든 포함·제외 조합인 2의 n제곱개를 만든다. 순서에 무관(대칭)하다.- 컬럼 2개 기준으로
ROLLUP은 3개 조합(7행),CUBE는 4개 조합(9행)이 나오며,CUBE의 결과 행 수가 항상ROLLUP이상이다. GROUPING(컬럼)은 그 컬럼이 소계·총계로 묶여NULL이 된 경우 1, 실제 값으로 묶인 경우 0을 반환해 원본NULL과 집계NULL을 구분한다.