이미지 로딩 중...

SQL CASE WHEN으로 조건 분기 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 11. 23. · 2 Views

SQL CASE WHEN으로 조건 분기 완벽 가이드

SQL의 CASE 표현식을 활용하여 데이터를 조건에 따라 분류하고 변환하는 방법을 배웁니다. 단순 CASE와 검색 CASE의 차이부터 집계 함수와의 조합, 피벗 테이블 생성까지 실무에서 바로 사용할 수 있는 핵심 기법을 다룹니다.


목차

  1. CASE 표현식 기본 문법
  2. 단순 CASE vs 검색 CASE
  3. ELSE 절 활용
  4. 다중 조건 처리
  5. 집계 함수와 CASE 조합
  6. 피벗 테이블 만들기

1. CASE 표현식 기본 문법

시작하며

여러분이 회원 데이터를 조회할 때 나이에 따라 '청소년', '성인', '시니어'로 자동으로 구분해서 보여주고 싶었던 적 있나요? 또는 주문 금액에 따라 '소액', '중액', '고액'으로 분류하고 싶었던 경험이 있나요?

이런 문제는 실제 개발 현장에서 정말 자주 발생합니다. 데이터를 그대로 보여주기보다는 비즈니스 로직에 맞게 변환해서 보여줘야 하는 경우가 많기 때문이죠.

만약 이걸 프로그래밍 언어에서 if-else로 처리한다면 데이터베이스에서 모든 데이터를 가져온 다음 하나하나 처리해야 해서 성능이 크게 떨어집니다. 바로 이럴 때 필요한 것이 CASE 표현식입니다.

SQL에서 직접 조건을 판단하여 원하는 값으로 변환해주기 때문에 데이터베이스 레벨에서 효율적으로 처리할 수 있습니다.

개요

간단히 말해서, CASE 표현식은 SQL의 if-else 문입니다. 조건에 따라 다른 값을 반환할 수 있는 강력한 도구죠.

여러분이 데이터를 조회할 때 원본 값을 그대로 보여주는 게 아니라 조건에 따라 다르게 표시하고 싶을 때 매우 유용합니다. 예를 들어, 상품의 재고 수량을 보여줄 때 0이면 '품절', 10개 미만이면 '재고부족', 그 이상이면 '재고있음'으로 표시하는 경우에 CASE를 사용하면 딱입니다.

전통적인 방법으로는 데이터를 모두 가져온 후 애플리케이션 코드에서 조건문으로 처리했다면, 이제는 SQL 쿼리 안에서 한 번에 처리할 수 있습니다. 이렇게 하면 데이터베이스와 애플리케이션 간의 통신량도 줄고 코드도 간결해집니다.

CASE 표현식의 핵심 특징은 세 가지입니다. 첫째, SELECT, WHERE, ORDER BY 등 거의 모든 곳에서 사용할 수 있습니다.

둘째, 중첩해서 사용할 수 있어 복잡한 조건도 처리 가능합니다. 셋째, NULL 처리도 깔끔하게 할 수 있습니다.

이러한 특징들이 CASE를 SQL에서 가장 많이 사용되는 표현식 중 하나로 만들어줍니다.

코드 예제

-- 회원 나이에 따라 연령대 분류하기
SELECT
    name,
    age,
    CASE
        WHEN age < 20 THEN '청소년'
        WHEN age >= 20 AND age < 65 THEN '성인'
        WHEN age >= 65 THEN '시니어'
        ELSE '미분류'
    END AS age_group
FROM users
ORDER BY age;

설명

이것이 하는 일: CASE 표현식은 여러 조건을 순서대로 검사하면서 조건이 참인 경우 해당하는 값을 반환합니다. 모든 조건이 거짓이면 ELSE 뒤의 값을 반환하죠.

첫 번째로, CASE 키워드로 시작해서 조건 검사를 시작합니다. WHEN 절에서 조건을 작성하는데, 이 예제에서는 'age < 20'처럼 비교 연산자를 사용했습니다.

조건이 참이면 THEN 뒤의 값('청소년')을 반환합니다. 왜 이렇게 하냐면, 데이터베이스에서 바로 의미 있는 문자열로 변환해서 가져올 수 있기 때문입니다.

그 다음으로, 첫 번째 WHEN 조건이 거짓이면 다음 WHEN 조건으로 넘어갑니다. 'age >= 20 AND age < 65'처럼 AND를 사용해 범위를 지정할 수도 있습니다.

이렇게 여러 WHEN 절을 연결해서 다양한 조건을 처리할 수 있습니다. 내부적으로는 위에서 아래로 순서대로 검사하면서 처음 만나는 참인 조건의 값을 반환하고 나머지는 무시합니다.

마지막으로, ELSE 절은 모든 WHEN 조건이 거짓일 때의 기본값을 지정합니다. 그리고 END 키워드로 CASE 표현식을 종료하죠.

AS age_group으로 컬럼명을 지정하면 결과를 보기 좋게 표시할 수 있습니다. 여러분이 이 코드를 사용하면 users 테이블의 각 행마다 나이를 자동으로 분류한 결과를 얻을 수 있습니다.

데이터베이스에서 이미 분류가 완료된 상태로 데이터를 받기 때문에 애플리케이션에서 추가 처리가 필요 없고, 쿼리 한 번으로 원하는 형태의 데이터를 얻을 수 있다는 큰 이점이 있습니다. 또한 이 방식은 대용량 데이터를 다룰 때 성능상 훨씬 유리합니다.

실전 팁

💡 WHEN 절의 순서가 중요합니다. 조건은 위에서 아래로 검사되므로 더 구체적인 조건을 먼저 작성하세요. 예를 들어 'age >= 20'을 먼저 쓰면 65세 이상도 여기서 걸려버립니다.

💡 ELSE 절을 생략하면 조건에 맞지 않는 경우 NULL이 반환됩니다. 예상치 못한 NULL 값을 방지하려면 항상 ELSE를 명시하는 습관을 들이세요.

💡 END 키워드를 빼먹으면 문법 오류가 발생합니다. CASE로 시작했으면 반드시 END로 끝내야 한다는 점을 기억하세요.

💡 복잡한 조건은 괄호로 명확하게 묶어주세요. (age >= 20 AND age < 30) OR (status = 'VIP') 처럼 작성하면 의도가 명확해집니다.

💡 CASE 표현식의 결과를 WHERE 절에서 재사용하고 싶다면 서브쿼리나 CTE(Common Table Expression)를 활용하세요. WHERE age_group = '성인' 같은 방식은 직접 사용할 수 없습니다.


2. 단순 CASE vs 검색 CASE

시작하며

여러분이 상품의 카테고리 코드(1, 2, 3)를 실제 카테고리명('전자제품', '의류', '식품')으로 변환하고 싶을 때 어떻게 하시나요? 또는 주문 상태 코드를 한글로 보기 좋게 바꾸고 싶을 때는요?

이런 상황에서 앞서 배운 WHEN age < 20 THEN 같은 방식은 좀 번거롭습니다. 단순히 값이 같은지만 비교하면 되는데 매번 = 연산자를 써야 하니까요.

또한 코드가 길어지면 가독성도 떨어집니다. 바로 이럴 때 사용하는 것이 단순 CASE 표현식입니다.

하나의 값과 여러 값을 비교할 때 훨씬 간결하게 작성할 수 있습니다. 검색 CASE와 단순 CASE의 차이를 이해하면 상황에 맞게 더 깔끔한 코드를 작성할 수 있습니다.

개요

간단히 말해서, 검색 CASE는 WHEN 절에서 조건식을 작성하는 방식이고, 단순 CASE는 CASE 뒤에 비교할 컬럼을 명시하고 WHEN 절에는 값만 작성하는 방식입니다. 검색 CASE는 우리가 앞서 배운 'CASE WHEN age < 20 THEN' 같은 형태입니다.

각 WHEN 절마다 완전한 조건식을 작성할 수 있어서 범위 비교, 복잡한 조건 등을 자유롭게 처리할 수 있습니다. 반면 단순 CASE는 'CASE status WHEN 'A' THEN' 처럼 하나의 값과 여러 값을 비교할 때 사용합니다.

코드 변환이나 상태 매핑에 매우 유용하죠. 전통적으로는 모든 상황에서 검색 CASE만 사용했다면, 이제는 상황에 따라 구분해서 사용할 수 있습니다.

단순 비교라면 단순 CASE로 간결하게, 복잡한 조건이라면 검색 CASE로 명확하게 작성하는 것이 좋습니다. 단순 CASE의 핵심 특징은 두 가지입니다.

첫째, 코드가 훨씬 간결해집니다. WHEN 절마다 'column_name =' 을 반복할 필요가 없으니까요.

둘째, 의도가 더 명확해집니다. "이 컬럼의 값에 따라 매핑한다"는 것이 한눈에 보이죠.

하지만 단순 비교(=)만 가능하다는 제약이 있어서 범위 조건이나 복잡한 비교에는 검색 CASE를 사용해야 합니다.

코드 예제

-- 검색 CASE: 범위 조건 사용
SELECT
    product_name,
    price,
    CASE
        WHEN price < 10000 THEN '저가'
        WHEN price >= 10000 AND price < 50000 THEN '중가'
        ELSE '고가'
    END AS price_range
FROM products;

-- 단순 CASE: 값 매핑에 사용
SELECT
    order_id,
    CASE status
        WHEN 'P' THEN '결제대기'
        WHEN 'C' THEN '결제완료'
        WHEN 'S' THEN '배송중'
        WHEN 'D' THEN '배송완료'
        ELSE '알수없음'
    END AS status_name
FROM orders;

설명

이것이 하는 일: 두 가지 CASE 표현식은 각각의 강점이 있어서 상황에 맞게 선택해서 사용할 수 있습니다. 첫 번째 예제의 검색 CASE를 보면, CASE 바로 뒤에 아무것도 없고 WHEN 절에 완전한 조건식('price < 10000')이 있습니다.

이 방식은 부등호 비교, AND/OR 조합, 함수 사용 등 모든 종류의 조건을 자유롭게 작성할 수 있습니다. 그래서 가격 범위처럼 '미만', '이상' 같은 조건을 처리할 때 적합합니다.

두 번째 예제의 단순 CASE를 보면, CASE status처럼 비교할 컬럼을 명시하고 WHEN 절에는 값만 작성('P', 'C' 등)합니다. 내부적으로는 'status = P', 'status = C' 처럼 동등 비교를 수행하는 것이죠.

이렇게 하면 같은 컬럼을 여러 번 반복해서 쓸 필요가 없어서 코드가 훨씬 깔끔합니다. 어떤 것을 선택할지는 간단합니다.

하나의 컬럼 값을 여러 개의 특정 값과 비교하는 경우(예: 상태 코드 변환, 카테고리 매핑)라면 단순 CASE를 사용하세요. 범위 비교, 여러 컬럼 조합, NULL 체크 등이 필요하다면 검색 CASE를 사용하면 됩니다.

여러분이 이 두 방식의 차이를 이해하면 쿼리를 작성할 때 더 적절한 선택을 할 수 있습니다. 단순 CASE로 충분한데 검색 CASE를 쓰면 불필요하게 길어지고, 반대로 복잡한 조건에 단순 CASE를 억지로 쓰려고 하면 쿼리가 복잡해집니다.

상황에 맞는 도구를 선택하는 것이 중요합니다. 실무에서는 두 방식을 하나의 쿼리에 함께 사용하는 경우도 많습니다.

예를 들어 상태 코드는 단순 CASE로 변환하고, 가격 범위는 검색 CASE로 분류하는 식이죠. 각각의 특성을 잘 이해하고 있으면 이런 혼용도 자연스럽게 할 수 있습니다.

실전 팁

💡 단순 CASE는 내부적으로 = 연산만 수행하므로 NULL 비교가 안 됩니다. NULL을 체크하려면 'WHEN column IS NULL THEN' 형식의 검색 CASE를 사용하세요.

💡 단순 CASE에서 여러 값을 하나의 결과로 매핑하고 싶다면 IN을 사용할 수 없습니다. 대신 검색 CASE에서 'WHEN status IN ('A', 'B') THEN' 형식을 사용하세요.

💡 성능 차이는 거의 없습니다. 데이터베이스 옵티마이저가 두 방식을 비슷하게 처리하므로 가독성을 우선으로 선택하세요.

💡 코드 리뷰 시 일관성을 유지하세요. 같은 프로젝트 내에서는 비슷한 상황에 같은 방식을 사용하는 것이 좋습니다.

💡 레거시 시스템의 코드 변환(1→'남성', 2→'여성' 등)은 별도의 코드 테이블로 관리하고 JOIN하는 것도 고려해보세요. CASE로 하드코딩하면 나중에 수정이 어려울 수 있습니다.


3. ELSE 절 활용

시작하며

여러분이 CASE 표현식을 작성하다가 ELSE 절을 빼먹은 적 있나요? 그랬더니 예상치 못한 NULL 값이 나와서 당황했던 경험이 있을 겁니다.

또는 NULL이 나와도 괜찮다고 생각했는데, 나중에 그 NULL 때문에 계산이나 정렬이 이상하게 동작한 경우도 있을 것입니다. 이런 문제는 ELSE 절의 중요성을 이해하지 못해서 발생합니다.

ELSE 절은 선택사항이지만, 실무에서는 거의 필수라고 봐야 합니다. 특히 집계 함수나 계산에 사용되는 CASE 표현식에서 ELSE를 생략하면 예기치 않은 결과가 나올 수 있습니다.

바로 이럴 때 ELSE 절을 제대로 활용하는 것이 중요합니다. ELSE 절을 통해 기본값을 지정하거나, 예외 상황을 명시적으로 처리하거나, 원본 값을 유지할 수도 있습니다.

개요

간단히 말해서, ELSE 절은 모든 WHEN 조건이 거짓일 때 반환할 기본값을 지정하는 부분입니다. 안전장치 역할을 하는 거죠.

ELSE 절을 생략하면 어떤 WHEN 조건도 만족하지 않을 때 자동으로 NULL이 반환됩니다. 이게 문제가 되는 경우가 많습니다.

예를 들어, SUM이나 AVG 같은 집계 함수에서 NULL은 계산에서 제외되어버리고, 문자열 결합에서는 전체가 NULL이 되어버립니다. 또한 NULL은 정렬 시 특별하게 처리되어 예상과 다른 순서로 나타날 수 있습니다.

전통적으로는 ELSE를 선택사항으로 생각했다면, 이제는 거의 필수로 작성하는 것이 좋은 관행입니다. 명시적으로 기본값을 지정하면 코드의 의도가 명확해지고, 예상치 못한 NULL로 인한 버그를 예방할 수 있습니다.

ELSE 절 활용의 핵심 패턴은 세 가지입니다. 첫째, 의미 있는 기본값 제공('미분류', '기타', 0 등).

둘째, 원본 값 유지(ELSE column_name). 셋째, 에러나 경고 표시(ELSE '확인필요').

이러한 패턴들을 상황에 맞게 사용하면 더 견고한 쿼리를 작성할 수 있습니다.

코드 예제

-- ELSE로 의미 있는 기본값 제공
SELECT
    product_name,
    CASE category_code
        WHEN 1 THEN '전자제품'
        WHEN 2 THEN '의류'
        WHEN 3 THEN '식품'
        ELSE '기타'  -- 예상치 못한 카테고리도 처리
    END AS category_name
FROM products;

-- ELSE로 원본 값 유지
SELECT
    name,
    CASE
        WHEN grade = 'VIP' THEN grade || ' 고객님'
        WHEN grade = 'GOLD' THEN grade || ' 고객님'
        ELSE grade  -- 일반 등급은 그대로 표시
    END AS grade_display
FROM customers;

-- ELSE 생략 시 NULL 반환 (주의!)
SELECT
    order_id,
    CASE status
        WHEN 'C' THEN '완료'
        WHEN 'P' THEN '처리중'
        -- ELSE 없음: 다른 상태는 모두 NULL
    END AS status_name
FROM orders
WHERE status_name IS NOT NULL;  -- 이 조건은 작동 안 함!

설명

이것이 하는 일: ELSE 절은 모든 예외 상황을 안전하게 처리하는 안전망 역할을 합니다. 첫 번째 예제에서는 ELSE '기타'로 명시적인 기본값을 제공합니다.

category_code가 1, 2, 3이 아닌 다른 값(예: 4, 5 또는 NULL)이 들어오면 '기타'로 표시됩니다. 이렇게 하면 데이터베이스에 예상치 못한 값이 있어도 쿼리 결과가 깔끔하게 나옵니다.

왜 이렇게 하냐면, 실무에서는 항상 예상 범위를 벗어나는 데이터가 있을 수 있기 때문입니다. 두 번째 예제에서는 ELSE grade로 원본 값을 그대로 유지합니다.

VIP나 GOLD 등급에만 특별한 처리를 하고, 나머지 등급(SILVER, BRONZE 등)은 변경하지 않고 그대로 반환하는 거죠. 이 패턴은 특정 값만 변환하고 나머지는 유지할 때 매우 유용합니다.

|| 연산자로 문자열을 결합할 때 ELSE가 없으면 NULL이 되어 전체가 NULL이 되어버립니다. 세 번째 예제는 ELSE를 생략한 안티패턴입니다.

status가 'C'나 'P'가 아니면 NULL이 반환되는데, 문제는 WHERE 절에서 status_name을 사용할 수 없다는 점입니다. CASE 표현식으로 만든 별칭은 같은 SELECT 절에서는 사용할 수 없거든요.

또한 NULL이 섞인 결과는 애플리케이션에서 처리할 때도 번거롭습니다. 여러분이 ELSE 절을 적극적으로 활용하면 여러 이점을 얻을 수 있습니다.

첫째, 코드의 의도가 명확해집니다. "이 경우들은 이렇게, 나머지는 저렇게"라는 것이 한눈에 보이죠.

둘째, NULL로 인한 예상치 못한 버그를 예방할 수 있습니다. 집계 함수, 문자열 결합, 정렬 등에서 NULL은 특별하게 동작하므로 명시적인 값을 주는 것이 안전합니다.

실무 팁으로는, ELSE 절에 원본 값이나 '확인필요' 같은 값을 넣어서 데이터 품질 문제를 찾는 용도로 활용할 수도 있습니다. 예를 들어 ELSE '미정의:' || category_code 처럼 하면 어떤 코드가 매핑되지 않았는지 바로 알 수 있죠.

실전 팁

💡 집계 함수(SUM, COUNT, AVG)와 함께 사용할 때는 반드시 ELSE 절을 명시하세요. ELSE 0이나 ELSE 1 같은 기본값이 계산 결과에 큰 영향을 미칩니다.

💡 문자열 결합이나 CONCAT 함수와 함께 사용할 때 ELSE를 빼먹으면 전체가 NULL이 됩니다. 최소한 ELSE ''(빈 문자열)은 넣어주세요.

💡 데이터 검증용으로 ELSE 'ERROR: 예상치 못한 값' 같은 명시적인 에러 메시지를 넣으면 데이터 품질 문제를 빠르게 발견할 수 있습니다.

💡 ELSE 절에서 원본 컬럼을 참조하면 (ELSE column_name) 특정 조건만 변환하고 나머지는 유지하는 패턴을 구현할 수 있습니다.

💡 코드 리뷰 시 ELSE 절이 없는 CASE를 발견하면 의도적인지 확인하세요. 대부분의 경우 실수로 빼먹은 것이므로 추가하는 것이 좋습니다.


4. 다중 조건 처리

시작하며

여러분이 고객을 세분화할 때 단순히 구매 금액만 보는 게 아니라 구매 횟수, 최근 구매일, 회원 등급 등 여러 조건을 동시에 고려해야 하는 경우가 있나요? 또는 배송비를 계산할 때 지역, 무게, 상품 종류를 모두 고려해야 하는 복잡한 상황을 마주한 적이 있나요?

이런 문제는 실제 비즈니스 로직에서 정말 흔합니다. 하나의 조건만으로는 충분하지 않고, 여러 요소를 복합적으로 판단해야 하는 경우가 많습니다.

단순한 if-else로는 코드가 복잡해지고 가독성도 떨어집니다. 바로 이럴 때 CASE 표현식에서 AND, OR, IN, BETWEEN 같은 연산자를 조합해서 다중 조건을 처리할 수 있습니다.

심지어 CASE 안에 CASE를 중첩해서 더 복잡한 로직도 구현할 수 있죠.

개요

간단히 말해서, 다중 조건 처리는 하나의 WHEN 절에 여러 조건을 AND나 OR로 결합하거나, 여러 CASE 표현식을 중첩해서 복잡한 비즈니스 로직을 구현하는 기법입니다. 실무에서는 단순한 조건보다 복잡한 조건이 훨씬 많습니다.

예를 들어, VIP 고객을 선정할 때 "총 구매금액 100만원 이상 AND 구매횟수 10회 이상 AND 최근 3개월 내 구매 있음" 같은 조건이 필요합니다. 이런 복잡한 조건도 CASE 표현식 하나로 깔끔하게 처리할 수 있습니다.

전통적으로는 이런 복잡한 로직을 애플리케이션 코드에서 처리했다면, 이제는 SQL 레벨에서 직접 처리할 수 있습니다. 데이터베이스가 데이터에 더 가깝기 때문에 성능도 좋고, 쿼리 한 번으로 원하는 결과를 얻을 수 있어 효율적입니다.

다중 조건 처리의 핵심 기법은 네 가지입니다. 첫째, AND/OR로 여러 조건 결합.

둘째, IN 연산자로 여러 값 중 하나 선택. 셋째, BETWEEN으로 범위 지정.

넷째, 중첩 CASE로 단계별 판단. 이러한 기법들을 조합하면 어떤 복잡한 로직도 표현할 수 있습니다.

코드 예제

-- AND/OR로 복합 조건 처리
SELECT
    customer_name,
    total_amount,
    order_count,
    last_order_date,
    CASE
        WHEN total_amount >= 1000000
             AND order_count >= 10
             AND last_order_date >= DATE_SUB(NOW(), INTERVAL 3 MONTH)
             THEN 'VIP'
        WHEN total_amount >= 500000
             AND order_count >= 5
             THEN 'GOLD'
        WHEN total_amount >= 100000 OR order_count >= 3
             THEN 'SILVER'
        ELSE 'BRONZE'
    END AS customer_grade
FROM customers;

-- 중첩 CASE로 단계적 판단
SELECT
    product_name,
    region,
    weight,
    CASE
        WHEN region IN ('제주', '도서지역') THEN
            CASE
                WHEN weight < 5 THEN 5000
                WHEN weight < 10 THEN 8000
                ELSE 12000
            END
        ELSE
            CASE
                WHEN weight < 5 THEN 2500
                WHEN weight < 10 THEN 4000
                ELSE 6000
            END
    END AS shipping_fee
FROM orders;

설명

이것이 하는 일: 다중 조건을 활용하면 실무의 복잡한 비즈니스 규칙을 SQL로 직접 표현할 수 있습니다. 첫 번째 예제에서는 AND 연산자로 세 가지 조건을 모두 만족해야 VIP 등급을 부여합니다.

total_amount >= 1000000(백만원 이상), order_count >= 10(10회 이상), last_order_date >= DATE_SUB(NOW(), INTERVAL 3 MONTH)(최근 3개월 내)를 모두 충족해야 하죠. 그 다음 WHEN 절은 좀 더 완화된 조건으로 GOLD를 판단합니다.

마지막 SILVER 조건에서는 OR을 사용해서 금액이나 횟수 중 하나만 만족해도 되도록 했습니다. 이렇게 AND/OR을 적절히 조합하면 복잡한 등급 체계를 구현할 수 있습니다.

두 번째 예제는 중첩 CASE를 보여줍니다. 먼저 지역이 제주나 도서지역인지 판단하고(IN 연산자 사용), 그에 따라 안쪽 CASE에서 무게별로 다른 배송비를 책정합니다.

제주/도서지역은 5kg 미만 5000원, 10kg 미만 8000원 이런 식이고, 일반 지역은 더 저렴한 요금을 적용하는 거죠. 내부적으로는 외부 CASE가 먼저 실행되어 지역을 구분하고, 그 결과에 따라 내부 CASE가 실행됩니다.

중첩 CASE를 사용할 때 주의할 점은 가독성입니다. 너무 깊게 중첩하면(3단계 이상) 코드를 이해하기 어려워지므로, 그럴 때는 CTE나 서브쿼리로 단계를 나누는 것이 좋습니다.

들여쓰기를 명확하게 해서 어느 CASE와 END가 짝인지 바로 알 수 있게 하는 것도 중요합니다. 여러분이 이런 다중 조건을 활용하면 복잡한 비즈니스 규칙도 SQL 쿼리 하나로 처리할 수 있습니다.

데이터베이스에서 직접 계산하므로 대량의 데이터를 애플리케이션으로 가져와서 처리하는 것보다 훨씬 빠릅니다. 또한 로직이 SQL에 명시되어 있어서 나중에 규칙이 어떻게 되어 있는지 확인하기도 쉽습니다.

실무에서 이런 복잡한 CASE를 작성할 때는 먼저 종이에 의사결정 트리를 그려보는 것이 좋습니다. "먼저 이걸 판단하고, 그 다음 저걸 판단하고" 같은 흐름을 시각화하면 CASE 구조를 설계하기가 훨씬 쉬워집니다.

실전 팁

💡 복잡한 AND 조건은 괄호로 명확하게 묶으세요. (A AND B) OR (C AND D) 처럼 하면 의도가 명확해지고 연산자 우선순위 실수를 방지할 수 있습니다.

💡 중첩 CASE는 2단계까지만 사용하는 것이 좋습니다. 3단계 이상 깊어지면 WITH 절(CTE)로 단계를 나누는 것이 가독성에 좋습니다.

💡 IN 연산자는 서브쿼리와도 함께 사용할 수 있습니다. WHEN customer_id IN (SELECT customer_id FROM vip_list) THEN 처럼 동적인 조건도 가능합니다.

💡 조건의 순서가 성능에 영향을 줄 수 있습니다. 더 자주 만족되는 조건을 위에 배치하면 아래 조건을 검사하지 않아서 약간 더 빠릅니다.

💡 복잡한 날짜 조건(DATE_SUB, DATEDIFF 등)은 별도의 컬럼이나 서브쿼리로 미리 계산해두면 CASE 절이 간결해집니다.


5. 집계 함수와 CASE 조합

시작하며

여러분이 월별 매출 보고서를 만들 때 "이번 달 신규 고객 수", "재구매 고객 수", "VIP 고객 구매액"을 각각 따로 쿼리해서 합친 적 있나요? 또는 상품 카테고리별 판매 통계를 내는데 각 카테고리마다 별도의 쿼리를 실행했던 경험이 있나요?

이런 방식은 비효율적입니다. 쿼리를 여러 번 실행하면 데이터베이스에 부담이 되고, 결과를 애플리케이션에서 조합하는 추가 작업도 필요합니다.

또한 각 쿼리가 실행되는 시점이 달라서 데이터 정합성 문제가 생길 수도 있습니다. 바로 이럴 때 CASE 표현식을 집계 함수와 조합하면 한 번의 쿼리로 여러 조건별 통계를 동시에 계산할 수 있습니다.

이 기법은 실무에서 정말 자주 사용되는 강력한 패턴입니다.

개요

간단히 말해서, CASE 표현식을 SUM, COUNT, AVG 같은 집계 함수 안에 넣으면 조건별로 선택적으로 집계할 수 있습니다. 조건부 집계라고 부르는 기법이죠.

이 패턴의 핵심은 CASE로 조건에 맞는 경우만 값을 반환하고, 나머지는 NULL을 반환하는 것입니다. 집계 함수는 NULL을 자동으로 무시하므로, 결과적으로 조건에 맞는 데이터만 집계되는 효과를 얻을 수 있습니다.

예를 들어, SUM(CASE WHEN status='완료' THEN amount ELSE NULL END)는 상태가 '완료'인 것들의 금액만 합산합니다. 전통적으로는 WHERE 절로 필터링한 여러 쿼리를 실행했다면, 이제는 하나의 쿼리로 모든 조건의 통계를 동시에 계산할 수 있습니다.

GROUP BY와 함께 사용하면 더욱 강력해집니다. 월별로, 카테고리별로 여러 조건의 통계를 한 번에 뽑아낼 수 있죠.

집계 함수와 CASE 조합의 핵심 패턴은 세 가지입니다. 첫째, 조건부 COUNT로 특정 조건을 만족하는 행의 개수 세기.

둘째, 조건부 SUM으로 특정 조건의 합계 구하기. 셋째, 조건부 AVG로 특정 그룹의 평균 계산하기.

이 패턴들을 마스터하면 복잡한 통계 쿼리도 간결하게 작성할 수 있습니다.

코드 예제

-- 조건별 집계를 한 번에 계산
SELECT
    DATE_FORMAT(order_date, '%Y-%m') AS order_month,
    COUNT(*) AS total_orders,
    -- 상태별 주문 수 집계
    COUNT(CASE WHEN status = 'C' THEN 1 END) AS completed_orders,
    COUNT(CASE WHEN status = 'P' THEN 1 END) AS pending_orders,
    COUNT(CASE WHEN status = 'X' THEN 1 END) AS cancelled_orders,
    -- 조건별 금액 집계
    SUM(CASE WHEN status = 'C' THEN amount ELSE 0 END) AS completed_amount,
    SUM(CASE WHEN customer_type = 'VIP' THEN amount ELSE 0 END) AS vip_amount,
    -- 조건별 평균
    AVG(CASE WHEN status = 'C' THEN amount END) AS avg_completed_amount,
    -- 신규 vs 재구매 비율
    ROUND(COUNT(CASE WHEN is_first_order = 1 THEN 1 END) * 100.0 / COUNT(*), 2) AS new_customer_rate
FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY order_month;

설명

이것이 하는 일: 집계 함수와 CASE의 조합은 한 번의 쿼리로 다양한 조건별 통계를 동시에 계산하는 강력한 기법입니다. 먼저 전체 구조를 보면, 월별로 그룹화해서(GROUP BY) 각 월마다 여러 통계를 동시에 계산합니다.

첫 번째 COUNT(*)는 전체 주문 수를 세고, 그 다음부터는 조건별 집계가 시작됩니다. COUNT(CASE WHEN status = 'C' THEN 1 END) 부분을 자세히 보겠습니다.

CASE가 status = 'C'일 때만 1을 반환하고 나머지는 NULL을 반환합니다(ELSE 생략 시 NULL). COUNT 함수는 NULL을 세지 않으므로, 결과적으로 status가 'C'인 행만 카운트됩니다.

이 패턴으로 완료, 대기, 취소 주문을 각각 셀 수 있습니다. SUM(CASE WHEN status = 'C' THEN amount ELSE 0 END) 부분은 완료된 주문의 금액만 합산합니다.

여기서는 ELSE 0을 명시했는데, SUM에서는 NULL이든 0이든 결과가 같지만 명시적으로 0을 쓰는 것이 의도가 더 명확합니다. 내부적으로는 각 행을 순회하면서 status가 'C'이면 amount를, 아니면 0을 더하는 방식으로 동작합니다.

AVG(CASE WHEN status = 'C' THEN amount END) 부분은 ELSE를 생략했습니다. 평균을 구할 때는 이렇게 하는 것이 좋습니다.

ELSE 0을 쓰면 0도 평균 계산에 포함되어 결과가 왜곡되기 때문입니다. NULL은 AVG에서 무시되므로, 완료된 주문들의 금액만 가지고 평균을 계산하게 됩니다.

마지막의 신규 고객 비율 계산은 더 고급 기법입니다. COUNT(CASE...)로 신규 고객 수를 세고, 전체 주문 수(COUNT(*))로 나눈 다음 100을 곱해서 퍼센트로 만듭니다.

ROUND로 소수점 둘째 자리까지 반올림하죠. 여러분이 이 패턴을 사용하면 엄청난 이점을 얻습니다.

첫째, 쿼리 한 번으로 모든 통계를 얻으니 성능이 좋습니다. 데이터를 한 번만 스캔하면 되니까요.

둘째, 모든 통계가 같은 시점의 데이터를 기준으로 계산되어 정합성이 보장됩니다. 셋째, 코드가 간결하고 유지보수하기 쉽습니다.

실전 팁

💡 COUNT에서는 'CASE WHEN 조건 THEN 1 END' 패턴을 사용하세요. THEN 절에 어떤 값을 넣어도 상관없지만 1이 가장 명확합니다.

💡 SUM에서는 'ELSE 0'을 명시하는 것이 좋습니다. ELSE NULL도 결과는 같지만 의도가 더 명확하게 드러납니다.

💡 AVG에서는 ELSE를 생략하거나 'ELSE NULL'을 사용하세요. ELSE 0을 쓰면 0도 평균 계산에 포함되어 결과가 왜곡됩니다.

💡 복잡한 계산은 서브쿼리나 CTE로 나누세요. 예를 들어 비율 계산에 필요한 COUNT를 먼저 CTE로 만들고, 그 결과로 비율을 계산하면 가독성이 좋습니다.

💡 대용량 데이터에서는 인덱스가 중요합니다. CASE 안의 조건 컬럼(status, customer_type 등)에 인덱스가 있으면 성능이 크게 향상됩니다.


6. 피벗 테이블 만들기

시작하며

여러분이 월별 카테고리별 매출을 보고할 때 세로로 긴 데이터를 가로로 펼쳐서 보기 좋게 만들고 싶었던 적 있나요? 엑셀의 피벗 테이블처럼 행과 열을 바꿔서 데이터를 요약하고 싶었던 경험이 있을 겁니다.

일반적인 SQL 쿼리 결과는 세로로 나열되는 형태입니다. "1월 전자제품 1000만원", "1월 의류 500만원", "2월 전자제품 1200만원" 이런 식으로 행이 많아지죠.

하지만 보고서에서는 월을 행으로, 카테고리를 열로 해서 한눈에 비교할 수 있는 표 형태를 원하는 경우가 많습니다. 바로 이럴 때 CASE 표현식과 집계 함수를 조합해서 피벗 테이블을 만들 수 있습니다.

별도의 피벗 함수 없이도 CASE 만으로 행과 열을 바꿀 수 있는 강력한 기법입니다.

개요

간단히 말해서, 피벗(Pivot)은 행 데이터를 열로 전환하는 것이고, 이를 CASE 표현식으로 구현하는 방법은 각 열이 될 값마다 조건부 집계를 하는 것입니다. 피벗의 핵심 아이디어는 이렇습니다.

"카테고리가 전자제품이면 금액을 더하고 아니면 0을 더한다"를 전자제품 열로, "카테고리가 의류면 금액을 더하고 아니면 0을 더한다"를 의류 열로 만드는 거죠. 각 카테고리별로 별도의 SUM(CASE...) 구문을 작성하면 각각이 하나의 열이 됩니다.

전통적으로는 데이터베이스의 PIVOT 함수를 사용하거나 애플리케이션에서 데이터를 재구성했다면, 이제는 표준 SQL의 CASE 표현식만으로 피벗을 구현할 수 있습니다. 이 방법은 모든 데이터베이스에서 동작하고, 더 세밀한 제어가 가능하다는 장점이 있습니다.

피벗 테이블의 핵심 단계는 세 가지입니다. 첫째, 행이 될 기준 정하기(예: 월, 지역).

둘째, 열이 될 값들을 각각 CASE로 조건부 집계하기. 셋째, GROUP BY로 행 기준별로 묶기.

이 패턴을 이해하면 어떤 형태의 피벗도 만들 수 있습니다.

코드 예제

-- 원본 데이터: 월별 카테고리별 매출이 세로로 나열됨
-- 원하는 결과: 월을 행으로, 카테고리를 열로 피벗

SELECT
    DATE_FORMAT(order_date, '%Y-%m') AS order_month,
    -- 각 카테고리를 별도의 열로 만들기
    SUM(CASE WHEN category = '전자제품' THEN amount ELSE 0 END) AS electronics,
    SUM(CASE WHEN category = '의류' THEN amount ELSE 0 END) AS clothing,
    SUM(CASE WHEN category = '식품' THEN amount ELSE 0 END) AS food,
    SUM(CASE WHEN category = '생활용품' THEN amount ELSE 0 END) AS household,
    -- 전체 합계도 함께 표시
    SUM(amount) AS total
FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY order_month;

-- 결과 예시:
-- order_month | electronics | clothing | food   | household | total
-- 2024-01     | 10000000    | 5000000  | 3000000| 2000000   | 20000000
-- 2024-02     | 12000000    | 5500000  | 3200000| 2100000   | 22800000

-- 더 복잡한 예: 지역별 연령대별 고객 수 피벗
SELECT
    region,
    COUNT(CASE WHEN age < 20 THEN 1 END) AS teen,
    COUNT(CASE WHEN age >= 20 AND age < 30 THEN 1 END) AS twenties,
    COUNT(CASE WHEN age >= 30 AND age < 40 THEN 1 END) AS thirties,
    COUNT(CASE WHEN age >= 40 AND age < 50 THEN 1 END) AS forties,
    COUNT(CASE WHEN age >= 50 THEN 1 END) AS fifties_plus,
    COUNT(*) AS total
FROM customers
GROUP BY region
ORDER BY region;

설명

이것이 하는 일: 피벗 테이블은 세로로 긴 데이터를 가로로 펼쳐서 비교하기 쉬운 형태로 만드는 강력한 보고서 기법입니다. 첫 번째 예제의 작동 원리를 단계별로 이해해봅시다.

먼저 원본 orders 테이블에는 각 주문마다 order_date, category, amount가 있습니다. 일반적인 GROUP BY를 하면 "2024-01, 전자제품, 1000만원", "2024-01, 의류, 500만원" 같은 여러 행으로 나옵니다.

하지만 우리가 원하는 것은 2024-01 월에 대한 행이 하나만 있고, 그 안에 전자제품, 의류, 식품 등의 금액이 각각의 열로 표시되는 것입니다. 이를 위해 SUM(CASE WHEN category = '전자제품' THEN amount ELSE 0 END)라는 마법을 사용합니다.

이 구문은 "카테고리가 전자제품인 행의 금액만 합산하고, 나머지는 0을 더한다"는 의미입니다. GROUP BY DATE_FORMAT(order_date, '%Y-%m')로 월별로 묶으면, 같은 월의 모든 주문이 하나의 그룹이 됩니다.

그 그룹 안에서 각각의 SUM(CASE...)이 실행되면서 전자제품은 electronics 열로, 의류는 clothing 열로 집계되는 거죠. 내부적으로는 데이터베이스가 각 그룹의 모든 행을 순회하면서 조건에 맞으면 금액을 더하고 아니면 0을 더하는 방식으로 동작합니다.

두 번째 예제는 지역별 연령대별 고객 수를 피벗한 것입니다. COUNT를 사용했기 때문에 'CASE WHEN 조건 THEN 1 END' 패턴을 사용했습니다.

각 지역마다 10대, 20대, 30대 등의 고객 수가 별도의 열로 표시되어 한눈에 지역별 연령 분포를 비교할 수 있습니다. 여러분이 이 피벗 기법을 마스터하면 보고서 작성이 훨씬 쉬워집니다.

엑셀로 가져가서 피벗하지 않아도 SQL에서 바로 원하는 형태로 만들 수 있으니까요. 또한 이 방식은 대용량 데이터에서도 효율적입니다.

데이터베이스에서 집계까지 완료해서 작은 결과만 가져오기 때문입니다. 실무 팁으로, 열이 될 값들이 동적으로 변하는 경우(예: 카테고리가 계속 추가됨)에는 동적 SQL을 생성하는 방법도 있습니다.

먼저 쿼리로 카테고리 목록을 조회하고, 그 결과를 바탕으로 CASE 구문을 자동 생성하는 스크립트를 만들 수 있죠. 하지만 대부분의 경우 열의 개수가 고정되어 있어서 직접 작성하는 것이 더 간단합니다.

또 다른 활용 예로는 요일별 시간대별 접속자 수, 상품별 월별 판매량 추이, 영업사원별 분기별 실적 등 다양한 크로스 집계를 만들 수 있습니다. 핵심은 "무엇을 행으로 하고 무엇을 열로 할 것인가"를 먼저 정하고, 열이 될 각 값마다 조건부 집계를 작성하는 것입니다.

실전 팁

💡 열이 될 값들을 미리 파악하세요. SELECT DISTINCT category FROM orders로 카테고리 목록을 먼저 확인하고 각각을 CASE로 작성합니다.

💡 NULL 대신 0을 표시하려면 COALESCE나 IFNULL을 사용하지 않고 'CASE ... ELSE 0 END' 패턴으로 충분합니다. 더 간결하고 명확합니다.

💡 너무 많은 열은 피하세요. 열이 20개 이상 되면 쿼리가 복잡해지고 결과도 보기 어렵습니다. 이럴 때는 데이터를 필터링하거나 분할하세요.

💡 총계 열(total)도 함께 제공하면 검증하기 좋습니다. 각 열의 합계가 total과 일치하는지 확인할 수 있으니까요.

💡 동적 피벗이 필요하면 프로시저나 애플리케이션에서 SQL을 생성하는 방법을 고려하세요. 하지만 대부분의 경우 고정된 열로 충분합니다.


#SQL#CASE WHEN#조건분기#데이터변환#집계함수#SQL,Database,데이터베이스

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

SQL 실전 종합 프로젝트 완벽 가이드

전자상거래 시스템을 직접 구축하면서 배우는 SQL 실전 프로젝트입니다. DB 설계부터 성능 최적화까지, 실무에서 필요한 모든 SQL 기술을 단계별로 마스터할 수 있습니다. 초급 개발자도 따라하기 쉬운 친절한 가이드로 구성되어 있습니다.

실무 데이터 분석 SQL 완벽 가이드

실제 업무에서 자주 사용하는 SQL 데이터 분석 기법을 단계별로 학습합니다. 매출 집계부터 고객 세분화까지, 실전 대시보드 쿼리 작성 방법을 배워보세요.

데이터 모델링과 정규화 완벽 가이드

데이터베이스 설계의 핵심인 데이터 모델링과 정규화를 초급 개발자 눈높이에서 쉽게 설명합니다. ERD 작성부터 제1~3정규형, 정규화의 장단점, 비정규화 전략, 실무 설계 패턴까지 실전에서 바로 활용할 수 있는 노하우를 담았습니다.

트랜잭션과 ACID 원칙 완벽 가이드

데이터베이스의 핵심 개념인 트랜잭션과 ACID 원칙을 초급 개발자도 쉽게 이해할 수 있도록 실무 예제와 함께 설명합니다. 안전한 데이터 처리를 위한 필수 지식을 친근하게 배워보세요.

인덱스와 쿼리 성능 최적화 완벽 가이드

데이터베이스 성능의 핵심인 인덱스를 처음부터 끝까지 배워봅니다. B-Tree 구조부터 실행 계획 분석까지, 실무에서 바로 사용할 수 있는 인덱스 최적화 전략을 초급자도 이해할 수 있게 설명합니다.