Dev/DB & Infra

[ORACLE 강좌] Oracle GROUP BY ROLLUP / CUBE / GROUPING SETS 완전 정리

develophil 2012. 8. 22. 17:10
반응형

집계 쿼리를 작성하다 보면 소계, 합계를 한 번에 뽑아야 할 때가 있습니다.
ROLLUP, CUBE, GROUPING SETS는 GROUP BY의 확장 문법으로, 여러 번 쿼리를 실행할 필요 없이 계층적 집계를 한 번에 처리합니다.

Oracle 9i 이상에서 모두 사용 가능합니다.


비교 한눈에 보기

구문 생성되는 집계 특징
GROUP BY ROLLUP(A, B) (A,B), (A), () 계층적 소계 — 오른쪽에서 왼쪽으로
GROUP BY CUBE(A, B) (A,B), (A), (B), () 모든 조합의 소계
GROUP BY GROUPING SETS((A,B), (A), ()) 지정한 조합만 원하는 집계만 선택

예제 데이터 (EMP, DEPT 테이블)

이하 예제는 Oracle 기본 샘플 테이블(EMP, DEPT)을 기준으로 합니다.

-- 기본 JOIN 구조
SELECT b.dname, a.job, a.sal, a.empno
FROM emp a, dept b
WHERE a.deptno = b.deptno;

ROLLUP

GROUP BY에 ROLLUP을 추가하면 지정한 컬럼 순서대로 소계와 전체 합계를 함께 출력합니다.

-- 직군별 급여 합계 + 전체 합계
SELECT job, SUM(sal)
FROM emp
GROUP BY ROLLUP(job);

결과 예시:

JOB SUM(SAL)
ANALYST 6000
CLERK 4150
MANAGER 8275
PRESIDENT 5000
SALESMAN 5600
(전체 합계) 29025
-- 부서별, 직군별 소계 + 부서 소계 + 전체 합계
SELECT b.dname, a.job, SUM(a.sal) sal, COUNT(a.empno) emp_count
FROM emp a, dept b
WHERE a.deptno = b.deptno
GROUP BY ROLLUP(b.dname, a.job);

집계 레벨:

  • (dname, job) → 부서 + 직군별 합계
  • (dname) → 부서별 소계
  • () → 전체 합계 (Grand Total)

CUBE

ROLLUP이 계층적 집계라면, CUBE는 지정한 컬럼의 모든 조합에 대해 소계를 생성합니다.

SELECT b.dname, a.job, SUM(a.sal) sal, COUNT(a.empno) emp_count
FROM emp a, dept b
WHERE a.deptno = b.deptno
GROUP BY CUBE(b.dname, a.job);

집계 레벨:

  • (dname, job) → 부서 + 직군별 합계
  • (dname) → 부서별 소계
  • (job) → 직군별 소계 (ROLLUP에는 없음)
  • () → 전체 합계

컬럼 수가 N개이면 CUBE는 최대 2ⁿ 조합을 생성합니다. 컬럼이 많을수록 결과 행이 기하급수적으로 늘어납니다.


GROUPING SETS

ROLLUP, CUBE 대신 원하는 집계 조합만 직접 지정할 수 있습니다. 불필요한 소계를 제거해 성능과 가독성을 높일 때 유용합니다.

-- 부서+직군 합계와 전체 합계만 (부서별 소계 제외)
SELECT b.dname, a.job, SUM(a.sal) sal
FROM emp a, dept b
WHERE a.deptno = b.deptno
GROUP BY GROUPING SETS((b.dname, a.job), ());

-- 부서별 합계와 직군별 합계만 (교차 없이)
SELECT b.dname, a.job, SUM(a.sal) sal
FROM emp a, dept b
WHERE a.deptno = b.deptno
GROUP BY GROUPING SETS((b.dname), (a.job));

ROLLUP을 GROUPING SETS로 표현하면:

-- 아래 두 쿼리는 동일한 결과
GROUP BY ROLLUP(dname, job)
GROUP BY GROUPING SETS((dname, job), (dname), ())

GROUPING 함수

ROLLUP/CUBE 결과에서 소계 행인지 일반 집계 행인지 구분할 때 사용합니다.

  • 반환 0 → GROUP BY로 만들어진 일반 집계 행

  • 반환 1 → ROLLUP/CUBE가 생성한 소계/합계 행

    SELECT b.dname, a.job,

         SUM(a.sal) sal,
         COUNT(a.empno) emp_count,
         GROUPING(b.dname) AS grp_dname,
         GROUPING(a.job) AS grp_job

    FROM emp a, dept b
    WHERE a.deptno = b.deptno
    GROUP BY CUBE(b.dname, a.job);

활용 — NULL 대신 레이블 표시:

SELECT
    CASE WHEN GROUPING(b.dname) = 1 THEN '전체 부서' ELSE b.dname END AS dname,
    CASE WHEN GROUPING(a.job) = 1 THEN '전체 직군' ELSE a.job END AS job,
    SUM(a.sal) sal
FROM emp a, dept b
WHERE a.deptno = b.deptno
GROUP BY CUBE(b.dname, a.job);

소계/합계 행의 NULL을 의미 있는 레이블로 치환할 수 있어 리포트에서 자주 활용됩니다.


GROUPING_ID 함수

여러 컬럼의 GROUPING 값을 비트 조합한 정수로 반환합니다. 집계 레벨을 하나의 숫자로 판별할 수 있어 GROUPING()을 여러 번 쓰는 것보다 편리합니다.

SELECT b.dname, a.job,
       SUM(a.sal) sal,
       GROUPING_ID(b.dname, a.job) AS grp_id
FROM emp a, dept b
WHERE a.deptno = b.deptno
GROUP BY CUBE(b.dname, a.job);

GROUPING_ID 해석 (컬럼 2개 기준):

GROUPING_ID dname job 의미
0 0 0 부서+직군 상세
1 0 1 부서별 소계
2 1 0 직군별 소계
3 1 1 전체 합계
-- 전체 합계 행만 필터링
SELECT b.dname, a.job, SUM(a.sal) sal
FROM emp a, dept b
WHERE a.deptno = b.deptno
GROUP BY CUBE(b.dname, a.job)
HAVING GROUPING_ID(b.dname, a.job) = 3;

실무 활용 팁

1. 리포트에서 소계 행 강조

SELECT
    NVL2(GROUPING(dname) + GROUPING(job),
        CASE GROUPING_ID(dname, job)
            WHEN 3 THEN '[ 전체 합계 ]'
            WHEN 1 THEN '[ ' || dname || ' 소계 ]'
            ELSE dname
        END, dname) AS dname,
    SUM(sal) sal
FROM emp a JOIN dept b ON a.deptno = b.deptno
GROUP BY ROLLUP(b.dname, a.job);

2. 불필요한 조합 제거 — GROUPING SETS 우선

CUBE(A, B, C)는 8가지 조합을 만들지만 실제로 필요한 게 3가지뿐이라면 GROUPING SETS로 명시하는 게 성능상 유리합니다.

3. ORDER BY 주의

ROLLUP/CUBE 결과는 기본 정렬이 보장되지 않습니다. 소계 행이 중간에 섞여 나올 수 있으니 ORDER BY에 GROUPING() 값을 포함하세요.

ORDER BY GROUPING(dname), dname, GROUPING(job), job

마치며

상황 추천
계층적 소계가 필요할 때 ROLLUP
모든 차원의 교차 집계가 필요할 때 CUBE
원하는 집계 조합만 뽑을 때 GROUPING SETS
소계 행 식별 / 레이블 치환 GROUPING, GROUPING_ID
반응형