소스: sql-src/mid_05_case/02_conditional_agg.sql, 03_dialects.sql. 조건부 집계, UPDATE 에서의 CASE, DB 별 편의 함수 차이를 확인합니다. orders 테이블에 부서별·상태별 주문 12건을 추가했습니다.
SELECT
e.dept
, SUM(CASE WHEN o.status = '완료' THEN o.amt ELSE 0 END) AS amt_done
, SUM(CASE WHEN o.status = '취소' THEN o.amt ELSE 0 END) AS amt_cancel
, SUM(CASE WHEN o.status = '보류' THEN o.amt ELSE 0 END) AS amt_hold
FROM orders o
JOIN emp e ON e.id = o.emp_id
GROUP BY e.dept
ORDER BY e.dept;DEPT | AMT_DONE | AMT_CANCEL | AMT_HOLD
-----+----------+------------+---------
개발 | 1200 | 300 | 200
영업 | 1300 | 150 | 0
인사 | 0 | 300 | 180
지원 | 620 | 0 | 0
(4행)상태마다 SUM(CASE ...) 을 하나씩 늘어놓으면 GROUP BY 한 번으로 상태별 금액이 컬럼으로 펼쳐집니다. 해당 상태가 아닌 행은 CASE 가 0 을 돌려주므로 SUM 에 영향을 주지 않습니다.
팁이렇게 상태 값을 미리 아는 소수의 컬럼으로 펼치는 정도라면 CASE 조합으로 충분합니다. 컬럼 목록을 데이터에 따라 동적으로 정해야 하는 본격 PIVOT 은 고급 05 에서 다룹니다.
SELECT
COUNT(*) AS total_cnt
, COUNT(CASE WHEN o.status = '완료' THEN 1 END) AS done_cnt
, COUNT(CASE WHEN o.status = '취소' THEN 1 END) AS cancel_cnt
, COUNT(CASE WHEN o.status = '보류' THEN 1 END) AS hold_cnt
FROM orders o;TOTAL_CNT | DONE_CNT | CANCEL_CNT | HOLD_CNT
----------+----------+------------+---------
12 | 7 | 3 | 2
(1행)COUNT 는 NULL 을 세지 않는다는 성질을 이용했습니다. CASE 가 조건에 안 맞는 행에 ELSE 없이 NULL 을 돌려주면 COUNT(CASE ...) 는 그 행을 건너뛰고, 조건에 맞는 행만 1 로 세어 건수를 구합니다. 7 + 3 + 2 = 12로 전체 건수와도 맞습니다.
UPDATE emp
SET sal =
CASE WHEN dept = '개발' THEN sal + 50
WHEN dept = '영업' THEN sal + 30
ELSE sal
END
WHERE dept IN ('개발', '영업', '인사');개발·영업·인사 세 부서를 한 번에 걸러 놓고, 부서별로 다른 인상액은 CASE 로 나눴습니다. 실행 뒤 조회하면 개발은 50, 영업은 30 이 오르고 인사는 ELSE 의 sal(원래 값)이 그대로 들어가 안 바뀐 것을 확인할 수 있습니다. WHERE 절의 부서 목록과 CASE 안의 부서 조건이 겹치지만, 이렇게 하면 부서별 UPDATE 문을 여러 번 나눠 쓰지 않아도 됩니다.
| 기능 | Oracle · Tibero | MySQL | MSSQL |
|---|---|---|---|
| CASE(단순·검색) | 지원 | 지원 | 지원 |
| DECODE | 지원 | 없음 | 없음 |
| IF(cond, a, b) | 없음 | 지원 | 없음 |
| IIF(cond, a, b) | 없음 | 없음 | 지원(2012 부터) |
| CHOOSE(i, ...) | 없음 | 없음 | 지원(2012 부터) |
주의IIF·CHOOSE 는 MSSQL 2012 이전 버전에는 없습니다. 배포 대상 MSSQL 버전이 그보다 낮다면 CASE 로 써야 하고, 확신이 없으면 처음부터 CASE 로 작성하는 편이 안전합니다.
DECODE 는 단순 CASE 와 같은 자리에서 값을 비교해 바꿔치기하는 Oracle·Tibero 전용 함수입니다. mgr 컬럼은 대표이사인 김대표만 NULL 이고, DECODE 로 NULL 여부를 표시해 봅니다.
SET MODE Oracle;
SELECT
name
, DECODE(mgr, NULL, '없음', '있음') AS mgr_flag
FROM emp
ORDER BY id;NAME | MGR_FLAG
-------+---------
김대표 | 없음
이팀장 | 있음
박선임 | 있음
최사원 | 있음
정팀장 | 있음
서팀장 | 있음
강팀장 | 있음
한주임 | 있음
문사원 | 있음
오사원 | 있음
(10행)SELECT
name
, CASE mgr WHEN NULL THEN '없음' ELSE '있음' END AS mgr_flag
FROM emp
ORDER BY id;NAME | MGR_FLAG
-------+---------
김대표 | 있음
이팀장 | 있음
박선임 | 있음
최사원 | 있음
정팀장 | 있음
서팀장 | 있음
강팀장 | 있음
한주임 | 있음
문사원 | 있음
오사원 | 있음
(10행)DECODE 는 김대표의 NULL 을 두 번째 인자 NULL 과 같다고 보고 없음을 돌려줬습니다. 단순 CASE 는 내부적으로 mgr = NULL 비교라 김대표를 포함해 모든 행이 있음(ELSE)으로 빠졌습니다. 2.5절에서 설명한 차이가 그대로 재현됩니다.
SET MODE MySQL;
SELECT IF(sal > 500, 'Y', 'N') FROM emp;예상 오류: Syntax error in SQL statementSET MODE MSSQLServer;
SELECT IIF(sal > 500, 'Y', 'N') FROM emp;예상 오류: Function "IIF" not foundSELECT CHOOSE(1, 'a', 'b', 'c') FROM emp;예상 오류: Function "CHOOSE" not found이 레슨의 H2 는 MySQL 모드에서 IF() 를 문법 오류로, MSSQL 모드에서 IIF·CHOOSE 를 "함수를 찾을 수 없음" 오류로 거절합니다. 셋 다 "H2 미지원, 문법 검토만"이며 실제 MySQL·MSSQL(2012 부터)에서는 정상 동작하는 함수입니다. 세 함수 모두 같은 검색 CASE 로 대체됩니다.
SELECT
name
, CASE WHEN sal > 500 THEN 'Y' ELSE 'N' END AS flag
FROM emp
ORDER BY id;NAME | FLAG
-------+-----
김대표 | Y
이팀장 | Y
박선임 | N
최사원 | N
정팀장 | Y
서팀장 | Y
강팀장 | N
한주임 | N
문사원 | N
오사원 | N
(10행)IF(cond, a, b)·IIF(cond, a, b) 는 CASE WHEN cond THEN a ELSE b END 와 뜻이 완전히 같습니다. CHOOSE(i, v1, v2, ...) 는 순번 i 로 값을 고르는 함수라 CASE i WHEN 1 THEN v1 WHEN 2 THEN v2 ... END 로 옮길 수 있습니다. 이 레슨 데이터에는 순번으로 고를 컬럼이 따로 없어 CHOOSE 대체 쿼리는 개념만 짚고 넘어갑니다.