CHHB stroy

SQL Server PIVOT과 UNPIVOT 완벽 정리 본문

DB

SQL Server PIVOT과 UNPIVOT 완벽 정리

CHHB 2026. 5. 11. 13:29

한 줄 요약

  • PIVOT: 행(Row)을 열(Column)로 변환
  • UNPIVOT: 열(Column)을 행(Row)으로 변환

서로 정반대 방향의 변환이다.


PIVOT — 행을 열로 회전

언제 쓰는가?

월별 매출 데이터가 행으로 12줄 쌓여 있는데, 한 줄에 1월~12월 컬럼으로 보고 싶을 때 사용한다.

변환 전 (행 기반)

TEAM MONTH AMOUNT
영업1팀 1 1000
영업1팀 2 2000
영업1팀 3 1500

변환 후 (열 기반)

TEAM 1월 2월 3월
영업1팀 1000 2000 1500

문법

SELECT *
FROM 원본테이블
PIVOT (
    집계함수(값컬럼)
    FOR 회전할컬럼 IN ([값1], [값2], [값3], ...)
) AS P

실전 예제

-- 팀별 월매출이 행으로 저장된 테이블
SELECT TEAM, [1] AS M01, [2] AS M02, [3] AS M03,
       [4] AS M04, [5] AS M05, [6] AS M06,
       [7] AS M07, [8] AS M08, [9] AS M09,
       [10] AS M10, [11] AS M11, [12] AS M12
FROM (
    SELECT TEAM, GOAL_MONTH, GOAL_AMOUNT
    FROM TSZM_GOAL_AMT
    WHERE GOAL_YEAR = '2026'
) AS SRC
PIVOT (
    SUM(GOAL_AMOUNT)
    FOR GOAL_MONTH IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12])
) AS P

핵심 포인트

  • 집계함수는 필수다. SUM, MAX, COUNT 등을 써야 한다.
  • IN 안의 값은 하드코딩해야 한다. 동적으로 하려면 동적 SQL이 필요하다.
  • PIVOT 결과에서 값이 없는 셀은 NULL이 된다. ISNULL로 0 처리하면 된다.

UNPIVOT — 열을 행으로 회전

언제 쓰는가?

M01~M12 컬럼으로 저장된 데이터를 월별 행으로 풀어서 INSERT하거나 비교할 때 사용한다.

변환 전 (열 기반)

TEAM M01 M02 M03
영업1팀 1000 2000 1500

변환 후 (행 기반)

TEAM MONTH_COL AMOUNT
영업1팀 M01 1000
영업1팀 M02 2000
영업1팀 M03 1500

문법

SELECT *
FROM 원본테이블
UNPIVOT (
    값을담을컬럼 FOR 컬럼명을담을컬럼 IN ([컬럼1], [컬럼2], [컬럼3], ...)
) AS U

실전 예제

-- M01~M12 컬럼을 월별 행으로 변환
SELECT
    TEAM_CODE,
    REPLACE(MONTH_COL, 'M', '') AS GOAL_MONTH,
    GOAL_AMOUNT
FROM #COLS
UNPIVOT (
    GOAL_AMOUNT FOR MONTH_COL IN (M01,M02,M03,M04,M05,M06,M07,M08,M09,M10,M11,M12)
) AS U

핵심 포인트

  • UNPIVOT 대상 컬럼들은 데이터 타입이 동일해야 한다. 하나라도 다르면 에러가 난다.
  • NULL 값이 있는 행은 자동으로 제외된다. NULL도 포함하려면 미리 ISNULL로 기본값을 채워야 한다.
  • MONTH_COL에는 컬럼명이 문자열로 들어온다. 위 예제처럼 REPLACE로 가공해서 사용할 수 있다.

PIVOT vs UNPIVOT 비교

구분 PIVOT UNPIVOT
방향 행 → 열 열 → 행
필수 요소 집계함수 (SUM, MAX 등) 없음
NULL 처리 NULL로 채워짐 NULL 행 자동 제외
주 용도 리포트, 크로스탭 조회 데이터 정규화, MERGE용 변환
IN 절 변환될 값 나열 변환할 컬럼명 나열

실무 활용 패턴

PIVOT: 월별 실적 리포트

-- 팀별 월별 매출을 한 줄로 조회
SELECT DEPT_CODE,
       ISNULL([1], 0) AS [1월],
       ISNULL([2], 0) AS [2월],
       ISNULL([3], 0) AS [3월]
       -- ... 12월까지
FROM (
    SELECT DEPT_CODE, GOAL_MONTH, GOAL_AMOUNT
    FROM TSZM_GOAL_AMT
    WHERE GOAL_YEAR = '2026'
) SRC
PIVOT (
    SUM(GOAL_AMOUNT) FOR GOAL_MONTH IN ([1],[2],[3])
) P

UNPIVOT + MERGE: 일괄 저장

-- 컬럼 기반 임시테이블을 행으로 풀어서 MERGE
MERGE INTO TSZM_GOAL_AMT AS T
USING (
    SELECT TEAM_CODE,
           CAST(REPLACE(MONTH_COL, 'M', '') AS INT) AS GOAL_MONTH,
           ISNULL(GOAL_AMOUNT, 0) AS GOAL_AMOUNT
    FROM #COLS
    UNPIVOT (
        GOAL_AMOUNT FOR MONTH_COL IN (M01,M02,M03,M04,M05,M06,M07,M08,M09,M10,M11,M12)
    ) AS U
) AS S
ON T.DEPT_CODE = S.TEAM_CODE AND T.GOAL_MONTH = S.GOAL_MONTH
WHEN MATCHED THEN UPDATE SET GOAL_AMOUNT = S.GOAL_AMOUNT
WHEN NOT MATCHED THEN INSERT (...) VALUES (...);