콘텐츠로 이동

매출 분석

사용 테이블

orders — 주문 (상태, 금액, 일시)

order_items — 주문 상세 (수량, 단가)

products — 상품 (이름, 가격, 재고, 브랜드)

categories — 카테고리 (부모-자식 계층)

customers — 고객 (등급, 포인트, 가입채널)

payments — 결제 (방법, 금액, 상태)

학습 범위

CTE, Window Functions, Multiple JOIN, Aggregation, YoY Growth, Moving Average, ABC Analysis, Cohort

1. 월별 매출 추이 (최근 3년)

2022~2024년 월별 매출, 주문 수, 평균 주문 금액을 구하세요.

힌트 1: - SUBSTR(ordered_at, 1, 7)로 연-월 추출 - SUM(total_amount), COUNT(*), AVG(total_amount)

정답
SELECT
    SUBSTR(ordered_at, 1, 7)   AS year_month,
    COUNT(*)                   AS order_count,
    ROUND(SUM(total_amount))   AS revenue,
    ROUND(AVG(total_amount))   AS avg_order_value
FROM orders
WHERE status NOT IN ('cancelled', 'returned', 'return_requested')
  AND ordered_at >= '2022-01-01'
  AND ordered_at < '2025-01-01'
GROUP BY SUBSTR(ordered_at, 1, 7)
ORDER BY year_month;

실행 결과 (총 36행 중 상위 7행)

year_month order_count revenue avg_order_value
2022-01 340 387,797,263.00 1,140,580.00
2022-02 343 349,125,148.00 1,017,858.00
2022-03 397 392,750,666.00 989,296.00
2022-04 337 313,546,744.00 930,406.00
2022-05 448 445,361,972.00 994,112.00
2022-06 348 353,057,024.00 1,014,532.00
2022-07 386 418,258,615.00 1,083,572.00

2. 카테고리별 매출 비중

2024년 대분류 카테고리별 매출과 전체 대비 비중(%)을 구하세요.

힌트 1: - categories.depth = 0이 대분류 - 소분류 → 중분류 → 대분류 경로: categories 자기 참조를 두 번 JOIN - 또는 depth=0인 최상위 카테고리를 찾는 서브쿼리 사용

정답
WITH category_revenue AS (
    SELECT
        COALESCE(top_cat.name, mid_cat.name, cat.name) AS top_category,
        SUM(oi.quantity * oi.unit_price) AS revenue
    FROM order_items AS oi
    INNER JOIN orders AS o ON oi.order_id = o.id
    INNER JOIN products AS p ON oi.product_id = p.id
    INNER JOIN categories AS cat ON p.category_id = cat.id
    LEFT JOIN categories AS mid_cat ON cat.parent_id = mid_cat.id
    LEFT JOIN categories AS top_cat ON mid_cat.parent_id = top_cat.id
    WHERE o.ordered_at >= '2024-01-01' AND o.ordered_at < '2025-01-01'
      AND o.status NOT IN ('cancelled', 'returned', 'return_requested')
    GROUP BY COALESCE(top_cat.name, mid_cat.name, cat.name)
)
SELECT
    top_category,
    ROUND(revenue) AS revenue,
    ROUND(100.0 * revenue / SUM(revenue) OVER (), 1) AS revenue_pct
FROM category_revenue
ORDER BY revenue DESC;

실행 결과 (총 18행 중 상위 7행)

top_category revenue revenue_pct
노트북 1,395,635,900.00 27.00
모니터 727,065,300.00 14.10
그래픽카드 713,579,800.00 13.80
메인보드 398,988,900.00 7.70
스피커/헤드셋 232,144,800.00 4.50
저장장치 205,861,200.00 4.00
메모리(RAM) 200,423,600.00 3.90

3. 상위 20명 고객 매출 순위

전체 기간에서 총 구매 금액 상위 20명의 고객 정보를 표시하세요. 고객명, 등급, 주문 횟수, 총 구매 금액, 순위를 포함합니다.

힌트 1: - RANK() 또는 ROW_NUMBER() 윈도우 함수 사용 - customers + orders JOIN

정답
SELECT
    RANK() OVER (ORDER BY SUM(o.total_amount) DESC) AS ranking,
    c.name          AS customer_name,
    c.grade,
    COUNT(*)        AS order_count,
    ROUND(SUM(o.total_amount)) AS total_spent
FROM customers AS c
INNER JOIN orders AS o ON c.id = o.customer_id
WHERE o.status NOT IN ('cancelled', 'returned', 'return_requested')
GROUP BY c.id, c.name, c.grade
ORDER BY total_spent DESC
LIMIT 20;

실행 결과 (총 20행 중 상위 7행)

ranking customer_name grade order_count total_spent
1 박정수 VIP 303 403,448,758.00
2 김병철 VIP 342 366,385,931.00
3 강명자 VIP 249 253,180,338.00
4 정유진 VIP 223 244,604,910.00
5 이미정 VIP 219 235,775,349.00
6 김성민 VIP 230 234,708,853.00
7 이영자 VIP 275 230,165,991.00

4. 요일별 매출 패턴

전체 주문 데이터에서 요일별(월~일) 평균 주문 수와 평균 매출을 구하세요. 어떤 요일에 매출이 가장 높은지 확인합니다.

힌트 1: - SQLite: strftime('%w', ordered_at) → 0(일)~6(토) - CASE문으로 요일명 변환 - 먼저 일별 매출을 구한 뒤, 요일별로 평균

정답
WITH daily_stats AS (
    SELECT
        DATE(ordered_at) AS order_date,
        CAST(strftime('%w', ordered_at) AS INTEGER) AS dow,
        COUNT(*)               AS order_count,
        SUM(total_amount)      AS revenue
    FROM orders
    WHERE status NOT IN ('cancelled', 'returned', 'return_requested')
    GROUP BY DATE(ordered_at)
)
SELECT
    CASE dow
        WHEN 0 THEN '일요일'
        WHEN 1 THEN '월요일'
        WHEN 2 THEN '화요일'
        WHEN 3 THEN '수요일'
        WHEN 4 THEN '목요일'
        WHEN 5 THEN '금요일'
        WHEN 6 THEN '토요일'
    END AS day_of_week,
    ROUND(AVG(order_count)) AS avg_daily_orders,
    ROUND(AVG(revenue))     AS avg_daily_revenue
FROM daily_stats
GROUP BY dow
ORDER BY dow;

실행 결과 (7행)

day_of_week avg_daily_orders avg_daily_revenue
일요일 11.00 10,702,305.00
월요일 11.00 10,470,017.00
화요일 9.00 9,434,724.00
수요일 9.00 8,818,457.00
목요일 9.00 8,818,498.00
금요일 9.00 9,178,156.00
토요일 11.00 10,550,779.00

5. 분기별 매출과 전분기 대비 성장률

2022~2024년 분기별 매출과 전분기 대비 성장률(%)을 구하세요.

힌트 1: - 분기: (CAST(SUBSTR(ordered_at,6,2) AS INTEGER) + 2) / 3 - LAG(revenue, 1) 윈도우 함수로 전분기 매출 참조 - 성장률 = (당분기 - 전분기) / 전분기 * 100

정답
WITH quarterly AS (
    SELECT
        SUBSTR(ordered_at, 1, 4) AS year,
        'Q' || ((CAST(SUBSTR(ordered_at, 6, 2) AS INTEGER) + 2) / 3) AS quarter,
        SUBSTR(ordered_at, 1, 4) || '-Q' || ((CAST(SUBSTR(ordered_at, 6, 2) AS INTEGER) + 2) / 3) AS yq,
        ROUND(SUM(total_amount)) AS revenue,
        COUNT(*) AS order_count
    FROM orders
    WHERE status NOT IN ('cancelled', 'returned', 'return_requested')
      AND ordered_at >= '2022-01-01' AND ordered_at < '2025-01-01'
    GROUP BY SUBSTR(ordered_at, 1, 4),
             (CAST(SUBSTR(ordered_at, 6, 2) AS INTEGER) + 2) / 3
)
SELECT
    yq,
    revenue,
    order_count,
    LAG(revenue, 1) OVER (ORDER BY yq) AS prev_quarter_revenue,
    ROUND(100.0 * (revenue - LAG(revenue, 1) OVER (ORDER BY yq))
        / LAG(revenue, 1) OVER (ORDER BY yq), 1) AS qoq_growth_pct
FROM quarterly
ORDER BY yq;

실행 결과 (총 12행 중 상위 7행)

yq revenue order_count prev_quarter_revenue qoq_growth_pct
2022-Q1 1,129,673,077.00 1080 NULL NULL
2022-Q2 1,111,965,740.00 1133 1,129,673,077.00 -1.60
2022-Q3 1,312,284,718.00 1246 1,111,965,740.00 18.00
2022-Q4 1,271,192,508.00 1359 1,312,284,718.00 -3.10
2023-Q1 1,075,250,589.00 1083 1,271,192,508.00 -15.40
2023-Q2 1,026,296,754.00 1102 1,075,250,589.00 -4.60
2023-Q3 1,127,278,823.00 1094 1,026,296,754.00 9.80

6. 결제 수단별 매출 비중 추이

2024년 월별로 각 결제 수단(card, bank_transfer, kakao_pay 등)의 매출 비중(%)을 구하세요.

힌트 1: - payments.method로 결제 수단 구분 - 윈도우 함수 SUM(revenue) OVER (PARTITION BY year_month)으로 월 전체 매출 - 비중 = 결제 수단별 매출 / 월 전체 매출 * 100

정답
WITH monthly_method AS (
    SELECT
        SUBSTR(o.ordered_at, 1, 7) AS year_month,
        pm.method,
        ROUND(SUM(pm.amount)) AS revenue
    FROM payments AS pm
    INNER JOIN orders AS o ON pm.order_id = o.id
    WHERE o.ordered_at >= '2024-01-01' AND o.ordered_at < '2025-01-01'
      AND o.status NOT IN ('cancelled', 'returned', 'return_requested')
      AND pm.status = 'paid'
    GROUP BY SUBSTR(o.ordered_at, 1, 7), pm.method
)
SELECT
    year_month,
    method,
    revenue,
    ROUND(100.0 * revenue / SUM(revenue) OVER (PARTITION BY year_month), 1) AS method_pct
FROM monthly_method
ORDER BY year_month, revenue DESC;

7. 카테고리별 상위 3개 상품 (Top-N per Group)

2024년 각 대분류 카테고리에서 매출 상위 3개 상품을 선발하세요.

힌트 1: - CTE에서 카테고리별 상품 매출 집계 - ROW_NUMBER() OVER (PARTITION BY category ORDER BY revenue DESC) 순위 - 외부 쿼리에서 WHERE rn <= 3 필터

정답
WITH product_sales AS (
    SELECT
        COALESCE(top_cat.name, mid_cat.name, cat.name) AS top_category,
        p.name AS product_name,
        SUM(oi.quantity)                        AS units_sold,
        ROUND(SUM(oi.quantity * oi.unit_price)) AS revenue
    FROM order_items AS oi
    INNER JOIN orders AS o ON oi.order_id = o.id
    INNER JOIN products AS p ON oi.product_id = p.id
    INNER JOIN categories AS cat ON p.category_id = cat.id
    LEFT JOIN categories AS mid_cat ON cat.parent_id = mid_cat.id
    LEFT JOIN categories AS top_cat ON mid_cat.parent_id = top_cat.id
    WHERE o.ordered_at >= '2024-01-01' AND o.ordered_at < '2025-01-01'
      AND o.status NOT IN ('cancelled', 'returned', 'return_requested')
    GROUP BY COALESCE(top_cat.name, mid_cat.name, cat.name), p.name
),
ranked AS (
    SELECT *,
        ROW_NUMBER() OVER (PARTITION BY top_category ORDER BY revenue DESC) AS rn
    FROM product_sales
)
SELECT top_category, rn AS rank, product_name, units_sold, revenue
FROM ranked
WHERE rn <= 3
ORDER BY top_category, rn;

실행 결과 (총 53행 중 상위 7행)

top_category rank product_name units_sold revenue
CPU 1 AMD Ryzen 9 9900X 239 80,232,300.00
CPU 2 Intel Core Ultra 7 265K 화이트 386 65,697,200.00
UPS/전원 1 CyberPower OR1500LCDRT2U 블랙 46 11,633,400.00
UPS/전원 2 APC Back-UPS Pro Gaming BGM1500B 블랙 21 10,842,300.00
UPS/전원 3 CyberPower BRG1500AVRLCD 실버 13 6,605,300.00
그래픽카드 1 ASUS Dual RTX 4060 Ti 블랙 40 106,992,000.00
그래픽카드 2 ASUS Dual RTX 5070 Ti 실버 106 104,558,400.00

8. 전년 동월 대비(YoY) 매출 성장률

2023~2024년 각 월의 매출과 전년 동월 대비 성장률(%)을 구하세요.

힌트 1: - LAG(revenue, 12) — 12개월 전 매출 참조 - 또는 CTE에서 연도+월 분리 후, 같은 월의 전년도를 SELF JOIN

정답
WITH monthly AS (
    SELECT
        SUBSTR(ordered_at, 1, 4) AS year,
        SUBSTR(ordered_at, 6, 2) AS month,
        SUBSTR(ordered_at, 1, 7) AS year_month,
        ROUND(SUM(total_amount)) AS revenue
    FROM orders
    WHERE status NOT IN ('cancelled', 'returned', 'return_requested')
      AND ordered_at >= '2022-01-01' AND ordered_at < '2025-01-01'
    GROUP BY SUBSTR(ordered_at, 1, 7)
)
SELECT
    cur.year_month,
    cur.revenue                AS current_revenue,
    prev.revenue               AS prev_year_revenue,
    ROUND(100.0 * (cur.revenue - prev.revenue) / prev.revenue, 1) AS yoy_growth_pct
FROM monthly AS cur
INNER JOIN monthly AS prev
    ON cur.month = prev.month
    AND CAST(cur.year AS INTEGER) = CAST(prev.year AS INTEGER) + 1
WHERE cur.year IN ('2023', '2024')
ORDER BY cur.year_month;

실행 결과 (총 24행 중 상위 7행)

year_month current_revenue prev_year_revenue yoy_growth_pct
2023-01 270,083,587.00 387,797,263.00 -30.40
2023-02 327,431,648.00 349,125,148.00 -6.20
2023-03 477,735,354.00 392,750,666.00 21.60
2023-04 396,849,049.00 313,546,744.00 26.60
2023-05 349,749,072.00 445,361,972.00 -21.50
2023-06 279,698,633.00 353,057,024.00 -20.80
2023-07 312,983,148.00 418,258,615.00 -25.20

9. 이동 평균(Moving Average) — 3개월 이동 평균 매출

월별 매출의 3개월 이동 평균을 구하세요. 이동 평균은 추세를 파악할 때 계절적 변동을 완화해줍니다.

힌트 1: - AVG(revenue) OVER (ORDER BY year_month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) - 처음 2개월은 데이터가 부족하므로 이동 평균이 정확하지 않을 수 있음

정답
WITH monthly AS (
    SELECT
        SUBSTR(ordered_at, 1, 7) AS year_month,
        ROUND(SUM(total_amount)) AS revenue
    FROM orders
    WHERE status NOT IN ('cancelled', 'returned', 'return_requested')
      AND ordered_at >= '2023-01-01' AND ordered_at < '2025-01-01'
    GROUP BY SUBSTR(ordered_at, 1, 7)
)
SELECT
    year_month,
    revenue,
    ROUND(AVG(revenue) OVER (
        ORDER BY year_month
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    )) AS moving_avg_3m,
    ROUND(AVG(revenue) OVER (
        ORDER BY year_month
        ROWS BETWEEN 5 PRECEDING AND CURRENT ROW
    )) AS moving_avg_6m
FROM monthly
ORDER BY year_month;

실행 결과 (총 24행 중 상위 7행)

year_month revenue moving_avg_3m moving_avg_6m
2023-01 270,083,587.00 270,083,587.00 270,083,587.00
2023-02 327,431,648.00 298,757,618.00 298,757,618.00
2023-03 477,735,354.00 358,416,863.00 358,416,863.00
2023-04 396,849,049.00 400,672,017.00 368,024,910.00
2023-05 349,749,072.00 408,111,158.00 364,369,742.00
2023-06 279,698,633.00 342,098,918.00 350,257,891.00
2023-07 312,983,148.00 314,143,618.00 357,407,817.00

10. ABC 분석 — 상품별 매출 누적 비율

2024년 상품별 매출을 내림차순으로 정렬하고, 누적 매출 비율로 A/B/C 등급을 부여하세요. (A: 상위 70%, B: 70~90%, C: 나머지)

힌트 1: - 누적 비율: SUM(revenue) OVER (ORDER BY revenue DESC) / SUM(revenue) OVER () - CASE문으로 A/B/C 등급 분류 - 파레토 법칙(80:20)의 변형

정답
WITH product_revenue AS (
    SELECT
        p.id,
        p.name AS product_name,
        ROUND(SUM(oi.quantity * oi.unit_price)) AS revenue
    FROM order_items AS oi
    INNER JOIN orders AS o ON oi.order_id = o.id
    INNER JOIN products AS p ON oi.product_id = p.id
    WHERE o.ordered_at >= '2024-01-01' AND o.ordered_at < '2025-01-01'
      AND o.status NOT IN ('cancelled', 'returned', 'return_requested')
    GROUP BY p.id, p.name
),
cumulative AS (
    SELECT
        product_name,
        revenue,
        SUM(revenue) OVER (ORDER BY revenue DESC) AS cum_revenue,
        SUM(revenue) OVER () AS total_revenue
    FROM product_revenue
)
SELECT
    product_name,
    revenue,
    ROUND(100.0 * cum_revenue / total_revenue, 1) AS cum_pct,
    CASE
        WHEN 100.0 * cum_revenue / total_revenue <= 70 THEN 'A'
        WHEN 100.0 * cum_revenue / total_revenue <= 90 THEN 'B'
        ELSE 'C'
    END AS abc_class
FROM cumulative
ORDER BY revenue DESC
LIMIT 30;

실행 결과 (총 30행 중 상위 7행)

product_name revenue cum_pct abc_class
Razer Blade 18 블랙 165,417,800.00 3.20 A
Razer Blade 16 실버 137,007,300.00 5.90 A
MacBook Air 15 M3 실버 126,065,300.00 8.30 A
ASUS Dual RTX 4060 Ti 블랙 106,992,000.00 10.40 A
ASUS Dual RTX 5070 Ti 실버 104,558,400.00 12.40 A
ASUS ROG Swift PG32UCDM 실버 90,734,400.00 14.20 A
ASUS ROG Strix Scar 16 85,837,500.00 15.80 A

11. 신규 고객 vs 재구매 고객 매출 비교

2024년 월별로 신규 고객(해당 월에 첫 주문)과 재구매 고객의 주문 수, 매출을 분리하세요.

힌트 1: - 각 고객의 첫 주문 월: MIN(ordered_at) 으로 구함 - 주문 월 = 첫 주문 월이면 "신규", 아니면 "재구매" - CTE로 단계적으로 처리

정답
WITH first_order AS (
    SELECT
        customer_id,
        SUBSTR(MIN(ordered_at), 1, 7) AS first_month
    FROM orders
    WHERE status NOT IN ('cancelled', 'returned', 'return_requested')
    GROUP BY customer_id
),
classified AS (
    SELECT
        SUBSTR(o.ordered_at, 1, 7) AS year_month,
        CASE
            WHEN SUBSTR(o.ordered_at, 1, 7) = fo.first_month THEN '신규'
            ELSE '재구매'
        END AS customer_type,
        o.total_amount
    FROM orders AS o
    INNER JOIN first_order AS fo ON o.customer_id = fo.customer_id
    WHERE o.ordered_at >= '2024-01-01' AND o.ordered_at < '2025-01-01'
      AND o.status NOT IN ('cancelled', 'returned', 'return_requested')
)
SELECT
    year_month,
    customer_type,
    COUNT(*)                  AS order_count,
    ROUND(SUM(total_amount)) AS revenue
FROM classified
GROUP BY year_month, customer_type
ORDER BY year_month, customer_type;

실행 결과 (총 24행 중 상위 7행)

year_month customer_type order_count revenue
2024-01 신규 32 31,865,130.00
2024-01 재구매 282 257,043,190.00
2024-02 신규 30 8,770,172.00
2024-02 재구매 386 394,357,577.00
2024-03 신규 50 28,455,371.00
2024-03 재구매 505 491,389,131.00
2024-04 신규 34 25,112,310.00

12. 고객 등급별 평균 객단가 추이

2024년 월별로 고객 등급(BRONZE/SILVER/GOLD/VIP)별 평균 주문 금액을 구하세요.

힌트 1: - customers.grade로 등급 구분 - AVG(total_amount) 그룹별 집계 - 월 + 등급 두 차원으로 GROUP BY

정답
SELECT
    SUBSTR(o.ordered_at, 1, 7) AS year_month,
    c.grade,
    COUNT(*)                   AS order_count,
    ROUND(AVG(o.total_amount)) AS avg_order_value
FROM orders AS o
INNER JOIN customers AS c ON o.customer_id = c.id
WHERE o.ordered_at >= '2024-01-01' AND o.ordered_at < '2025-01-01'
  AND o.status NOT IN ('cancelled', 'returned', 'return_requested')
GROUP BY SUBSTR(o.ordered_at, 1, 7), c.grade
ORDER BY year_month, 
    CASE c.grade
        WHEN 'VIP' THEN 1
        WHEN 'GOLD' THEN 2
        WHEN 'SILVER' THEN 3
        WHEN 'BRONZE' THEN 4
    END;

실행 결과 (총 48행 중 상위 7행)

year_month grade order_count avg_order_value
2024-01 VIP 124 834,949.00
2024-01 GOLD 73 1,202,930.00
2024-01 SILVER 56 904,820.00
2024-01 BRONZE 61 768,702.00
2024-02 VIP 178 926,721.00
2024-02 GOLD 97 926,946.00
2024-02 SILVER 40 974,420.00

13. 배송사별 배송 소요일 분석

2024년 배송사(carrier)별 평균 배송 소요일, 최소/최대 소요일, 배송 건수를 구하세요. 배송 완료(delivered)된 건만 대상으로 합니다.

힌트 1: - 배송 소요일: JULIANDAY(delivered_at) - JULIANDAY(shipped_at) - shipping 테이블의 status = 'delivered' - shipped_atdelivered_at이 모두 NOT NULL인 건만

정답
SELECT
    s.carrier,
    COUNT(*)                                                        AS delivery_count,
    ROUND(AVG(JULIANDAY(s.delivered_at) - JULIANDAY(s.shipped_at)), 1) AS avg_days,
    MIN(ROUND(JULIANDAY(s.delivered_at) - JULIANDAY(s.shipped_at), 1)) AS min_days,
    MAX(ROUND(JULIANDAY(s.delivered_at) - JULIANDAY(s.shipped_at), 1)) AS max_days,
    ROUND(100.0 * SUM(CASE
        WHEN JULIANDAY(s.delivered_at) - JULIANDAY(s.shipped_at) <= 2 THEN 1
        ELSE 0
    END) / COUNT(*), 1) AS within_2days_pct
FROM shipping AS s
INNER JOIN orders AS o ON s.order_id = o.id
WHERE s.status = 'delivered'
  AND s.shipped_at IS NOT NULL
  AND s.delivered_at IS NOT NULL
  AND o.ordered_at >= '2024-01-01' AND o.ordered_at < '2025-01-01'
GROUP BY s.carrier
ORDER BY avg_days;

실행 결과 (4행)

carrier delivery_count avg_days min_days max_days within_2days_pct
CJ대한통운 2103 2.50 1.00 4.00 49.50
로젠택배 1061 2.50 1.00 4.00 48.80
우체국택배 824 2.50 1.00 4.00 50.70
한진택배 1332 2.50 1.00 4.00 49.80

14. 할인율 구간별 매출 영향

2024년 주문의 할인율(discount_amount / (total_amount + discount_amount))을 구간별로 나누고, 각 구간의 주문 수, 평균 주문 금액, 총 매출을 분석하세요.

힌트 1: - 할인율 = discount_amount / (total_amount + discount_amount) * 100 - CASE문으로 0%, 1~5%, 6~10%, 11~20%, 20%+ 구간 분류 - discount_amount = 0이면 할인 없음

정답
WITH order_discount AS (
    SELECT
        id,
        total_amount,
        discount_amount,
        CASE
            WHEN discount_amount = 0 THEN 0
            ELSE ROUND(100.0 * discount_amount / (total_amount + discount_amount), 1)
        END AS discount_pct
    FROM orders
    WHERE ordered_at >= '2024-01-01' AND ordered_at < '2025-01-01'
      AND status NOT IN ('cancelled', 'returned', 'return_requested')
)
SELECT
    CASE
        WHEN discount_pct = 0    THEN '할인 없음'
        WHEN discount_pct <= 5   THEN '1~5%'
        WHEN discount_pct <= 10  THEN '6~10%'
        WHEN discount_pct <= 20  THEN '11~20%'
        ELSE '20% 초과'
    END AS discount_range,
    COUNT(*)                    AS order_count,
    ROUND(AVG(total_amount))    AS avg_order_value,
    ROUND(SUM(total_amount))    AS total_revenue
FROM order_discount
GROUP BY CASE
    WHEN discount_pct = 0    THEN '할인 없음'
    WHEN discount_pct <= 5   THEN '1~5%'
    WHEN discount_pct <= 10  THEN '6~10%'
    WHEN discount_pct <= 20  THEN '11~20%'
    ELSE '20% 초과'
END
ORDER BY
    CASE
        WHEN discount_pct = 0    THEN 1
        WHEN discount_pct <= 5   THEN 2
        WHEN discount_pct <= 10  THEN 3
        WHEN discount_pct <= 20  THEN 4
        ELSE 5
    END;

실행 결과 (4행)

discount_range order_count avg_order_value total_revenue
할인 없음 4152 815,975.00 3,387,926,737.00
1~5% 811 1,783,303.00 1,446,258,687.00
6~10% 228 848,082.00 193,362,738.00
11~20% 129 673,607.00 86,895,358.00

15. 프로모션 ROI 분석

각 프로모션의 투입 할인 금액 대비 매출 효과(ROI)를 분석하세요. 프로모션 기간 중 프로모션 대상 상품의 매출과 할인 금액을 집계합니다.

힌트 1: - promotions + promotion_products로 대상 상품 파악 - 프로모션 기간: started_at ~ ended_at - order_items에서 해당 기간 + 해당 상품의 매출 집계 - ROI = (매출 - 할인 총액) / 할인 총액 * 100

정답
WITH promo_sales AS (
    SELECT
        pr.id           AS promo_id,
        pr.name         AS promo_name,
        pr.type         AS promo_type,
        pr.discount_type,
        pr.discount_value,
        COUNT(DISTINCT o.id) AS order_count,
        ROUND(SUM(oi.quantity * oi.unit_price)) AS gross_revenue,
        ROUND(SUM(oi.discount_amount))          AS total_discount
    FROM promotions AS pr
    INNER JOIN promotion_products AS pp ON pr.id = pp.promotion_id
    INNER JOIN order_items AS oi ON pp.product_id = oi.product_id
    INNER JOIN orders AS o ON oi.order_id = o.id
    WHERE o.ordered_at >= pr.started_at
      AND o.ordered_at <= pr.ended_at
      AND o.status NOT IN ('cancelled', 'returned', 'return_requested')
    GROUP BY pr.id, pr.name, pr.type, pr.discount_type, pr.discount_value
)
SELECT
    promo_name,
    promo_type,
    discount_type || ' ' || discount_value AS discount_info,
    order_count,
    gross_revenue,
    total_discount,
    gross_revenue - total_discount AS net_revenue,
    CASE
        WHEN total_discount > 0
        THEN ROUND(100.0 * (gross_revenue - total_discount) / total_discount, 1)
        ELSE NULL
    END AS roi_pct
FROM promo_sales
ORDER BY roi_pct DESC;

실행 결과 (총 105행 중 상위 7행)

promo_name promo_type discount_info order_count gross_revenue total_discount net_revenue roi_pct
게이밍 기어 페스타 2023 category percent 18.0 25 16,859,400.00 15,600.00 16,843,800.00 107,973.10
블랙프라이데이 2022 seasonal percent 25.0 25 17,059,700.00 18,500.00 17,041,200.00 92,114.60
게이밍 기어 페스타 2024 category percent 18.0 30 18,118,000.00 27,600.00 18,090,400.00 65,544.90
게이밍 기어 페스타 2020 category percent 18.0 33 12,592,900.00 19,500.00 12,573,400.00 64,479.00
블랙프라이데이 2020 seasonal percent 25.0 35 18,172,900.00 31,300.00 18,141,600.00 57,960.40
게이밍 기어 페스타 2016 category percent 18.0 5 5,221,100.00 11,300.00 5,209,800.00 46,104.40
새해 특가 세일 2021 seasonal percent 10.0 35 33,039,800.00 76,200.00 32,963,600.00 43,259.30

16. 장바구니 → 구매 전환율

장바구니에 담긴 상품 중 실제 구매로 전환된 비율을 카테고리별로 구하세요.

힌트 1: - cart_items에 담긴 상품 수 vs 같은 고객이 실제 주문한 같은 상품 수 - carts + cart_items + order_items + orders JOIN - 전환율 = 구매된 장바구니 항목 수 / 전체 장바구니 항목 수 * 100

정답
WITH cart_products AS (
    SELECT
        c.customer_id,
        ci.product_id,
        cat.name AS category
    FROM carts AS c
    INNER JOIN cart_items AS ci ON c.id = ci.cart_id
    INNER JOIN products AS p ON ci.product_id = p.id
    INNER JOIN categories AS cat ON p.category_id = cat.id
),
purchased AS (
    SELECT DISTINCT
        o.customer_id,
        oi.product_id
    FROM orders AS o
    INNER JOIN order_items AS oi ON o.id = oi.order_id
    WHERE o.status NOT IN ('cancelled', 'returned', 'return_requested')
)
SELECT
    cp.category,
    COUNT(*) AS cart_items_total,
    SUM(CASE WHEN pur.product_id IS NOT NULL THEN 1 ELSE 0 END) AS converted,
    ROUND(100.0 * SUM(CASE WHEN pur.product_id IS NOT NULL THEN 1 ELSE 0 END)
        / COUNT(*), 1) AS conversion_rate_pct
FROM cart_products AS cp
LEFT JOIN purchased AS pur
    ON cp.customer_id = pur.customer_id
    AND cp.product_id = pur.product_id
GROUP BY cp.category
ORDER BY conversion_rate_pct DESC;

실행 결과 (총 38행 중 상위 7행)

category cart_items_total converted conversion_rate_pct
게이밍 332 49 14.80
케이스 325 37 11.40
SSD 201 22 10.90
DDR4 197 21 10.70
Intel 158 14 8.90
무선 205 18 8.80
스피커/헤드셋 398 34 8.50

17. 동시 구매 패턴(장바구니 분석)

같은 주문에서 함께 구매된 상품 쌍(pair)을 찾으세요. 동시 구매 빈도가 5회 이상인 상품 쌍만 표시합니다.

힌트 1: - order_items를 자기 조인(Self JOIN)하여 같은 주문의 서로 다른 상품 쌍 생성 - 중복 제거: oi1.product_id < oi2.product_id - GROUP BY 상품 쌍으로 동시 구매 횟수 집계

정답
SELECT
    p1.name AS product_a,
    p2.name AS product_b,
    COUNT(*) AS co_purchase_count
FROM order_items AS oi1
INNER JOIN order_items AS oi2
    ON oi1.order_id = oi2.order_id
    AND oi1.product_id < oi2.product_id
INNER JOIN products AS p1 ON oi1.product_id = p1.id
INNER JOIN products AS p2 ON oi2.product_id = p2.id
INNER JOIN orders AS o ON oi1.order_id = o.id
WHERE o.status NOT IN ('cancelled', 'returned', 'return_requested')
GROUP BY oi1.product_id, oi2.product_id, p1.name, p2.name
HAVING COUNT(*) >= 5
ORDER BY co_purchase_count DESC
LIMIT 20;

실행 결과 (총 20행 중 상위 7행)

product_a product_b co_purchase_count
AMD Ryzen 9 9900X Crucial T700 2TB 실버 430
AMD Ryzen 9 9900X SK하이닉스 Platinum P41 2TB 실버 329
be quiet! Light Base 900 Crucial T700 2TB 실버 294
Intel Core Ultra 5 245KF Crucial T700 2TB 실버 282
be quiet! Light Base 900 AMD Ryzen 9 9900X 249
시소닉 VERTEX GX-1200 블랙 Crucial T700 2TB 실버 221
삼성 DDR5 32GB PC5-38400 Crucial T700 2TB 실버 217

18. 리뷰 평점과 매출 상관관계

상품별 평균 리뷰 평점과 매출의 관계를 분석하세요. 평점 구간(1~2, 2~3, 3~4, 4~5)별 평균 매출을 구합니다.

힌트 1: - 상품별 평균 평점과 매출을 먼저 구한 뒤 - CASE문으로 평점 구간 분류 - 리뷰가 없는 상품은 제외

정답
WITH product_metrics AS (
    SELECT
        p.id,
        p.name,
        AVG(r.rating)                          AS avg_rating,
        COUNT(DISTINCT r.id)                    AS review_count,
        ROUND(SUM(oi.quantity * oi.unit_price)) AS revenue
    FROM products AS p
    INNER JOIN reviews AS r ON p.id = r.product_id
    INNER JOIN order_items AS oi ON p.id = oi.product_id
    INNER JOIN orders AS o ON oi.order_id = o.id
    WHERE o.status NOT IN ('cancelled', 'returned', 'return_requested')
    GROUP BY p.id, p.name
    HAVING COUNT(DISTINCT r.id) >= 3
)
SELECT
    CASE
        WHEN avg_rating < 2 THEN '1.0~1.9'
        WHEN avg_rating < 3 THEN '2.0~2.9'
        WHEN avg_rating < 4 THEN '3.0~3.9'
        ELSE '4.0~5.0'
    END AS rating_range,
    COUNT(*)                    AS product_count,
    ROUND(AVG(revenue))         AS avg_revenue,
    ROUND(AVG(review_count))    AS avg_reviews,
    ROUND(AVG(avg_rating), 2)   AS avg_rating_in_range
FROM product_metrics
GROUP BY CASE
    WHEN avg_rating < 2 THEN '1.0~1.9'
    WHEN avg_rating < 3 THEN '2.0~2.9'
    WHEN avg_rating < 4 THEN '3.0~3.9'
    ELSE '4.0~5.0'
END
ORDER BY rating_range;

실행 결과 (2행)

rating_range product_count avg_revenue avg_reviews avg_rating_in_range
3.0~3.9 165 3,987,852,901.00 33.00 3.73
4.0~5.0 99 4,123,862,055.00 30.00 4.15

19. 포인트 사용 효과 분석

포인트를 사용한 주문과 사용하지 않은 주문의 평균 주문 금액, 재구매율을 비교하세요. (2024년 기준)

힌트 1: - orders.point_used > 0이면 포인트 사용 주문 - 재구매율: 해당 그룹 고객 중 2회 이상 주문한 비율 - CTE로 고객별 주문 특성을 먼저 집계

정답
WITH order_classified AS (
    SELECT
        customer_id,
        total_amount,
        CASE WHEN point_used > 0 THEN '포인트 사용' ELSE '미사용' END AS point_type
    FROM orders
    WHERE ordered_at >= '2024-01-01' AND ordered_at < '2025-01-01'
      AND status NOT IN ('cancelled', 'returned', 'return_requested')
),
customer_stats AS (
    SELECT
        point_type,
        customer_id,
        COUNT(*) AS order_count,
        AVG(total_amount) AS avg_amount
    FROM order_classified
    GROUP BY point_type, customer_id
)
SELECT
    point_type,
    COUNT(DISTINCT customer_id)    AS customer_count,
    ROUND(AVG(avg_amount))         AS avg_order_value,
    ROUND(100.0 * SUM(CASE WHEN order_count >= 2 THEN 1 ELSE 0 END)
        / COUNT(*), 1)            AS repeat_rate_pct
FROM customer_stats
GROUP BY point_type;

실행 결과 (2행)

point_type customer_count avg_order_value repeat_rate_pct
미사용 1607 895,789.00 57.70
포인트 사용 396 1,033,110.00 18.70

20. 종합 경영 대시보드

CEO를 위한 2024년 종합 경영 대시보드를 하나의 쿼리로 생성하세요. 총 매출, 주문 수, 고객 수, 평균 객단가, 반품률, 평균 배송일, 평균 리뷰 평점을 포함합니다.

힌트 1: - 각 지표를 서브쿼리 또는 CTE로 개별 계산한 뒤 결합 - CROSS JOIN 또는 스칼라 서브쿼리로 단일 행 결합 - 반품률 = 반품 주문 수 / 전체 주문 수

정답
WITH sales AS (
    SELECT
        COUNT(*)                            AS total_orders,
        COUNT(DISTINCT customer_id)         AS unique_customers,
        ROUND(SUM(total_amount))            AS total_revenue,
        ROUND(AVG(total_amount))            AS avg_order_value
    FROM orders
    WHERE ordered_at >= '2024-01-01' AND ordered_at < '2025-01-01'
      AND status NOT IN ('cancelled', 'returned', 'return_requested')
),
returns_stat AS (
    SELECT
        ROUND(100.0 * COUNT(*) / (
            SELECT COUNT(*) FROM orders
            WHERE ordered_at >= '2024-01-01' AND ordered_at < '2025-01-01'
        ), 1) AS return_rate_pct
    FROM orders
    WHERE ordered_at >= '2024-01-01' AND ordered_at < '2025-01-01'
      AND status IN ('returned', 'return_requested')
),
shipping_stat AS (
    SELECT
        ROUND(AVG(JULIANDAY(s.delivered_at) - JULIANDAY(s.shipped_at)), 1) AS avg_delivery_days
    FROM shipping AS s
    INNER JOIN orders AS o ON s.order_id = o.id
    WHERE s.status = 'delivered'
      AND s.shipped_at IS NOT NULL
      AND s.delivered_at IS NOT NULL
      AND o.ordered_at >= '2024-01-01' AND o.ordered_at < '2025-01-01'
),
review_stat AS (
    SELECT
        ROUND(AVG(rating), 2) AS avg_rating,
        COUNT(*) AS review_count
    FROM reviews
    WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01'
)
SELECT
    s.total_revenue,
    s.total_orders,
    s.unique_customers,
    s.avg_order_value,
    r.return_rate_pct,
    sh.avg_delivery_days,
    rv.avg_rating,
    rv.review_count
FROM sales AS s
CROSS JOIN returns_stat AS r
CROSS JOIN shipping_stat AS sh
CROSS JOIN review_stat AS rv;

실행 결과 (1행)

total_revenue total_orders unique_customers avg_order_value return_rate_pct avg_delivery_days avg_rating review_count
5,114,443,520.00 5320 1669 961,362.00 2.70 2.50 3.91 1267