Skillset/SQL

SQL CASE WHEN 구문 사용법 - 예제로 알아보기

Everly. 2022. 6. 7. 13:17

오늘은 SQL에서 매우 자주 쓰이는 CASE WHEN 구문에 대해 포스팅합니다.

CASE WHEN 구문은 SELECT절에 쓰이며, 대표적으로 2가지의 쓰임새가 있는데요!

1) 새로운 열을 생성하는 경우

SELECT CASE WHEN 기존 열 = 조건 THEN '값 1'
                          WHEN 기존 열 = 조건2 THEN '값 2'
                                       ....
                          (ELSE '값 N') END AS 새로운 열

 

2) 열을 집계하는 경우 (집계함수와 함께 사용) : 집계 열에 집계함수를 적용

SELECT 집계함수((DISTINCT) CASE WHEN 기존 열 = 조건 THEN 집계 열 (ELSE 값) END) AS 새로운 열

 

자주 사용하므로 반드시 알아야 하는 구문입니다. 바로 예제로 알아보겠습니다!

 

✔Table of Contents

     

    예제 1.

    1) 새로운 열 생성: 고객 세그먼트 나누기 

    EDU 데이터베이스의 [MEMBER] 라는 테이블을 활용합니다. 이 테이블은 이렇게 생겼습니다.

     

    USE EDU
    
    SELECT *
    FROM [MEMBER]

     

    [MEMBER] 테이블은 회원정보 테이블이며, mem_no를 PK(Primary Key)로 가지며 이 열은 중복이 없습니다.

     

    여기서 주의깊게 볼 것은 바로 ageband인데요,

    고객의 연령대를 20대, 30대, .. 이런 식으로 나눠 놓았습니다.

    ageband를 조금 더 단순화하여 고객을 나누는 고객 세그멘테이션(segmentation)을 진행해보려 합니다.

     

    Q. ageband 값이 20~30이면 '2030', 40~50이면 '4050', 나머지는 'other'의 값을 갖는 새로운 열 [ageband_seg]를 만드시오.

    이렇게 새로운 열을 만들 때 잘 활용할 수 있는 것이 CASE WHEN 입니다. 

    CASE WHEN을 사용하게 되면 기존에 있는 열의 조건에 따른 새로운 값을 갖는 새로운 열이 만들어지게 됩니다. 위 케이스의 경우엔 밑의 그림처럼 만들어진다고 보면 됩니다.

     

    이제 CASE WHEN을 사용하여 위의 문제를 풀어봅시다.

     

    SELECT *, 
    	CASE WHEN ageband BETWEEN 20 AND 30 THEN '2030'
    	WHEN ageband BETWEEN 40 AND 50 THEN '4050'
    	ELSE 'other' END AS ageband_seg
    FROM [MEMBER]

    우리가 원하는 대로 결과가 잘 나왔음을 알 수 있습니다.

     

    ELSE를 쓰지 않는 경우

    참고로 ELSE는 꼭 쓸 필요가 없이 생략해도 상관없습니다. 

    위 경우 2030이나 4050이 아니면 전부 'other' 처리를 해주었는데, ELSE를 빼고 쓰면 전부 NULL 처리가 됩니다.

     

    -- NOT USING "ELSE" -> it becomes 'NULL'
    SELECT *, 
    	CASE WHEN ageband BETWEEN 20 AND 30 THEN '2030'
    	WHEN ageband BETWEEN 40 AND 50 THEN '4050'
    	END AS ageband_seg
    FROM [MEMBER]

     

    2) 열 집계: 연도별 회원 수 계산

    CASE WHEN의 두번째 쓰임새는 열을 집계하는 용도입니다.

    예를 들어, 위의 [MEMBER] 테이블에서 성별 및 연령대별 회원수를 카운트한다면 어떻게 할까요?

     

    SELECT gender, ageband, COUNT(mem_no) AS mem_cnt 
    FROM [MEMBER] 
    GROUP BY gender, ageband 
    ORDER BY 1

    맞습니다. 잘 아시다시피, 간단히 GROUP BY를 통해 결과를 얻어낼 수 있죠.

    하지만, "성별 및 연령대별로 카운트하되, join_date의 연도별로 나누어 회원 수를 카운트하시오." 라는 질문이라면 어떨까요? 

    여기서는 'gender(성별)', 'ageband(연령대)', 그리고 '2018년 가입자', '2019년 가입자' 이렇게 4개의 열이 만들어져야 한다면요?

    이럴 때 GROUP BY를 대신하여 쉽게 집계할 수 있는 것이 CASE WHEN 입니다.

    이런 식으로 2019년 가입자를 뜻하는 새로운 열 'join_19'는, join_date(가입일자)의 연도가 2019일 때의 mem_no의 개수를 센 값으로 들어옵니다. 

    물론 앞에 gender, ageband로 GROUP BY가 되어있다면 앞단에서 이게 먼저 그룹화되어 계산되겠죠! 

     

    SELECT gender, ageband,	
    	COUNT(CASE WHEN YEAR(join_date) = 2018 THEN mem_no END) AS join_18,
    	COUNT(CASE WHEN YEAR(join_date) = 2019 THEN mem_no END) AS join_19
    FROM [MEMBER]
    GROUP BY gender, ageband
    ORDER BY 1

    결과는 이렇게 기존의 mem_cnt로 계산된 것이, 2018년 가입자(join_18) / 2019년 가입자(join_19)로 나누어 집계됩니다! 정말 편리하죠?


    예제 2.

    1) 새로운 열 생성: 구매전환 여부 열 만들기

    이번엔 새로운 테이블 [CAR_MART]를 가져와서 CASE WHEN을 활용해봅니다.

     

    SELECT * FROM [CAR_MART]

    이 데이터는 자동차 구매에 관한 데이터이며, 주문한 내역에 고객과 상품코드, 상점코드 등으로 무엇을 구매했는지 알 수 있습니다.

     

    여기서는 구매전환율을 구해보겠습니다.

    이를 위해, 2020년에 구매한 고객이 2021년에도 구매한 경우 Y, 아니면 N을 표시하는 'retention' 열을 생성합니다.

     

    먼저 이 데이터에서 2020년에 구매한 고객(mem_no)을 밑바탕으로 깔고, 그 옆에 2021년에 구매한 고객을 LEFT JOIN시키겠습니다. 왜냐하면 2020년을 기준으로, 21년에 구매했다면 값이 있을 것이고, 21년에 구매하지 않았다면 NULL로 조인될 테니까요.

     

    SELECT *
    FROM (SELECT DISTINCT mem_no FROM [CAR_MART] WHERE YEAR(order_date) = 2020) A 
    LEFT JOIN (SELECT DISTINCT mem_no FROM [CAR_MART] WHERE YEAR(order_date) = 2021) B
    ON A.mem_no = B.mem_no

     

    위와 같은 결과가 나옵니다. (참고로 [CAR_MART] 테이블은 mem_no가 여러번 구매할 경우 중복으로 찍히기 때문에 DISTINCT를 써서 집계합니다.)

     

    결과를 좀 더 가공합니다. NULL로 나온거라면 전환이 안 된것이고(N), 값이 있으면 재구매를 한 것(Y)이겠죠?

    이를 활용해 CASE WHEN 구문을 써줍니다.

     

    SELECT A.mem_no AS mem_20, B.mem_no AS mem_21,
    		CASE WHEN B.mem_no IS NOT NULL THEN 'Y'
    		ELSE 'N' END AS retention
    INTO #RETENTION_BASE 
    FROM (SELECT DISTINCT mem_no FROM [CAR_MART] WHERE YEAR(order_date) = 2020) A 
    LEFT JOIN (SELECT DISTINCT mem_no FROM [CAR_MART] WHERE YEAR(order_date) = 2021) B
    ON A.mem_no = B.mem_no
    
    SELECT * FROM #RETENTION_BASE

     

    결과를 #RETENTION_BASE 라는 임시 세션 테이블에 저장하여 불러왔습니다.

    테이블에 저장한 이유는 뒤의 연산(구매전환율 계산)을 하기 위함입니다. 아무튼, 전환여부를 알 수 있는 retention 열이 잘 만들어졌습니다.

     

    2) 열 집계: 구매전환율 계산 

    이번에는 위의 #RETENTION_BASE를 활용해 구매전환율을 계산해봅니다.

    계산은 간단합니다. 'retention' 열의 전체 개수에서 'Y'의 개수의 비율을 구하면 그것이 구매 전환율이겠죠?

     

    먼저, 간단하게 GROUP BY를 활용하여 계산하면 이렇게 됩니다.

     

    SELECT retention, COUNT(retention) AS re_cnt
    FROM #RETENTION_BASE
    GROUP BY retention
    WITH ROLLUP 
    ORDER BY 1 DESC

    구매전환이 된 것은 117개, 전체는 1,581개군요.

    앞서 배운 그룹함수 GROUPING과 CASE WHEN을 사용하면, 'NULL' 값을 'total'로도 바꿀 수 있습니다.

     

    SELECT CASE WHEN GROUPING(retention) = 1 THEN 'total' ELSE retention END AS 현황,
    	   COUNT(retention) AS re_cnt
    FROM #RETENTION_BASE
    GROUP BY retention
    WITH ROLLUP

     

    GROUP BY를 사용하여 전환된 것과 아닌 것의 개수를 셌다면, 이번엔 CASE WHEN 만 사용해서 집계를 해봅시다.

    COUNT 집계함수를 활용해 retention이 'Y'인 것만 셉니다.

     

    -- 또는 CASE WHEN을 사용하면
    SELECT COUNT(*) AS total,
    	COUNT(CASE WHEN retention = 'Y' THEN retention END) AS re_y
    FROM #RETENTION_BASE

     

    또한 COUNT가 아니라 SUM 함수를 사용할 수도 있습니다. 

    조건에 retention이 'Y'이면 1로 두고 1을 전부 더하는 것입니다.

     

    SELECT COUNT(*) AS total,
    	SUM(CASE WHEN retention = 'Y' THEN 1 ELSE 0 END) AS re_y
    FROM #RETENTION_BASE

    결과는 같습니다.

     

     

    (번외편) SQL 상에서 전환 "비율"까지 구할 수 있을까?

    그럼요! 하지만 집계함수 COUNT를 사용하면 안되고, SUM일 때만 가능합니다. (아마 MS-SQL이라서 그런 것 같습니다.. 빅쿼리에선 잘됩니다)

    또한 COUNT(*) 를 분모에 사용할 때 형식을 FLOAT로 변경해주지 않으면 결과가 0으로 나오니 주의하세요!!

     

    -- COUNT가 안먹기 때문에 SUM으로 바꿔서 써줘야 한다
    SELECT ROUND((SUM(CASE WHEN retention = 'Y' THEN 1 ELSE 0 END)/ CAST(COUNT(*) AS FLOAT))*100, 2)
    FROM #RETENTION_BASE

    위와 같이 retention이 'Y'인 것의 비율은 약 7.4%입니다.

    예전에 포스팅한 적 있었던 CONCAT과 CAST 함수도 함께 활용하면 SQL 상에서 7.4%로 나타낼 수 있습니다.

     

    SELECT CONCAT(CAST(ROUND((SUM(CASE WHEN retention = 'Y' THEN 1 ELSE 0 END)/ CAST(COUNT(*) AS FLOAT))*100, 2) AS VARCHAR), '%')
    FROM #RETENTION_BASE

    반응형