비즈니스 시나리오
사용 테이블
orders — 주문 (상태, 금액, 일시)
order_items — 주문 상세 (수량, 단가)
products — 상품 (이름, 가격, 재고, 브랜드)
categories — 카테고리 (부모-자식 계층)
customers — 고객 (등급, 포인트, 가입채널)
complaints — 고객 불만 (유형, 우선순위)
returns — 반품/교환 (사유, 상태)
shipping — 배송 (택배사, 추적번호, 상태)
reviews — 리뷰 (평점, 내용)
payments — 결제 (방법, 금액, 상태)
suppliers — 공급업체 (업체명, 연락처)
staff — 직원 (부서, 역할, 관리자)
학습 범위
CTE, Scalar Subquery, CASE, JULIANDAY, Business Reporting
1. 시나리오 1: CEO 주간 보고
역할: 데이터 분석가. CEO가 월요일 아침 회의에서 볼 주간 요약을 요청했습니다. 지난 주(2024-12-16 ~ 2024-12-22) 기준으로 주문 수, 매출, 평균 주문 금액, 신규 가입 고객 수, 전주 대비 매출 증감률을 한 번에 보여주세요.
힌트 1: 이번 주/지난 주/신규 고객을 각각 CTE로 계산한 뒤
FROM this_week, last_week, new_customers로 한 행에 합칩니다.
정답
WITH this_week AS (
SELECT
COUNT(*) AS orders,
ROUND(SUM(total_amount), 0) AS revenue,
ROUND(AVG(total_amount), 0) AS avg_order
FROM orders
WHERE ordered_at BETWEEN '2024-12-16' AND '2024-12-22 23:59:59'
AND status NOT IN ('cancelled')
),
last_week AS (
SELECT ROUND(SUM(total_amount), 0) AS revenue
FROM orders
WHERE ordered_at BETWEEN '2024-12-09' AND '2024-12-15 23:59:59'
AND status NOT IN ('cancelled')
),
new_customers AS (
SELECT COUNT(*) AS signups
FROM customers
WHERE created_at BETWEEN '2024-12-16' AND '2024-12-22 23:59:59'
)
SELECT
tw.orders,
tw.revenue,
tw.avg_order,
nc.signups AS new_customers,
ROUND(100.0 * (tw.revenue - lw.revenue) / NULLIF(lw.revenue, 0), 1) AS wow_growth_pct
FROM this_week tw, last_week lw, new_customers nc;
실행 결과 (1행)
| orders | revenue | avg_order | new_customers | wow_growth_pct |
|---|---|---|---|---|
| 108 | 92,193,999.00 | 853,648.00 | 15 | -13.00 |
2. 시나리오 2: MD팀 상품 리뷰
역할: MD(상품기획)팀 담당자. 2024년 매출 상위 20개 상품에 대해 상품명, 카테고리, 매출, 판매 수량, 평균 평점, 반품 건수를 보여주세요. 평점/반품 경고 플래그도 포함합니다.
힌트 1: 매출 Top 20을 CTE로 먼저 뽑고, reviews와 returns를 LEFT JOIN으로 붙이세요.
CASE로 경고 플래그를 추가합니다.
정답
WITH top_products AS (
SELECT
p.id, p.name, cat.name AS category,
SUM(oi.quantity) AS units_sold,
ROUND(SUM(oi.quantity * oi.unit_price), 0) AS revenue
FROM order_items oi
INNER JOIN orders o ON oi.order_id = o.id
INNER JOIN products p ON oi.product_id = p.id
INNER JOIN categories cat ON p.category_id = cat.id
WHERE o.ordered_at LIKE '2024%'
AND o.status NOT IN ('cancelled')
GROUP BY p.id, p.name, cat.name
ORDER BY revenue DESC
LIMIT 20
),
product_reviews AS (
SELECT product_id, ROUND(AVG(rating), 2) AS avg_rating, COUNT(*) AS review_count
FROM reviews GROUP BY product_id
),
product_returns AS (
SELECT oi.product_id, COUNT(DISTINCT r.id) AS return_count
FROM returns r
INNER JOIN order_items oi ON r.order_id = oi.order_id
GROUP BY oi.product_id
)
SELECT
tp.name, tp.category, tp.revenue, tp.units_sold,
COALESCE(pr.avg_rating, 0) AS avg_rating,
COALESCE(pr.review_count, 0) AS reviews,
COALESCE(ret.return_count, 0) AS returns,
CASE
WHEN pr.avg_rating < 3.5 THEN 'Low Rating'
WHEN ret.return_count > 5 THEN 'High Returns'
ELSE ''
END AS alert
FROM top_products tp
LEFT JOIN product_reviews pr ON tp.id = pr.product_id
LEFT JOIN product_returns ret ON tp.id = ret.product_id
ORDER BY tp.revenue DESC;
실행 결과 (총 20행 중 상위 7행)
| name | category | revenue | units_sold | avg_rating | reviews | returns | alert |
|---|---|---|---|---|---|---|---|
| Razer Blade 18 블랙 | 게이밍 노트북 | 169,770,900.00 | 39 | 4.10 | 20 | 23 | High Returns |
| Razer Blade 16 실버 | 게이밍 노트북 | 137,007,300.00 | 37 | 3.95 | 19 | 13 | High Returns |
| MacBook Air 15 M3 실버 | 맥북 | 131,546,400.00 | 24 | 3.75 | 4 | 2 | |
| ASUS Dual RTX 4060 Ti 블랙 | NVIDIA | 117,691,200.00 | 44 | 3.75 | 16 | 16 | High Returns |
| ASUS Dual RTX 5070 Ti 실버 | NVIDIA | 109,490,400.00 | 111 | 3.65 | 23 | 8 | High Returns |
| ASUS ROG Swift PG32UCDM 실버 | 게이밍 모니터 | 96,405,300.00 | 51 | 3.67 | 24 | 10 | High Returns |
| Razer Blade 18 블랙 | 게이밍 노트북 | 95,600,000.00 | 32 | 3.92 | 25 | 10 | High Returns |
3. 시나리오 3: 마케팅 캠페인 대상자
역할: 마케팅팀 담당자. 재활성화 이메일 캠페인을 준비합니다. 과거 3회 이상 구매했지만 최근 6개월간 주문이 없는 고객 리스트를 뽑아주세요.
힌트 1: HAVING COUNT(*) >= 3 AND MAX(ordered_at) < DATE('기준일', '-6 months')
정답
SELECT
c.name, c.email, c.grade,
MAX(o.ordered_at) AS last_order,
COUNT(*) AS order_count,
ROUND(SUM(o.total_amount), 0) AS total_spent,
CAST(JULIANDAY('2025-12-31') - JULIANDAY(MAX(o.ordered_at)) AS INTEGER) AS days_inactive
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id
WHERE o.status NOT IN ('cancelled')
AND c.is_active = 1
GROUP BY c.id, c.name, c.email, c.grade
HAVING COUNT(*) >= 3
AND MAX(o.ordered_at) < DATE('2025-12-31', '-6 months')
ORDER BY total_spent DESC;
실행 결과 (총 827행 중 상위 7행)
| name | grade | last_order | order_count | total_spent | days_inactive | |
|---|---|---|---|---|---|---|
| 이영철 | user33@testmail.kr | VIP | 2025-06-05 17:58:17 | 172 | 174,223,341.00 | 208 |
| 전영희 | user359@testmail.kr | SILVER | 2025-03-16 15:13:13 | 89 | 99,843,125.00 | 289 |
| 김민재 | user551@testmail.kr | BRONZE | 2025-06-01 14:44:44 | 71 | 99,783,432.00 | 212 |
| 양현정 | user844@testmail.kr | VIP | 2025-05-28 19:15:41 | 46 | 79,718,969.00 | 216 |
| 오은경 | user553@testmail.kr | GOLD | 2025-03-22 19:11:40 | 78 | 77,049,170.00 | 283 |
| 김상철 | user903@testmail.kr | GOLD | 2025-04-13 09:52:14 | 67 | 75,881,651.00 | 261 |
| 노시우 | user70@testmail.kr | BRONZE | 2024-10-26 22:15:57 | 97 | 70,981,337.00 | 430 |
4. 시나리오 4: 재무팀 월말 마감
역할: 재무팀 담당자. 2024년 12월 월말 마감 보고서를 준비합니다. 결제 수단별 건수, 총 결제 금액, 환불 건수, 환불 금액, 순매출을 보여주세요.
힌트 1: payments 테이블에서 SUM(CASE WHEN status = 'completed' ...)와
SUM(CASE WHEN status = 'refunded' ...)로 조건부 집계합니다.
정답
SELECT
method,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS paid_count,
ROUND(SUM(CASE WHEN status = 'completed' THEN amount ELSE 0 END), 0) AS paid_amount,
SUM(CASE WHEN status = 'refunded' THEN 1 ELSE 0 END) AS refund_count,
ROUND(SUM(CASE WHEN status = 'refunded' THEN amount ELSE 0 END), 0) AS refund_amount,
ROUND(SUM(CASE WHEN status = 'completed' THEN amount ELSE 0 END)
- SUM(CASE WHEN status = 'refunded' THEN amount ELSE 0 END), 0) AS net_revenue
FROM payments
WHERE created_at LIKE '2024-12%'
GROUP BY method
ORDER BY net_revenue DESC;
실행 결과 (6행)
| method | paid_count | paid_amount | refund_count | refund_amount | net_revenue |
|---|---|---|---|---|---|
| card | 182 | 153,966,200.00 | 10 | 12,925,184.00 | 141,041,016.00 |
| kakao_pay | 91 | 89,478,577.00 | 8 | 8,251,931.00 | 81,226,646.00 |
| naver_pay | 76 | 60,848,112.00 | 4 | 2,169,400.00 | 58,678,712.00 |
| bank_transfer | 51 | 56,388,551.00 | 1 | 253,200.00 | 56,135,351.00 |
| point | 23 | 28,788,195.00 | 1 | 555,500.00 | 28,232,695.00 |
| virtual_account | 36 | 27,679,127.00 | 3 | 5,461,600.00 | 22,217,527.00 |
5. 시나리오 5: 물류팀 배송 지연 보고
역할: 물류팀 담당자. 출고 후 3일 이상 걸린 배송 건을 택배사별로 집계하세요.
힌트 1: JULIANDAY(delivered_at) - JULIANDAY(shipped_at)로 배송 소요일을 계산하고,
>= 3 조건으로 지연 건만 필터링합니다.
정답
SELECT
sh.carrier,
COUNT(*) AS delayed_count,
ROUND(AVG(JULIANDAY(sh.delivered_at) - JULIANDAY(sh.shipped_at)), 1) AS avg_days,
MAX(CAST(JULIANDAY(sh.delivered_at) - JULIANDAY(sh.shipped_at) AS INTEGER)) AS max_days
FROM shipping sh
WHERE sh.delivered_at IS NOT NULL
AND sh.shipped_at IS NOT NULL
AND JULIANDAY(sh.delivered_at) - JULIANDAY(sh.shipped_at) >= 3
GROUP BY sh.carrier
ORDER BY delayed_count DESC;
실행 결과 (4행)
| carrier | delayed_count | avg_days | max_days |
|---|---|---|---|
| CJ대한통운 | 7071 | 3.50 | 4 |
| 한진택배 | 4507 | 3.50 | 4 |
| 로젠택배 | 3617 | 3.50 | 4 |
| 우체국택배 | 2731 | 3.50 | 4 |
6. 시나리오 6: 구매팀 발주 제안
역할: 구매팀 담당자. 최근 30일간 일평균 판매량 대비 현재 재고가 14일 미만인 상품 리스트를 만드세요. 공급업체 연락처도 포함합니다.
힌트 1: CTE로 30일간 일평균 판매량을 계산한 뒤,
stock_qty / avg_daily_sales < 14로 필터링합니다.
정답
WITH daily_sales AS (
SELECT
oi.product_id,
ROUND(1.0 * SUM(oi.quantity) / 30, 2) AS avg_daily_sales
FROM order_items oi
INNER JOIN orders o ON oi.order_id = o.id
WHERE o.ordered_at >= DATE('2025-12-31', '-30 days')
AND o.status NOT IN ('cancelled')
GROUP BY oi.product_id
)
SELECT
p.name, p.sku, p.stock_qty,
ds.avg_daily_sales,
CASE
WHEN ds.avg_daily_sales > 0
THEN CAST(p.stock_qty / ds.avg_daily_sales AS INTEGER)
ELSE 9999
END AS days_of_stock,
s.company_name AS supplier,
s.contact_name, s.phone AS supplier_phone
FROM products p
INNER JOIN daily_sales ds ON p.id = ds.product_id
INNER JOIN suppliers s ON p.supplier_id = s.id
WHERE p.is_active = 1
AND ds.avg_daily_sales > 0
AND p.stock_qty / ds.avg_daily_sales < 14
ORDER BY days_of_stock ASC;
실행 결과 (1행)
| name | sku | stock_qty | avg_daily_sales | days_of_stock | supplier | contact_name | supplier_phone |
|---|---|---|---|---|---|---|---|
| Arctic Freezer 36 A-RGB 화이트 | CO-AIR-ARC-00049 | 0 | 0.2 | 0 | 아틱코리아 | 최재현 | 020-2200-4333 |
7. 시나리오 7: CS팀 에스컬레이션
역할: CS팀장. 7일 이상 미해결 문의 중 VIP/GOLD 고객의 건을 우선순위와 함께 보여주세요.
힌트 1: complaints JOIN customers에서 status = 'open'과
grade IN ('VIP', 'GOLD')로 필터링합니다.
정답
SELECT
c.name, c.grade, c.email,
comp.title, comp.category, comp.priority,
comp.created_at,
CAST(JULIANDAY('2025-12-31') - JULIANDAY(comp.created_at) AS INTEGER) AS days_open,
COALESCE(cv.total_spent, 0) AS total_spent
FROM complaints comp
INNER JOIN customers c ON comp.customer_id = c.id
LEFT JOIN (
SELECT customer_id, ROUND(SUM(total_amount), 0) AS total_spent
FROM orders WHERE status NOT IN ('cancelled')
GROUP BY customer_id
) cv ON c.id = cv.customer_id
WHERE comp.status = 'open'
AND c.grade IN ('VIP', 'GOLD')
AND JULIANDAY('2025-12-31') - JULIANDAY(comp.created_at) >= 7
ORDER BY
CASE comp.priority
WHEN 'urgent' THEN 1 WHEN 'high' THEN 2
WHEN 'medium' THEN 3 ELSE 4
END,
cv.total_spent DESC;
실행 결과 (총 98행 중 상위 7행)
| name | grade | title | category | priority | created_at | days_open | total_spent | |
|---|---|---|---|---|---|---|---|---|
| 이경숙 | VIP | user614@testmail.kr | 환불 처리 언제 되나요? | refund_request | urgent | 2020-01-10 11:19:37 | 2181 | 148,203,887.00 |
| 이성민 | VIP | user388@testmail.kr | 환불 요청합니다 | refund_request | urgent | 2020-08-22 12:41:19 | 1956 | 133,207,783.00 |
| 노예지 | VIP | user1420@testmail.kr | 색상이 다르게 왔습니다 | wrong_item | urgent | 2024-07-11 05:53:51 | 537 | 124,382,602.00 |
| 황지영 | VIP | user1097@testmail.kr | 주문한 것과 다른 제품이 배송됐습니다 | wrong_item | urgent | 2020-12-15 06:41:56 | 1841 | 85,987,658.00 |
| 이서윤 | VIP | user1522@testmail.kr | 배송 지연 문의 | delivery_issue | urgent | 2021-04-23 22:21:29 | 1712 | 51,532,028.00 |
| 김보람 | GOLD | user875@testmail.kr | 환불 처리 언제 되나요? | refund_request | urgent | 2020-03-04 11:54:29 | 2127 | 49,812,024.00 |
| 이영진 | VIP | user2227@testmail.kr | 다른 주소로 배송됐습니다 | delivery_issue | urgent | 2024-03-17 16:26:20 | 653 | 46,684,135.00 |
8. 시나리오 8: 경영진 연간 KPI 대시보드
역할: 데이터 분석가. 연말 경영 회의용 KPI를 준비합니다. 2024년 핵심 지표를 한 행으로 요약하세요: 총 매출, 주문 수, 신규 고객 수, 활성 고객 수, 평균 객단가, 취소율, 반품률.
힌트 1: 각 KPI를 스칼라 서브쿼리 (SELECT ... FROM ...)로 SELECT 절에 나열하면
한 행으로 출력됩니다.
정답
SELECT
(SELECT ROUND(SUM(total_amount), 0) FROM orders
WHERE ordered_at LIKE '2024%' AND status NOT IN ('cancelled')) AS revenue,
(SELECT COUNT(*) FROM orders
WHERE ordered_at LIKE '2024%' AND status NOT IN ('cancelled')) AS orders,
(SELECT COUNT(*) FROM customers
WHERE created_at LIKE '2024%') AS new_customers,
(SELECT COUNT(DISTINCT customer_id) FROM orders
WHERE ordered_at LIKE '2024%' AND status NOT IN ('cancelled')) AS active_customers,
(SELECT ROUND(AVG(total_amount), 0) FROM orders
WHERE ordered_at LIKE '2024%' AND status NOT IN ('cancelled')) AS avg_order_value,
(SELECT ROUND(100.0 * SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) / COUNT(*), 1)
FROM orders WHERE ordered_at LIKE '2024%') AS cancel_rate,
(SELECT ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM orders WHERE ordered_at LIKE '2024%'), 1)
FROM returns WHERE requested_at LIKE '2024%') AS return_rate;
실행 결과 (1행)
| revenue | orders | new_customers | active_customers | avg_order_value | cancel_rate | return_rate |
|---|---|---|---|---|---|---|
| 5,346,776,711.00 | 5474 | 700 | 1692 | 976,759.00 | 5.40 | 2.70 |