본문 바로가기

SQL/내용 정리

복잡한 연산을 서브쿼리(Subquery)로 해결하기

서브쿼리(Subquery)란?
  • 하나의 SQL 문 안에 포함된 또 다른 SELECT 쿼리
  • 중간 결과를 생성하여 바깥 쿼리에서 재사용하는 구조
-- 기본 구조

SELECT ...
FROM (
    SELECT ...
    FROM 테이블
) 별칭;

 

 

왜 쓰는가?
  • 집계 → 조건 분리
    • GROUP BY로 집계한 결과는 바로 WHERE/CASE에서 쓰기 어려움
    • 그래서 먼저 서브쿼리로 결과를 만든 뒤, 바깥에서 조건/분류 처리
  • 계산 단계 분리 (가독성)
  • 재사용 가능한 '임시 테이블'
    • 서브쿼리 결과는 하나의 테이블처럼 사용 가능

 

주의할 점
  • 별칭(alias) 필수 → 없으면 오류
  • 성능 이슈
    • 서브쿼리 많아지면 성능 저하 가능(특히 중첩 서브쿼리 주의)
  • 가독성 vs 성능
    • 단순 쿼리 → 굳이 필요 없음
    • 집계 후 조건 처리 → 서브쿼리 필수
-- 예시. 음식 준비시간이 25분 초과한 데이터 가져오기

SELECT
    order_id,
    restaurant_name,
    food_preparation_time
FROM 
    (
    SELECT
        order_id,
        restaurant_name,
        food_preparation_time
    FROM
        food_orders
    WHERE
        food_preparation_time > 25      -- 준비시간이 25분 초과인 데이터만 추출
    ) a;
    
    
 // -------------------
 
 
 /*
실습 1. 음식점의 평균 단가별 segmentation 을 진행하고, 그룹에 따라 수수료 연산하기
(수수료 구간 -
5000원 미만 0.05%
5000원 이상 ~ 20,000원 미만 1%
20000원 이상 ~ 30,000원 미만:2%
30000원 이상 3%)
*/

SELECT
    restaurant_name,
    price_per_plate * ratio_of_add "수수료"     -- 평균 단가 × 수수료율 = 수수료 계산
FROM 
    (
    SELECT
        restaurant_name,
        CASE
            WHEN price_per_plate < 5000 THEN 0.005              -- 5000원 미만 → 0.5%
            WHEN price_per_plate BETWEEN 5000 AND 19999 THEN 0.01 -- 5000~19999 → 1%
            WHEN price_per_plate BETWEEN 20000 AND 29999 THEN 0.02 -- 20000~29999 → 2%
            ELSE 0.03                                           -- 30000 이상 → 3%
        END ratio_of_add,
        price_per_plate
    FROM 
        (
        SELECT
            restaurant_name,
            AVG(price / quantity) price_per_plate               -- 음식점별 평균 단가 계산
        FROM
            food_orders
        GROUP BY 1                                              -- 음식점별 그룹화
        ) a
    ) b;
 
 
 // -------------------
 
 
 -- 실습 2. 음식점의 지역과 평균 배달시간으로 segmentation 하기
 
 SELECT
    restaurant_name,
    sido,
    CASE
       WHEN avg_time <= 20 THEN '<=20'                 -- 평균 배달시간 20분 이하
       WHEN avg_time > 20 AND avg_time <= 30 THEN '20<x<=30' -- 20분 초과 30분 이하
       WHEN avg_time > 30 THEN '>30'                   -- 30분 초과
   END time_segment
FROM 
    (
    SELECT
        restaurant_name,
        SUBSTRING(addr, 1, 2) sido,                    -- 주소 앞 2글자로 시/도 추출
        AVG(delivery_time) avg_time                    -- 음식점별 평균 배달시간 계산
    FROM
        food_orders
    GROUP BY 1, 2                                      -- 음식점 + 지역 기준 그룹화
    ) a;
 
 
 // -------------------
 
 
 /*
실습 3.
음식 타입별 지역별 총 주문수량과 음식점 수를 연산하고, 주문수량과 음식점수 별 수수료율을 산정하기
(음식점수 5개 이상, 주문수 30개 이상 → 수수료 0.5%
 음식점수 5개 이상, 주문수 30개 미만 → 수수료 0.8%
 음식점수 5개 미만, 주문수 30개 이상 → 수수료 1%
 음식점수 5개 미만, 주문수 30개 미만 → 수수로 2%)
*/

SELECT
    cuisine_type, 
    total_quantity,
    count_of_restautant,
    CASE
        WHEN count_of_restautant >= 5
            AND total_quantity >= 30 THEN 0.005      -- 음식점 5개 이상 + 주문수 30개 이상
        WHEN count_of_restautant >= 5
            AND total_quantity < 30 THEN 0.008       -- 음식점 5개 이상 + 주문수 30개 미만
        WHEN count_of_restautant < 5
            AND total_quantity >= 30 THEN 0.01       -- 음식점 5개 미만 + 주문수 30개 이상
        WHEN count_of_restautant < 5
            AND total_quantity < 30 THEN 0.02        -- 음식점 5개 미만 + 주문수 30개 미만
   END rate
FROM
    (
    SELECT
        cuisine_type,
        SUM(quantity) total_quantity,                 -- 음식 타입별 총 주문수량
        COUNT(DISTINCT restaurant_name) count_of_restautant -- 음식 타입별 음식점 수
    FROM
        food_orders
    GROUP BY 1                                        -- 음식 타입별 그룹화
    ) a;
 
 
 // -------------------
 
 
 /*
실습 4.
음식점의 총 주문수량과 주문 금액을 연산하고, 주문 수량을 기반으로 수수료 할인율 구하기
(할인조건 : 
 수량이 5개 이하 → 10%
 수량이 15개 초과, 총 주문금액이 300000 이상 → 0.5%
 이 외에는 일괄 1%)
 */
 
 SELECT
    cuisine_type,
    sido,
    total_quantity,
    count_of_restaurant,
    CASE
        WHEN count_of_restaurant >= 5 AND total_quantity >= 30 THEN 0.005
        WHEN count_of_restaurant >= 5 AND total_quantity < 30 THEN 0.008
        WHEN count_of_restaurant < 5 AND total_quantity >= 30 THEN 0.01
        ELSE 0.02
    END AS rate
FROM
    (
    SELECT
        cuisine_type,
        SUBSTRING(addr, 1, 2) AS sido,                       -- 지역 추출
        SUM(quantity) AS total_quantity,                     -- 음식 타입+지역별 총 주문수량
        COUNT(DISTINCT restaurant_name) AS count_of_restaurant -- 음식 타입+지역별 음식점 수
    FROM
        food_orders
    GROUP BY 1, 2
    ) a;
-- 이번 실습 핵심 패턴

SELECT
    최종출력컬럼,
    CASE ... END
FROM
    (
    SELECT
        그룹기준,
        집계값
    FROM 테이블
    GROUP BY 기준
    ) a;
    
-- 먼저 집계/가공 → 집계 결과를 기준으로 분류/계산