DB

지피티와 함께하는 통계쿼리 공부일기 2

99duuk 2025. 6. 30. 17:07

ROLLUP을 이용한 부서별/직무별/성별 인원 및 급여 통계

MYSQL에서는 GROUP BY ... WITH ROLLUP 구문을 이용하여, 집계 결과로 생성되는 NULL 값들을 구분하기 위해 GROUPING() 함수를 사용할 수 있다.

GROUPING(col) = 1 이면 해당 행이 ROLLUP에 의해 생성된 소계/총계 행이라는 의미다. 

이를 응용하여, NULL 대신 '전체' 등의 라벨을 표시하면 결과를 이해하기 쉬워진다. 

 

 

ROLLUP은 컬럼의 순서에 따라 점진적으로 상위 수준의 총계를 계산한다. 
    - 컬럼 리스트를 계층적으로 줄여가며 총계를 만듦

    - ex) GROUP BY A, B, C WITH ROLLUP 

             (A, B, C): 개별 데이터. 

             (A, B): C에 대한 총계.

             (A): B, C에 대한 총계.

             (): 전체 총계

 

 

 

rollup을 사용하면 다중 수준의 집계 결과(부분합과 총합)을 자동으로 계산할 수 있다.

부서별, 직무별, 성별로 직원 수와 급여 합계를 집계하고, 각 부서별 소계(모든 직무 합계), 전체 총계를 출력해보자!

SELECT 
    IF(GROUPING(d.dept_name)=1, '전체 부서', d.dept_name)   AS dept_name,
    IF(GROUPING(j.job_title)=1, '전체 직무', j.job_title)   AS job_title,
    IF(GROUPING(e.gender)=1,    '전체',      e.gender)      AS gender,
    COUNT(*)   AS emp_count,               -- 직원 수
    SUM(e.salary) AS total_salary          -- 급여 총합 (합계)
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
JOIN jobs j        ON e.job_id  = j.job_id
GROUP BY d.dept_name, j.job_title, e.gender WITH ROLLUP;

 

요로코롬 WITH ROLLUP을 사용함으로써 부서별/직무별 세부내역과 함께 부서별 합계, 전체 합계를 계층적으로 얻었다. 

그리고 GROUPING()을 사용해 '전체' 라벨을 붙혀 가독성을 높일 수 있긔;;

 

 

+ 합계행을 상단에 올려버리고 싶으면 

ORDER BY
    GROUPING(d.dept_name) DESC, d.dept_name,
    GROUPING(j.job_title) DESC, j.job_title,
    GROUPING(e.gender) DESC, e.gender;

정렬을 추가하면 된다.

 

 

 

# if(조건, 참값, 거짓값)
# GROUPING() : 어떤 행이 실제 데이터고, 어떤 행이 집계 행인지 구분 (0: 실제 데이터, 1:NULL로 총계행)
    # 특정 컬럼이 그룹화 과정에서 집계로 인해 NULL로 표시가 되었는지 확인 (총계행은 컬럼이 NULL로 나타남)
    # rollup, cube, grouping sets 에서 사용, 집계된 행이 '실제 데이터'인지, 아니면 rollup/cube로 생성된 '집계행'인지 구분하는데 사용
    # WITH ROLLUP 또는 GROUPING SETS을 사용할 때 의미가 있다.

 


CUBE를 이용한 부서-직무-입사연도 조합별 통계

 

CUBE는 지정된 컬럼의 모든 가능한 조합에 대해 총계를 생성한다. ROLLUP은 계층적 조합만 생성하지만, CUBE는 모든 컬럼의 부분집합을 다 만들어벌임.

    - 컬럼 리스트를 계층적으로 줄여가며 총계를 만듦

    - ex) GROUP BY CUBE(A, B, C)  (총 조합 수: 2^n)

             (A, B, C): 개별 데이터. 

             (A, B): C에 대한 총계.

             (A, C): B에 대한 총계.

             (B, C): A에 대한 총계.

             (A): B, C에 대한 총계.

             (B): A, C에 대한 총계.

             (C): A, B에 대한 총계.

             (): 전체 총계

             (2^3 = 8개 조합)

(MYSQL은 CUBE를 지원하지 않는다. 대신 GROUPING SET이나 UNION ALL로 비슷하게 구현할 수 있다.) 

 

-- CUBE(dept_name, job_title, hire_year)의 효과를 내는 유니온 쿼리

-- (dept_name, job_title, hire_year): 개별 데이터.
SELECT d.dept_name AS dept_name,
       j.job_title AS job_title,
       YEAR(e.hire_date) AS hire_year,
       COUNT(*)  AS emp_count,
       SUM(e.salary) AS total_salary
FROM employees e
         JOIN departments d ON e.dept_id = d.dept_id
         JOIN jobs j        ON e.job_id  = j.job_id
GROUP BY d.dept_name, j.job_title, YEAR(e.hire_date)
UNION ALL

-- 부서-직무별 (dept_name, job_title): 연도 총계.
SELECT d.dept_name AS dept_name,
       j.job_title AS job_title,
       '전체'        AS hire_year,
       COUNT(*), SUM(e.salary)
FROM employees e
         JOIN departments d ON e.dept_id = d.dept_id
         JOIN jobs j        ON e.job_id  = j.job_id
GROUP BY d.dept_name, j.job_title
UNION ALL
-- 부서-연도별 (dept_name, hire_year): 직무 총계.
SELECT d.dept_name AS dept_name,
       '전체'       AS job_title,
       YEAR(e.hire_date) AS hire_year,
       COUNT(*), SUM(e.salary)
FROM employees e
         JOIN departments d ON e.dept_id = d.dept_id
GROUP BY d.dept_name, YEAR(e.hire_date)
UNION ALL
-- 직무-연도별 (job_title, hire_year): 부서 총계.
SELECT '전체'      AS dept_name,
       j.job_title AS job_title,
       YEAR(e.hire_date) AS hire_year,
       COUNT(*), SUM(e.salary)
FROM employees e
         JOIN jobs j ON e.job_id = j.job_id
GROUP BY j.job_title, YEAR(e.hire_date)
UNION ALL
-- 부서별 전체 (dept_name): 직무, 연도 총계.
SELECT d.dept_name AS dept_name,
       '전체'       AS job_title,
       '전체'       AS hire_year,
       COUNT(*), SUM(e.salary)
FROM employees e
         JOIN departments d ON e.dept_id = d.dept_id
GROUP BY d.dept_name
UNION ALL
-- 직무별 전체 (job_title): 부서, 연도 총계.
SELECT '전체'      AS dept_name,
       j.job_title AS job_title,
       '전체'       AS hire_year,
       COUNT(*), SUM(e.salary)
FROM employees e
         JOIN jobs j ON e.job_id = j.job_id
GROUP BY j.job_title
UNION ALL
-- 연도별 전체 (hire_year): 부서, 직무 총계.
SELECT '전체'      AS dept_name,
       '전체'      AS job_title,
       YEAR(e.hire_date) AS hire_year,
       COUNT(*), SUM(e.salary)
FROM employees e
GROUP BY YEAR(e.hire_date)
UNION ALL
-- 전체 총계 (모든 부서/직무/연도)
SELECT '전체' AS dept_name, '전체' AS job_title, '전체' AS hire_year,
       COUNT(*), SUM(e.salary)
FROM employees e;

 

...

 

모든 조합에 대한 행이 출력된다. 

 


GROUPING SETS를 활용한 다양한 조합의 소계/총계 분석

임의의 그룹화 조합에 대한 집계를 수행해보게씀.

GROUPING SETS는 ROLLUP이나 CUBE와 달리, 원하는 특정 그룹화의 조합만 선택하여 결과를 얻을 수 있긔.

예를 들어 부서별 합계와 성별 합계만 한 번에 보고 싶은 그런 상황에서 활용하믄 댐.
(MySQL에서는 마찬가지로 GROUPING SETS를 지원하지 않으므로 UNION으로 대체하게씀 첨부터 오라클로 할 걸...)

 

-- GROUPING SETS ( (dept_name, gender), (dept_name), (gender), () ) 시뮬레이션
# 부서-성별별 통계
SELECT d.dept_name AS dept_name, e.gender AS gender,
       COUNT(*) AS emp_count, SUM(e.salary) AS total_salary
FROM employees e
         JOIN departments d ON e.dept_id = d.dept_id
GROUP BY d.dept_name, e.gender
UNION ALL
# 부서별 전체 통계
SELECT d.dept_name AS dept_name, '전체' AS gender,
       COUNT(*), SUM(e.salary)
FROM employees e
         JOIN departments d ON e.dept_id = d.dept_id
GROUP BY d.dept_name
UNION ALL
# 성별별 전체 통계
SELECT '전체' AS dept_name, e.gender AS gender,
       COUNT(*), SUM(e.salary)
FROM employees e
GROUP BY e.gender
UNION ALL
# 전체 총계
SELECT '전체' AS dept_name, '전체' AS gender,
       COUNT(*), SUM(e.salary)
FROM employees e;

 

  • 부서별 + 성별별 세부 내역 (첫 번째 SELECT): 각 부서의 남/여 직원 수 및 급여 합계
  • 부서별 전체 (두 번째 SELECT): 각 부서의 전체 직원 합계 (성별 무관)
  • 성별별 전체 (세 번째 SELECT): 전체 회사를 성별로 구분한 합계 (부서 무관)
  • 전사 총계 (네 번째 SELECT): 전체 직원 합계

 

 

 

 

# GROUPING SETS은 사용자가 원하는 특정 그룹화 조합을 명시적으로 지정해 집계를 생성한다. 
# ROLLUP이나 CUBE와는 달리 그룹화 조합을 선택할 수 있어 유연성이 높다.
GROUP BY GROUPING SETS (
	(col1, col2, ...) -- 조합 1 
    (col1, ...) 	  -- 조합 2
    (), 			  -- 전체 총계
)

# 각 괄호는 하나의 그룹화 조합을 나타내고, 각 조합에 대해 집계함수를 적용해 결과를 생성할 수 있음

 

 


ROLLUP:
    계층적 총계 생성.
    예: GROUP BY A, B, C WITH ROLLUP → (A, B, C), (A, B), (A), ().
    순서에 따라 제한된 조합만 생성.

CUBE:
    모든 가능한 조합 생성.
    예: CUBE(A, B, C) → (A, B, C), (A, B), (A, C), (B, C), (A), (B), (C), ().
    MySQL은 직접 지원하지 않음(대신 GROUPING SETS나 UNION ALL로 구현).

GROUPING SETS:
    원하는 조합만 선택.
    예: GROUPING SETS((A, B), (A, C), ()) → (A, B), (A, C), 전체 총계만 생성.
    ROLLUP이나 CUBE의 부분집합 또는 커스텀 조합 가능.

 


CASE WHEN을 활용한 피벗(Pivot) 형태 요약표

영어 단어 pivot의 원뜻:
명사: 회전축, 중심점 동사: 축을 중심으로 회전하다
ex) 모니터암 달아서 모니터를 가로 -> 세로 돌리기 ~ 

데이터를 어떤 기준을 중심으로 회전시켜서
행(row) → 열(column) 또는
열 → 행
형태로 바꾸는 것을 "피벗한다(pivot)"고 표현

 

피벗 형태로 변환하여 열(column) 기준의 요약표를 만들어보겠긔.

(또) MySQL은 PIVOT 연산에 대한 전용 문법이 없으므로, CASE WHEN 과 집계함수를 조합하여 행을 열로 바꾸겠긔.

 

 

피벗 변환의 핵심은 조건부 집계이다. 

조건에 따라 값을 집계하고, 그 결과를 별도 컬럼으로 표현하면 행 -> 열 변환 효과를 얻을 수 있다.

 

부서별 성별 인원 현황표를 만들어 보자!~

SELECT
    d.dept_name AS dept_name,
    SUM(CASE WHEN e.gender = 'M' THEN 1 ELSE 0 END) AS male_count,
    SUM(CASE WHEN e.gender = 'F' THEN 1 ELSE 0 END) AS female_count
FROM employees e
         JOIN departments d ON e.dept_id = d.dept_id
GROUP BY d.dept_name;

CASE WHEN e.gender = 'M' THEN 1 ELSE 0 END 표현식은 해당 직원이 남성일 경우 1, 아니면 0을 반환한다. SUM()을 씌우면 그 부서의 남성 직원 수 합계가 구해진다. 

이처럼 동일한 그룹 내에서 조건별로 따로 집계한 결과를 각각 컬럼으로 표시하면 행 형식의 데이터를 열 형식으로 볼 수 있다. 

부서명 행에 성별 열을 갖는 교차표 형태로 데이터를 확인할 수 있다.