PIVOT으로 행 값을 열로 펼쳐 집계하고, UNPIVOT으로 여러 열을 행으로 모읍니다. 집계 필수·암묵적 그룹화·NULL 제외를 기출 함정 중심으로 익힙니다.
가끔기출 11회분 중 6문항 · 많이 나온 주제: PIVOT/UNPIVOT(6)
학습 목표
PIVOT으로 행 값을 열로 펼쳐 집계하고, UNPIVOT으로 여러 열을 행으로 모읍니다. 집계 필수·암묵적 그룹화·NULL 제외를 기출 함정 중심으로 익힙니다.
01
PIVOT과 UNPIVOT 한눈에 보기
PIVOT은 행에 들어 있는 구분 값을 열 머리글로 펼쳐 집계합니다(세로 → 가로). UNPIVOT은 여러 값 열을 '구분 열 + 값 열' 두 열의 행으로 모읍니다(가로 → 세로). 둘 다 Oracle의 FROM 절 뒤에 붙는 구문입니다.
두 연산은 방향만 반대일 뿐 서로 완전히 되돌리지는 못합니다. PIVOT은 집계하면서 여러 행을 한 칸으로 합치고, UNPIVOT은 기본적으로 NULL 칸을 버리기 때문입니다.
작은 표로 보는 PIVOT과 UNPIVOT
원본 (세로) 4행
BOOTH
PAY
AMT
B-01
CARD
120
B-01
CARD
80
B-01
CASH
50
B-02
CARD
200
PIVOT→PAY 값이 열로, SUM으로 합침
PIVOT 결과 (가로) 2행
BOOTH
CARD
CASH
B-01
200
50
B-02
200
NULL
UNPIVOT→열이 다시 행으로, NULL 칸은 버림
UNPIVOT 결과 (세로) 3행
BOOTH
PAY
AMT
B-01
CARD
200
B-01
CASH
50
B-02
CARD
200
B-01 카드 120 + 80 = 200으로 합쳐지고, B-02 현금 NULL 칸은 UNPIVOT에서 사라져 원본 4행이 아니라 3행이 됩니다. 그래서 둘은 완전한 역연산이 아닙니다.
-- PIVOT: FOR 뒤 = 열 머리글이 될 열, IN = 열로 펼칠 값PIVOT(집계함수(값열)FOR 기준열 IN('값1'AS 열이름1,'값2'AS 열이름2))-- UNPIVOT: FOR 뒤 = 새 구분 열, IN = 행으로 내릴 원본 열 이름UNPIVOT(값열 FOR 구분열
IN(원본열1 AS'값1', 원본열2 AS'값2'))
02
PIVOT
PIVOT에는 집계 함수가 반드시 있어야 합니다. FOR 뒤에는 열 머리글이 될 열을, IN 안에는 열로 펼칠 값을 적습니다. IN에 적은 값만 열이 되고, 적지 않은 값은 결과에 나타나지 않습니다.
PIVOT은 GROUP BY를 따로 쓰지 않습니다. 입력에 남아 있는 열 중 집계 대상도 FOR 대상도 아닌 열이 자동으로 그룹 기준이 됩니다. 그래서 테이블을 바로 PIVOT하면 SALE_ID 같은 열까지 그룹 기준이 되어 행이 쪼개집니다. 인라인 뷰에서 필요한 열만 남긴 뒤 PIVOT합니다.
예시는 축제 부스 결제 기록 10건입니다. B-01은 카드 결제가 2건이고, B-03 카드는 실제 0원, B-01 앱과 B-04 현금은 금액이 NULL입니다.
TABLEFESTIVAL_SALE — 결제 기록 10건10행
SALE_ID
BOOTH_CODE
PAY_METHOD
AMOUNT
1
B-01
CARD
120
2
B-01
CARD
80
3
B-01
CASH
50
4
B-01
APP
NULL
5
B-02
CARD
200
6
B-02
APP
90
7
B-03
CASH
70
8
B-03
APP
40
9
B-03
CARD
0
10
B-04
CASH
NULL
-- 인라인 뷰에서 그룹 기준(BOOTH_CODE)만 남기고 PIVOT → 4행SELECT*FROM(SELECT BOOTH_CODE, PAY_METHOD, AMOUNT
FROM FESTIVAL_SALE)PIVOT(SUM(AMOUNT)FOR PAY_METHOD IN('CARD'AS CARD_AMT,'CASH'AS CASH_AMT,'APP'AS APP_AMT))ORDERBY BOOTH_CODE;-- 함정: 테이블을 바로 PIVOT하면 SALE_ID도 그룹 기준 → 10행SELECT*FROM FESTIVAL_SALE
PIVOT(SUM(AMOUNT)FOR PAY_METHOD IN('CARD','CASH','APP'));
TABLEPIVOT 결과 — 부스별 4행4행
BOOTH_CODE
CARD_AMT
CASH_AMT
APP_AMT
B-01
200
50
NULL
B-02
200
NULL
90
B-03
0
70
40
B-04
NULL
NULL
NULL
B-01 카드는 120 + 80 = 200으로 합쳐지고, B-03 카드는 실제 값 0이 그대로 남습니다. B-02 현금은 거래가 없어서, B-01 앱은 금액이 NULL이라서 둘 다 NULL로 보입니다.
03
UNPIVOT
UNPIVOT은 UNPIVOT (값열 FOR 구분열 IN (원본열, ...)) 형태입니다. IN 안에는 행으로 내릴 원본 열 이름을 적고, 그 열의 값은 새 값 열로, 어느 열에서 왔는지는 새 구분 열로 들어갑니다. AS 'CARD'를 붙이면 구분 열에 원본 열 이름 대신 그 값이 들어갑니다.
기본값은 EXCLUDE NULLS라서 NULL 칸은 행으로 만들지 않습니다. NULL까지 남기려면 UNPIVOT INCLUDE NULLS를 씁니다. IN에 나열한 원본 열들은 데이터 타입이 호환되어야 합니다.
-- 앞의 PIVOT 결과(WIDE)를 다시 세로로: 기본 NULL 제외 → 7행SELECT BOOTH_CODE, PAY_METHOD, AMOUNT
FROM WIDE
UNPIVOT(AMOUNT FOR PAY_METHOD
IN(CARD_AMT AS'CARD',
CASH_AMT AS'CASH',
APP_AMT AS'APP'));-- INCLUDE NULLS: 4행 × 3열 = 12행 모두
UNPIVOT 행 수
옵션
남는 칸
행 수
기본 (EXCLUDE NULLS)
B-01 2칸, B-02 2칸, B-03 3칸, B-04 0칸
7
INCLUDE NULLS
4행 × 3열 전부
12
TABLE기본 UNPIVOT 결과 — 7행7행
BOOTH_CODE
PAY_METHOD
AMOUNT
B-01
CARD
200
B-01
CASH
50
B-02
APP
90
B-02
CARD
200
B-03
APP
40
B-03
CARD
0
B-03
CASH
70
04
CASE 집계로 바꿔 쓰기와 NULL·0
PIVOT 없이도 SUM(CASE WHEN 구분 = '값' THEN 금액 END)와 GROUP BY로 같은 교차표를 만들 수 있습니다. ELSE를 생략하면 조건에 맞지 않는 행은 NULL이 되고, SUM은 NULL을 빼고 더하므로 PIVOT과 결과가 같습니다.
ELSE 0을 붙이면 조건에 맞지 않는 행도 0으로 더해져 '거래 없음'과 '실제 0원'을 구분할 수 없게 됩니다. 비슷하게 집계 함수에 따라 빈 칸의 값도 다릅니다. SUM은 빈 칸이 NULL, COUNT(*)는 빈 칸이 0입니다.
-- PIVOT 결과를 WITH로 두고 UNPIVOT: INCLUDE NULLS → 12행WITH WIDE AS(SELECT*FROM(SELECT BOOTH_CODE, PAY_METHOD, AMOUNT
FROM FESTIVAL_SALE)PIVOT(SUM(AMOUNT)FOR PAY_METHOD IN('CARD'AS CARD_AMT,'CASH'AS CASH_AMT,'APP'AS APP_AMT)))SELECT BOOTH_CODE, PAY_METHOD, AMOUNT
FROM WIDE
UNPIVOT INCLUDE NULLS(AMOUNT FOR PAY_METHOD
IN(CARD_AMT AS'CARD',
CASH_AMT AS'CASH',
APP_AMT AS'APP'))ORDERBY BOOTH_CODE, PAY_METHOD;
집계 함수를 두 개 쓰면 결과 열 이름이 IN 별칭_집계 별칭으로 만들어집니다. 아래 쿼리는 CARD_ROWS_CNT, CARD_KNOWN 같은 열을 만듭니다.
SELECT*FROM(SELECT BOOTH_CODE, PAY_METHOD, AMOUNT
FROM FESTIVAL_SALE)PIVOT(COUNT(*)AS ROWS_CNT,COUNT(AMOUNT)AS KNOWN
FOR PAY_METHOD IN('CARD'AS CARD,'CASH'AS CASH,'APP'AS APP))ORDERBY BOOTH_CODE;
TABLE거래 행 수 / 금액 기록 행 수4행
BOOTH_CODE
CARD
CASH
APP
B-01
2 / 2
1 / 1
1 / 0
B-02
1 / 1
0 / 0
1 / 1
B-03
1 / 1
1 / 1
1 / 1
B-04
0 / 0
1 / 0
0 / 0
참고: IN 목록은 고정이라 새 결제수단이 생겨도 열이 저절로 늘지 않습니다. 결과 순서는 ORDER BY로만 정해집니다.