Intermediate Comprehensive Exercises
Tables
orders — Orders (status, amount, date)
customers — Customers (grade, points, channel)
order_items — Order items (qty, unit price)
products — Products (name, price, stock, brand)
categories — Categories (parent-child hierarchy)
payments — Payments (method, amount, status)
reviews — Reviews (rating, content)
shipping — Shipping (carrier, tracking, status)
complaints — Complaints (type, priority)
wishlists — Wishlists (customer-product)
suppliers — Suppliers (company, contact)
returns — Returns (reason, status)
coupon_usage — Coupon usage records
Concepts
JOIN, subquery, date functions, string functions, CASE, UNION, GROUP BY, HAVING, aggregate functions, LAG, window functions
1. JOIN + GROUP BY: Find active product count and average price
JOIN + GROUP BY: Find active product count and average price per category. Top 10 by product count.
Hint 1: JOIN products with categories, aggregate per category with GROUP BY. Filter active with WHERE is_active = 1.
Answer
SELECT
cat.name AS category,
COUNT(*) AS product_count,
ROUND(AVG(p.price)) AS avg_price
FROM products p
INNER JOIN categories cat ON p.category_id = cat.id
WHERE p.is_active = 1
GROUP BY cat.id, cat.name
ORDER BY product_count DESC
LIMIT 10;
Result (top 7 of 10 rows)
| category | product_count | avg_price |
|---|---|---|
| Power Supply (PSU) | 11 | 234,645.00 |
| Gaming Monitor | 10 | 1,123,150.00 |
| Intel Socket | 10 | 527,080.00 |
| Case | 10 | 159,930.00 |
| Custom Build | 9 | 1,836,467.00 |
| AMD Socket | 9 | 511,056.00 |
| Speakers/Headsets | 9 | 274,056.00 |
2. JOIN + CASE: Find order number, customer name, and payment m
JOIN + CASE: Find order number, customer name, and payment method (in Korean). Most recent 10.
Hint 1: Connect 3 tables. Convert to Korean with CASE payments.method WHEN ... THEN ....
Answer
SELECT
o.order_number,
c.name AS customer_name,
CASE pay.method
WHEN 'card' THEN '카드'
WHEN 'bank_transfer' THEN '계좌이체'
WHEN 'kakao_pay' THEN '카카오페이'
WHEN 'naver_pay' THEN '네이버페이'
WHEN 'virtual_account' THEN '가상계좌'
WHEN 'point' THEN '포인트'
ELSE '기타'
END AS payment_method_kr,
o.total_amount
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN payments pay ON o.id = pay.order_id
ORDER BY o.ordered_at DESC
LIMIT 10;
Result (top 7 of 10 rows)
| order_number | customer_name | payment_method_kr | total_amount |
|---|---|---|---|
| ORD-20251231-37555 | Angel Jones | 카드 | 74,800.00 |
| ORD-20251231-37543 | Carla Watson | 카카오페이 | 134,100.00 |
| ORD-20251231-37552 | Martin Hanson | 카드 | 254,300.00 |
| ORD-20251231-37548 | Lucas Johnson | 계좌이체 | 187,700.00 |
| ORD-20251231-37542 | Adam Moore | 카카오페이 | 155,700.00 |
| ORD-20251231-37546 | Justin Murphy | 네이버페이 | 198,300.00 |
| ORD-20251231-37547 | Sara Hill | 계좌이체 | 335,000.00 |
3. Subquery + dates: Find the top 5 customers by order count in
Subquery + dates: Find the top 5 customers by order count in 2024, with name, count, and total spent.
Hint 1: Filter with ordered_at LIKE '2024%' in orders, aggregate with GROUP BY customer_id.
Answer
SELECT
c.name,
sub.order_count,
sub.total_spent
FROM (
SELECT customer_id, COUNT(*) AS order_count, SUM(total_amount) AS total_spent
FROM orders
WHERE ordered_at LIKE '2024%'
GROUP BY customer_id
ORDER BY order_count DESC
LIMIT 5
) sub
INNER JOIN customers c ON sub.customer_id = c.id
ORDER BY sub.order_count DESC;
Result (5 rows)
| name | order_count | total_spent |
|---|---|---|
| Dennis Brown | 25 | 25,706,293.00 |
| Christina Jennings | 25 | 24,330,429.00 |
| Carol Anderson | 24 | 28,684,563.00 |
| Mrs. Jennifer Rios | 23 | 24,524,212.00 |
| Tiffany Graham | 23 | 20,979,498.00 |
4. String functions + GROUP BY: Aggregate customer count by ema
String functions + GROUP BY: Aggregate customer count by email domain.
Hint 1: Extract domain with SUBSTR(email, INSTR(email, '@') + 1).
Answer
SELECT
SUBSTR(email, INSTR(email, '@') + 1) AS domain,
COUNT(*) AS customer_count
FROM customers
GROUP BY domain
ORDER BY customer_count DESC;
Result (1 rows)
| domain | customer_count |
|---|---|
| testmail.kr | 5230 |
5. JOIN + HAVING: Find customers who wrote 5+ reviews, with nam
JOIN + HAVING: Find customers who wrote 5+ reviews, with name and average rating. Top 10.
Hint 1: JOIN reviews with customers, then GROUP BY + HAVING COUNT(*) >= 5.
Answer
SELECT
c.name,
COUNT(*) AS review_count,
ROUND(AVG(r.rating), 1) AS avg_rating
FROM reviews r
INNER JOIN customers c ON r.customer_id = c.id
GROUP BY r.customer_id, c.name
HAVING COUNT(*) >= 5
ORDER BY review_count DESC
LIMIT 10;
Result (top 7 of 10 rows)
| name | review_count | avg_rating |
|---|---|---|
| Jason Rivera | 72 | 3.80 |
| Allen Snyder | 63 | 3.80 |
| Courtney Huff | 63 | 4.10 |
| Gabriel Walters | 62 | 4.00 |
| Brenda Garcia | 62 | 3.80 |
| Ronald Arellano | 60 | 3.60 |
| James Banks | 59 | 4.20 |
6. UNION + CASE: Combine new customer count and new product cou
UNION + CASE: Combine new customer count and new product count from 2024 into a single result.
Hint 1: COUNT 2024 data from each table and combine with UNION ALL.
Answer
SELECT '신규 고객' AS category, COUNT(*) AS count_2024
FROM customers
WHERE created_at LIKE '2024%'
UNION ALL
SELECT '신규 상품' AS category, COUNT(*) AS count_2024
FROM products
WHERE created_at LIKE '2024%';
Result (2 rows)
| category | count_2024 |
|---|---|
| 신규 고객 | 700 |
| 신규 상품 | 30 |
7. Date functions + JOIN: Find average delivery days per carrie
Date functions + JOIN: Find average delivery days per carrier. Delivered orders only.
Hint 1: Calculate days with julianday(delivered_at) - julianday(ordered_at).
Answer
SELECT
s.carrier,
COUNT(*) AS delivered_count,
ROUND(AVG(julianday(s.delivered_at) - julianday(o.ordered_at)), 1) AS avg_days
FROM shipping s
INNER JOIN orders o ON s.order_id = o.id
WHERE s.status = 'delivered' AND s.delivered_at IS NOT NULL
GROUP BY s.carrier
ORDER BY avg_days;
Result (5 rows)
| carrier | delivered_count | avg_days |
|---|---|---|
| DHL | 5184 | 4.50 |
| FedEx | 10,198 | 4.50 |
| OnTrac | 3417 | 4.50 |
| UPS | 8729 | 4.50 |
| USPS | 6990 | 4.50 |
8. Subquery + CASE: Show each product's price level relative to
Subquery + CASE: Show each product's price level relative to its category average. Top 15.
Hint 1: Compute category avg via subquery, JOIN it, then use CASE for comparison.
Answer
SELECT
p.name, p.price, cat_avg.avg_price,
CASE
WHEN p.price > cat_avg.avg_price * 1.2 THEN '고가'
WHEN p.price < cat_avg.avg_price * 0.8 THEN '저가'
ELSE '평균 수준'
END AS price_level
FROM products p
INNER JOIN (
SELECT category_id, ROUND(AVG(price)) AS avg_price
FROM products WHERE is_active = 1
GROUP BY category_id
) cat_avg ON p.category_id = cat_avg.category_id
WHERE p.is_active = 1
ORDER BY p.price DESC
LIMIT 15;
Result (top 7 of 15 rows)
| name | price | avg_price | price_level |
|---|---|---|---|
| MacBook Air 15 M3 Silver | 5,481,100.00 | 5,481,100.00 | 평균 수준 |
| ASUS Dual RTX 5070 Ti [Special Limite... | 4,496,700.00 | 2,207,600.00 | 고가 |
| Razer Blade 18 Black | 4,353,100.00 | 2,887,583.00 | 고가 |
| Razer Blade 16 Silver | 3,702,900.00 | 2,887,583.00 | 고가 |
| ASUS ROG Strix G16CH White | 3,671,500.00 | 1,836,467.00 | 고가 |
| ASUS ROG Strix GT35 | 3,296,800.00 | 1,836,467.00 | 고가 |
| Razer Blade 18 Black | 2,987,500.00 | 2,887,583.00 | 평균 수준 |
9. JOIN + GROUP BY + HAVING + Subquery: Find brands with above-
JOIN + GROUP BY + HAVING + Subquery: Find brands with above-average revenue. Top 10.
Hint 1: Overall average is the mean of per-brand revenues.
Answer
SELECT
p.brand,
SUM(oi.subtotal) AS total_revenue,
COUNT(DISTINCT oi.order_id) AS order_count
FROM order_items oi
INNER JOIN products p ON oi.product_id = p.id
GROUP BY p.brand
HAVING SUM(oi.subtotal) > (
SELECT AVG(brand_revenue)
FROM (
SELECT SUM(oi2.subtotal) AS brand_revenue
FROM order_items oi2
INNER JOIN products p2 ON oi2.product_id = p2.id
GROUP BY p2.brand
)
)
ORDER BY total_revenue DESC
LIMIT 10;
Result (top 7 of 10 rows)
| brand | total_revenue | order_count |
|---|---|---|
| ASUS | 6,181,415,600.00 | 4945 |
| Razer | 4,167,252,100.00 | 2754 |
| Samsung | 3,000,828,600.00 | 7169 |
| MSI | 2,787,039,100.00 | 3531 |
| LG | 2,221,778,900.00 | 1647 |
| ASRock | 1,741,420,800.00 | 3435 |
| Intel | 1,267,419,000.00 | 2579 |
10. Show 2024 monthly order status distribution: confirmed, canc
Show 2024 monthly order status distribution: confirmed, cancelled, in-progress counts and cancel rate.
Hint 1: Conditional aggregation with SUM(CASE WHEN ... THEN 1 ELSE 0 END).
Answer
SELECT
SUBSTR(ordered_at, 1, 7) AS month,
COUNT(*) AS total_orders,
SUM(CASE WHEN status = 'confirmed' THEN 1 ELSE 0 END) AS confirmed,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled,
SUM(CASE WHEN status NOT IN ('confirmed', 'cancelled') THEN 1 ELSE 0 END) AS in_progress,
ROUND(100.0 * SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) / COUNT(*), 1) AS cancel_rate
FROM orders
WHERE ordered_at LIKE '2024%'
GROUP BY SUBSTR(ordered_at, 1, 7)
ORDER BY month;
Result (top 7 of 12 rows)
| month | total_orders | confirmed | cancelled | in_progress | cancel_rate |
|---|---|---|---|---|---|
| 2024-01 | 346 | 314 | 21 | 11 | 6.10 |
| 2024-02 | 465 | 416 | 32 | 17 | 6.90 |
| 2024-03 | 601 | 555 | 29 | 17 | 4.80 |
| 2024-04 | 506 | 466 | 28 | 12 | 5.50 |
| 2024-05 | 415 | 385 | 19 | 11 | 4.60 |
| 2024-06 | 415 | 389 | 18 | 8 | 4.30 |
| 2024-07 | 414 | 381 | 23 | 10 | 5.60 |
11. Find products purchased by VIP customers with avg review rat
Find products purchased by VIP customers with avg review rating >= 4.0. Show name, brand, rating, and VIP purchase count. Top 10.
Hint 1: Multi-table JOIN for VIP purchases. Subquery for review avg >= 4.0.
Answer
SELECT
p.name, p.brand, review_avg.avg_rating,
COUNT(*) AS vip_purchase_count
FROM order_items oi
INNER JOIN orders o ON oi.order_id = o.id
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN products p ON oi.product_id = p.id
INNER JOIN (
SELECT product_id, ROUND(AVG(rating), 1) AS avg_rating
FROM reviews GROUP BY product_id HAVING AVG(rating) >= 4.0
) review_avg ON p.id = review_avg.product_id
WHERE c.grade = 'VIP'
GROUP BY p.id, p.name, p.brand, review_avg.avg_rating
ORDER BY vip_purchase_count DESC
LIMIT 10;
Result (top 7 of 10 rows)
| name | brand | avg_rating | vip_purchase_count |
|---|---|---|---|
| Crucial T700 2TB Silver | Crucial | 4.20 | 617 |
| Logitech G502 X PLUS | Logitech | 4.20 | 429 |
| be quiet! Light Base 900 | be quiet! | 4.10 | 338 |
| Arctic Freezer 36 A-RGB White | Arctic | 4.00 | 317 |
| Logitech MX Anywhere 3S Black | Logitech | 4.00 | 315 |
| Logitech G715 White | Logitech | 4.10 | 314 |
| be quiet! Pure Power 12 M 850W White | be quiet! | 4.10 | 304 |
12. LEFT JOIN + COALESCE: Find all products' name, price, total
LEFT JOIN + COALESCE: Find all products' name, price, total sold, and review count. Show 0 for none. Top 15.
Hint 1: Aggregate sales and reviews separately as subqueries, then LEFT JOIN to prevent cardinality explosion.
Answer
SELECT
p.name, p.price,
COALESCE(sales.total_qty, 0) AS total_sold,
COALESCE(rev.review_count, 0) AS review_count
FROM products p
LEFT JOIN (
SELECT product_id, SUM(quantity) AS total_qty
FROM order_items GROUP BY product_id
) sales ON p.id = sales.product_id
LEFT JOIN (
SELECT product_id, COUNT(*) AS review_count
FROM reviews GROUP BY product_id
) rev ON p.id = rev.product_id
WHERE p.is_active = 1
ORDER BY total_sold DESC
LIMIT 15;
Result (top 7 of 15 rows)
| name | price | total_sold | review_count |
|---|---|---|---|
| Crucial T700 2TB Silver | 257,000.00 | 1503 | 77 |
| AMD Ryzen 9 9900X | 335,700.00 | 1447 | 65 |
| SK hynix Platinum P41 2TB Silver | 255,500.00 | 1359 | 49 |
| Logitech G502 X PLUS | 97,500.00 | 1087 | 101 |
| Kingston FURY Beast DDR4 16GB Silver | 48,000.00 | 1061 | 102 |
| SteelSeries Prime Wireless Black | 89,800.00 | 1034 | 80 |
| SteelSeries Aerox 5 Wireless Silver | 100,000.00 | 1030 | 100 |
13. UNION + JOIN + GROUP BY: Calculate per-customer "activity sc
UNION + JOIN + GROUP BY: Calculate per-customer "activity score". Order=10pts, review=5pts, inquiry=3pts. Top 10.
Hint 1: Combine scores per activity with UNION ALL, then SUM externally for total.
Answer
SELECT
c.name, c.grade,
SUM(activity.score) AS total_score,
SUM(CASE WHEN activity.type = '주문' THEN 1 ELSE 0 END) AS orders,
SUM(CASE WHEN activity.type = '리뷰' THEN 1 ELSE 0 END) AS reviews,
SUM(CASE WHEN activity.type = '문의' THEN 1 ELSE 0 END) AS complaints
FROM (
SELECT customer_id, '주문' AS type, 10 AS score FROM orders
UNION ALL
SELECT customer_id, '리뷰' AS type, 5 AS score FROM reviews
UNION ALL
SELECT customer_id, '문의' AS type, 3 AS score FROM complaints
) activity
INNER JOIN customers c ON activity.customer_id = c.id
GROUP BY activity.customer_id, c.name, c.grade
ORDER BY total_score DESC
LIMIT 10;
Result (top 7 of 10 rows)
| name | grade | total_score | orders | reviews | complaints |
|---|---|---|---|---|---|
| Jason Rivera | VIP | 4095 | 366 | 72 | 25 |
| Allen Snyder | VIP | 3652 | 328 | 63 | 19 |
| Gabriel Walters | VIP | 3467 | 307 | 62 | 29 |
| Brenda Garcia | VIP | 3042 | 266 | 62 | 24 |
| James Banks | VIP | 2794 | 246 | 59 | 13 |
| Courtney Huff | VIP | 2742 | 237 | 63 | 19 |
| Ronald Arellano | VIP | 2697 | 234 | 60 | 19 |
14. Subquery + JOIN + dates: Compare customer counts by time-to-
Subquery + JOIN + dates: Compare customer counts by time-to-first-order segments (30d/31-90d/90d+).
Hint 1: Get first order date with MIN(ordered_at), compute days with julianday difference.
Answer
SELECT
CASE
WHEN days_to_first_order <= 30 THEN '30일 이내'
WHEN days_to_first_order <= 90 THEN '31~90일'
ELSE '90일 초과'
END AS segment,
COUNT(*) AS customer_count,
ROUND(AVG(days_to_first_order), 1) AS avg_days
FROM (
SELECT c.id,
ROUND(julianday(MIN(o.ordered_at)) - julianday(c.created_at)) AS days_to_first_order
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.created_at
)
GROUP BY segment
ORDER BY MIN(days_to_first_order);
Result (3 rows)
| segment | customer_count | avg_days |
|---|---|---|
| 30일 이내 | 955 | 8.30 |
| 31~90일 | 596 | 56.10 |
| 90일 초과 | 1288 | 329.50 |
15. Show revenue by payment method and card issuer. Non-card sho
Show revenue by payment method and card issuer. Non-card shows 'N/A'. Top 15.
Hint 1: Handle NULL with COALESCE(card_issuer, 'N/A'). GROUP BY method, card_issuer.
Answer
SELECT
CASE pay.method
WHEN 'card' THEN '카드'
WHEN 'bank_transfer' THEN '계좌이체'
WHEN 'kakao_pay' THEN '카카오페이'
WHEN 'naver_pay' THEN '네이버페이'
ELSE pay.method
END AS method_kr,
COALESCE(pay.card_issuer, '해당없음') AS issuer,
COUNT(*) AS payment_count,
SUM(pay.amount) AS total_amount,
ROUND(AVG(pay.amount)) AS avg_amount
FROM payments pay
WHERE pay.status = 'completed'
GROUP BY pay.method, pay.card_issuer
ORDER BY total_amount DESC
LIMIT 15;
Result (top 7 of 12 rows)
| method_kr | issuer | payment_count | total_amount | avg_amount |
|---|---|---|---|---|
| 카카오페이 | 해당없음 | 6886 | 6,781,114,303.00 | 984,768.00 |
| 네이버페이 | 해당없음 | 5270 | 5,420,480,093.00 | 1,028,554.00 |
| 카드 | Visa | 4701 | 4,629,735,049.00 | 984,840.00 |
| 카드 | Mastercard | 3741 | 3,950,729,395.00 | 1,056,062.00 |
| 계좌이체 | 해당없음 | 3429 | 3,456,454,657.00 | 1,008,007.00 |
| 카드 | American Express | 2320 | 2,371,883,900.00 | 1,022,364.00 |
| point | 해당없음 | 1770 | 1,780,334,619.00 | 1,005,839.00 |
16. Classify customers by RFM Recency: Active (30d), Warm (90d),
Classify customers by RFM Recency: Active (30d), Warm (90d), Cold (180d), Dormant.
Hint 1: Include customers without orders via LEFT JOIN. Use MAX and julianday for elapsed days.
Answer
SELECT
recency_group,
COUNT(*) AS customer_count,
ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER(), 1) AS pct
FROM (
SELECT c.id,
CASE
WHEN MAX(o.ordered_at) IS NULL THEN 'Dormant'
WHEN julianday('now') - julianday(MAX(o.ordered_at)) <= 30 THEN 'Active'
WHEN julianday('now') - julianday(MAX(o.ordered_at)) <= 90 THEN 'Warm'
WHEN julianday('now') - julianday(MAX(o.ordered_at)) <= 180 THEN 'Cold'
ELSE 'Dormant'
END AS recency_group
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id
)
GROUP BY recency_group
ORDER BY CASE recency_group WHEN 'Active' THEN 1 WHEN 'Warm' THEN 2 WHEN 'Cold' THEN 3 ELSE 4 END;
Result (2 rows)
| recency_group | customer_count | pct |
|---|---|---|
| Cold | 960 | 18.40 |
| Dormant | 4270 | 81.60 |
17. Monthly revenue report: 2024 monthly revenue, order count, u
Monthly revenue report: 2024 monthly revenue, order count, unique customers, avg order value, and MoM growth rate.
Hint 1: Use LAG() window function to get previous month revenue and calculate growth rate.
Answer
SELECT
month, total_revenue, order_count, unique_customers,
ROUND(total_revenue * 1.0 / order_count) AS avg_order_value,
CASE
WHEN prev_revenue IS NULL THEN '-'
ELSE ROUND((total_revenue - prev_revenue) * 100.0 / prev_revenue, 1) || '%'
END AS growth_rate
FROM (
SELECT
SUBSTR(ordered_at, 1, 7) AS month,
SUM(total_amount) AS total_revenue,
COUNT(*) AS order_count,
COUNT(DISTINCT customer_id) AS unique_customers,
LAG(SUM(total_amount)) OVER (ORDER BY SUBSTR(ordered_at, 1, 7)) AS prev_revenue
FROM orders
WHERE ordered_at LIKE '2024%' AND status NOT IN ('cancelled')
GROUP BY SUBSTR(ordered_at, 1, 7)
)
ORDER BY month;
Result (top 7 of 12 rows)
| month | total_revenue | order_count | unique_customers | avg_order_value | growth_rate |
|---|---|---|---|---|---|
| 2024-01 | 301,075,320.00 | 325 | 275 | 926,386.00 | - |
| 2024-02 | 426,177,449.00 | 433 | 345 | 984,244.00 | 41.6% |
| 2024-03 | 536,322,767.00 | 572 | 428 | 937,627.00 | 25.8% |
| 2024-04 | 470,154,081.00 | 478 | 362 | 983,586.00 | -12.3% |
| 2024-05 | 459,724,596.00 | 396 | 323 | 1,160,921.00 | -2.2% |
| 2024-06 | 377,040,302.00 | 397 | 327 | 949,724.00 | -18.0% |
| 2024-07 | 363,944,597.00 | 391 | 320 | 930,805.00 | -3.5% |
18. Find avg order amount, avg review rating, and return rate pe
Find avg order amount, avg review rating, and return rate per customer grade.
Hint 1: Aggregate each metric separately as subqueries, then GROUP BY grade.
Answer
SELECT
c.grade,
COUNT(DISTINCT c.id) AS customer_count,
ROUND(AVG(order_stats.avg_amount)) AS avg_order_amount,
ROUND(AVG(review_stats.avg_rating), 2) AS avg_rating,
ROUND(100.0 * SUM(COALESCE(return_stats.return_count, 0))
/ NULLIF(SUM(COALESCE(order_stats.order_count, 0)), 0), 2) AS return_rate_pct
FROM customers c
LEFT JOIN (
SELECT customer_id, COUNT(*) AS order_count, AVG(total_amount) AS avg_amount
FROM orders GROUP BY customer_id
) order_stats ON c.id = order_stats.customer_id
LEFT JOIN (
SELECT customer_id, AVG(rating) AS avg_rating
FROM reviews GROUP BY customer_id
) review_stats ON c.id = review_stats.customer_id
LEFT JOIN (
SELECT customer_id, COUNT(*) AS return_count
FROM returns GROUP BY customer_id
) return_stats ON c.id = return_stats.customer_id
GROUP BY c.grade
ORDER BY CASE c.grade WHEN 'VIP' THEN 1 WHEN 'GOLD' THEN 2 WHEN 'SILVER' THEN 3 WHEN 'BRONZE' THEN 4 END;
Result (4 rows)
| grade | customer_count | avg_order_amount | avg_rating | return_rate_pct |
|---|---|---|---|---|
| VIP | 368 | 1,419,393.00 | 3.88 | 2.56 |
| GOLD | 524 | 1,119,695.00 | 3.92 | 2.48 |
| SILVER | 479 | 887,476.00 | 3.90 | 2.68 |
| BRONZE | 3859 | 699,518.00 | 3.92 | 3.02 |
19. Low stock risk check: Find products where current stock is l
Low stock risk check: Find products where current stock is less than 30-day sales volume. Include estimated days left. Top 15.
Hint 1: Calculate 30-day sales from order_items JOIN orders with date filter.
Answer
SELECT
p.name, p.stock_qty AS current_stock,
COALESCE(sales.qty_30d, 0) AS sold_30d,
CASE
WHEN COALESCE(sales.qty_30d, 0) = 0 THEN '판매 없음'
ELSE CAST(ROUND(p.stock_qty * 30.0 / sales.qty_30d) AS INTEGER) || '일'
END AS est_days_left
FROM products p
LEFT JOIN (
SELECT oi.product_id, SUM(oi.quantity) AS qty_30d
FROM order_items oi
INNER JOIN orders o ON oi.order_id = o.id
WHERE o.ordered_at >= date('now', '-30 days') AND o.status NOT IN ('cancelled')
GROUP BY oi.product_id
) sales ON p.id = sales.product_id
WHERE p.is_active = 1 AND p.stock_qty < COALESCE(sales.qty_30d, 0)
ORDER BY p.stock_qty ASC
LIMIT 15;
20. Category hierarchy analysis: Find product count, total reven
Category hierarchy analysis: Find product count, total revenue, and avg review rating per top-level category (depth=0).
Hint 1: Follow the category parent_id upward. Self-JOIN categories to find the top-level.
Answer
SELECT
top_cat.name AS top_category,
COUNT(DISTINCT p.id) AS product_count,
COALESCE(SUM(sales.revenue), 0) AS total_revenue,
ROUND(AVG(rev.avg_rating), 1) AS avg_rating
FROM categories top_cat
LEFT JOIN categories mid_cat ON mid_cat.parent_id = top_cat.id
LEFT JOIN categories sub_cat ON sub_cat.parent_id = mid_cat.id
LEFT JOIN products p ON p.category_id IN (top_cat.id, mid_cat.id, sub_cat.id)
LEFT JOIN (
SELECT product_id, SUM(subtotal) AS revenue FROM order_items GROUP BY product_id
) sales ON p.id = sales.product_id
LEFT JOIN (
SELECT product_id, AVG(rating) AS avg_rating FROM reviews GROUP BY product_id
) rev ON p.id = rev.product_id
WHERE top_cat.depth = 0
GROUP BY top_cat.id, top_cat.name
ORDER BY total_revenue DESC;
Result (top 7 of 18 rows)
| top_category | product_count | total_revenue | avg_rating |
|---|---|---|---|
| Laptop | 29 | 10,050,178,200.00 | 3.70 |
| Graphics Card | 15 | 5,559,698,400.00 | 3.90 |
| Monitor | 22 | 4,712,362,900.00 | 3.90 |
| Motherboard | 23 | 3,225,292,000.00 | 3.70 |
| CPU | 7 | 1,858,019,600.00 | 3.80 |
| Speakers/Headsets | 12 | 1,546,414,500.00 | 3.90 |
| Storage | 15 | 1,511,680,300.00 | 3.90 |
21. Supplier performance comparison: product count, revenue, avg
Supplier performance comparison: product count, revenue, avg rating, return rate per supplier. Top 10 by revenue.
Hint 1: Separating each metric into subqueries keeps it clean.
Answer
SELECT
s.company_name,
COUNT(DISTINCT p.id) AS product_count,
COALESCE(SUM(sales.revenue), 0) AS total_revenue,
ROUND(AVG(rev.avg_rating), 1) AS avg_rating,
ROUND(100.0 * COALESCE(SUM(ret.return_count), 0)
/ NULLIF(COALESCE(SUM(sales.sold_count), 0), 0), 2) AS return_rate_pct
FROM suppliers s
INNER JOIN products p ON p.supplier_id = s.id
LEFT JOIN (SELECT product_id, SUM(subtotal) AS revenue, COUNT(*) AS sold_count FROM order_items GROUP BY product_id) sales ON p.id = sales.product_id
LEFT JOIN (SELECT product_id, AVG(rating) AS avg_rating FROM reviews GROUP BY product_id) rev ON p.id = rev.product_id
LEFT JOIN (SELECT oi.product_id, COUNT(DISTINCT r.id) AS return_count FROM returns r INNER JOIN orders o ON r.order_id = o.id INNER JOIN order_items oi ON o.id = oi.order_id GROUP BY oi.product_id) ret ON p.id = ret.product_id
GROUP BY s.id, s.company_name
ORDER BY total_revenue DESC
LIMIT 10;
Result (top 7 of 10 rows)
| company_name | product_count | total_revenue | avg_rating | return_rate_pct |
|---|---|---|---|---|
| ASUS Corp. | 26 | 6,181,415,600.00 | 3.90 | 3.42 |
| Razer Corp. | 9 | 4,167,252,100.00 | 3.90 | 4.17 |
| Samsung Official Distribution | 26 | 3,000,828,600.00 | 3.80 | 2.96 |
| MSI Corp. | 13 | 2,787,039,100.00 | 3.90 | 3.50 |
| LG Official Distribution | 11 | 2,221,778,900.00 | 3.90 | 3.61 |
| ASRock Corp. | 11 | 1,741,420,800.00 | 3.60 | 3.38 |
| Intel Corp. | 6 | 1,267,419,000.00 | 3.80 | 3.21 |
22. Coupon impact analysis: Compare avg amount, avg items, and c
Coupon impact analysis: Compare avg amount, avg items, and completion rate between coupon-used and non-coupon orders.
Hint 1: JOIN orders LEFT JOIN coupon_usage. Distinguish with CASE on cu.id IS NOT NULL.
Answer
SELECT
CASE WHEN cu.id IS NOT NULL THEN '쿠폰 사용' ELSE '쿠폰 미사용' END AS coupon_group,
COUNT(DISTINCT o.id) AS order_count,
ROUND(AVG(o.total_amount)) AS avg_amount,
ROUND(AVG(item_stats.item_count), 1) AS avg_items,
ROUND(100.0 * SUM(CASE WHEN o.status = 'confirmed' THEN 1 ELSE 0 END)
/ COUNT(DISTINCT o.id), 1) AS confirm_rate_pct
FROM orders o
LEFT JOIN coupon_usage cu ON o.id = cu.order_id
LEFT JOIN (SELECT order_id, COUNT(*) AS item_count FROM order_items GROUP BY order_id) item_stats ON o.id = item_stats.order_id
GROUP BY CASE WHEN cu.id IS NOT NULL THEN '쿠폰 사용' ELSE '쿠폰 미사용' END;
Result (2 rows)
| coupon_group | order_count | avg_amount | avg_items | confirm_rate_pct |
|---|---|---|---|---|
| 쿠폰 미사용 | 35,893 | 998,277.00 | 2.40 | 91.20 |
| 쿠폰 사용 | 1664 | 1,413,663.00 | 2.90 | 100.00 |
23. Wishlist conversion analysis: Find conversion rate per categ
Wishlist conversion analysis: Find conversion rate per category for wishlisted products. 5+ entries only. Top 10.
Hint 1: Determine conversion via wishlists.is_purchased.
Answer
SELECT
cat.name AS category,
COUNT(*) AS wishlist_count,
SUM(w.is_purchased) AS purchased_count,
ROUND(100.0 * SUM(w.is_purchased) / COUNT(*), 1) AS conversion_rate_pct
FROM wishlists w
INNER JOIN products p ON w.product_id = p.id
INNER JOIN categories cat ON p.category_id = cat.id
GROUP BY cat.id, cat.name
HAVING COUNT(*) >= 5
ORDER BY conversion_rate_pct DESC
LIMIT 10;
Result (top 7 of 10 rows)
| category | wishlist_count | purchased_count | conversion_rate_pct |
|---|---|---|---|
| Barebone | 8 | 1 | 12.50 |
| Membrane | 81 | 6 | 7.40 |
| Case | 96 | 7 | 7.30 |
| Intel Socket | 92 | 5 | 5.40 |
| DDR5 | 64 | 3 | 4.70 |
| Wired | 22 | 1 | 4.50 |
| AMD | 47 | 2 | 4.30 |
24. Customer inquiry response analysis: Find avg resolution time
Customer inquiry response analysis: Find avg resolution time, resolution rate, and escalation rate per inquiry type.
Hint 1: Resolution time: julianday difference. Escalation: SUM(escalated).
Answer
SELECT
category,
COUNT(*) AS total_count,
ROUND(AVG(CASE WHEN resolved_at IS NOT NULL THEN julianday(resolved_at) - julianday(created_at) END), 1) AS avg_resolve_days,
ROUND(100.0 * SUM(CASE WHEN resolved_at IS NOT NULL THEN 1 ELSE 0 END) / COUNT(*), 1) AS resolve_rate_pct,
ROUND(100.0 * SUM(escalated) / COUNT(*), 1) AS escalation_rate_pct
FROM complaints
GROUP BY category
ORDER BY total_count DESC;
Result (7 rows)
| category | total_count | avg_resolve_days | resolve_rate_pct | escalation_rate_pct |
|---|---|---|---|---|
| general_inquiry | 1232 | 1.70 | 94.20 | 4.20 |
| delivery_issue | 708 | 0.7 | 95.60 | 4.90 |
| refund_request | 522 | 0.8 | 94.40 | 11.10 |
| product_defect | 460 | 0.6 | 95.00 | 12.20 |
| price_inquiry | 439 | 1.60 | 95.70 | 3.20 |
| wrong_item | 240 | 0.6 | 94.20 | 6.70 |
| exchange_request | 212 | 1.30 | 95.30 | 14.60 |
25. Comprehensive dashboard query: Total customers, active custo
Comprehensive dashboard query: Total customers, active customers, this/last month revenue, avg order amount, avg review rating, and unresolved inquiries in one query.
Hint 1: Create each KPI as SELECT with metric name and value, combine with UNION ALL.
Answer
SELECT '총 고객 수' AS metric, CAST(COUNT(*) AS TEXT) AS value FROM customers
UNION ALL
SELECT '활성 고객 수', CAST(COUNT(*) AS TEXT) FROM customers WHERE is_active = 1
UNION ALL
SELECT '이번 달 매출', CAST(COALESCE(SUM(total_amount), 0) AS TEXT) FROM orders WHERE SUBSTR(ordered_at, 1, 7) = strftime('%Y-%m', 'now') AND status NOT IN ('cancelled')
UNION ALL
SELECT '지난 달 매출', CAST(COALESCE(SUM(total_amount), 0) AS TEXT) FROM orders WHERE SUBSTR(ordered_at, 1, 7) = strftime('%Y-%m', 'now', '-1 month') AND status NOT IN ('cancelled')
UNION ALL
SELECT '평균 주문 금액', CAST(ROUND(AVG(total_amount)) AS TEXT) FROM orders WHERE status NOT IN ('cancelled')
UNION ALL
SELECT '평균 리뷰 평점', CAST(ROUND(AVG(rating), 2) AS TEXT) FROM reviews
UNION ALL
SELECT '미해결 문의', CAST(COUNT(*) AS TEXT) FROM complaints WHERE status NOT IN ('resolved', 'closed');
Result (7 rows)
| metric | value |
|---|---|
| 총 고객 수 | 5230 |
| 활성 고객 수 | 3660 |
| 이번 달 매출 | 0 |
| 지난 달 매출 | 0 |
| 평균 주문 금액 | 1015193.0 |
| 평균 리뷰 평점 | 3.9 |
| 미해결 문의 | 197 |