서브쿼리 완전 정복
사용 테이블
products — 상품 (이름, 가격, 재고, 브랜드)
customers — 고객 (등급, 포인트, 가입채널)
orders — 주문 (상태, 금액, 일시)
order_items — 주문 상세 (수량, 단가)
reviews — 리뷰 (평점, 내용)
wishlists — 위시리스트 (고객-상품)
categories — 카테고리 (부모-자식 계층)
payments — 결제 (방법, 금액, 상태)
학습 범위
scalar subquery, IN, NOT IN, EXISTS, NOT EXISTS, FROM clause subquery, correlated subquery
1. 전체 평균 가격보다 비싼 상품의 이름과 가격을 조회하세요. 가격 내림차순, 상위 10개.
전체 평균 가격보다 비싼 상품의 이름과 가격을 조회하세요. 가격 내림차순, 상위 10개.
힌트 1: WHERE price > (SELECT AVG(price) FROM products) 형태의 스칼라 서브쿼리를 사용합니다.
정답
SELECT name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products)
ORDER BY price DESC
LIMIT 10;
실행 결과 (총 10행 중 상위 7행)
| name | price |
|---|---|
| MacBook Air 15 M3 실버 | 5,481,100.00 |
| ASUS TUF Gaming RTX 5080 화이트 | 4,526,600.00 |
| ASUS Dual RTX 5070 Ti [특별 한정판 에디션] 저소... | 4,496,700.00 |
| Razer Blade 18 블랙 | 4,353,100.00 |
| Razer Blade 16 실버 | 3,702,900.00 |
| ASUS ROG Strix G16CH 화이트 | 3,671,500.00 |
| ASUS ROG Zephyrus G16 | 3,429,900.00 |
2. 가장 비싼 상품의 이름, 가격, 카테고리 ID를 조회하세요.
가장 비싼 상품의 이름, 가격, 카테고리 ID를 조회하세요.
힌트 1: WHERE price = (SELECT MAX(price) FROM products) 형태로 최대값을 서브쿼리로 구합니다.
정답
실행 결과 (1행)
| name | price | category_id |
|---|---|---|
| MacBook Air 15 M3 실버 | 5,481,100.00 | 9 |
3. 주문을 한 번이라도 한 고객의 이름과 이메일을 조회하세요. 이름순 정렬, 상위 15개.
주문을 한 번이라도 한 고객의 이름과 이메일을 조회하세요. 이름순 정렬, 상위 15개.
힌트 1: WHERE id IN (SELECT customer_id FROM orders) — 서브쿼리가 주문이 존재하는 고객 ID 목록을 반환합니다.
정답
SELECT name, email
FROM customers
WHERE id IN (SELECT customer_id FROM orders)
ORDER BY name
LIMIT 15;
실행 결과 (총 15행 중 상위 7행)
| name | |
|---|---|
| 강경수 | user3281@testmail.kr |
| 강경숙 | user2384@testmail.kr |
| 강경숙 | user3645@testmail.kr |
| 강경자 | user1109@testmail.kr |
| 강경희 | user2103@testmail.kr |
| 강광수 | user3374@testmail.kr |
| 강도윤 | user1492@testmail.kr |
4. 리뷰를 한 번도 작성하지 않은 고객의 이름과 등급을 조회하세요. 등급 내림차순, 이름순, 상위 15개.
리뷰를 한 번도 작성하지 않은 고객의 이름과 등급을 조회하세요. 등급 내림차순, 이름순, 상위 15개.
힌트 1: WHERE id NOT IN (SELECT customer_id FROM reviews) — reviews 테이블에 없는 고객을 찾습니다.
정답
SELECT name, grade
FROM customers
WHERE id NOT IN (SELECT customer_id FROM reviews)
ORDER BY
CASE grade
WHEN 'VIP' THEN 1
WHEN 'GOLD' THEN 2
WHEN 'SILVER' THEN 3
ELSE 4
END,
name
LIMIT 15;
실행 결과 (총 15행 중 상위 7행)
| name | grade |
|---|---|
| 강지은 | VIP |
| 고상현 | VIP |
| 권상훈 | VIP |
| 김상철 | VIP |
| 김상철 | VIP |
| 김상호 | VIP |
| 김상호 | VIP |
5. 위시리스트에 담긴 적이 있는 상품의 이름과 가격을 조회하세요. 가격 내림차순, 상위 10개.
위시리스트에 담긴 적이 있는 상품의 이름과 가격을 조회하세요. 가격 내림차순, 상위 10개.
힌트 1: WHERE id IN (SELECT product_id FROM wishlists) — 위시리스트에 한 번이라도 등록된 상품 ID를 구합니다.
정답
SELECT name, price
FROM products
WHERE id IN (SELECT product_id FROM wishlists)
ORDER BY price DESC
LIMIT 10;
실행 결과 (총 10행 중 상위 7행)
| name | price |
|---|---|
| MacBook Air 15 M3 실버 | 5,481,100.00 |
| ASUS Dual RTX 5070 Ti [특별 한정판 에디션] 저소... | 4,496,700.00 |
| Razer Blade 18 블랙 | 4,353,100.00 |
| Razer Blade 16 실버 | 3,702,900.00 |
| ASUS ROG Strix G16CH 화이트 | 3,671,500.00 |
| ASUS ROG Strix GT35 | 3,296,800.00 |
| Razer Blade 18 블랙 | 2,987,500.00 |
6. 한 번도 주문되지 않은 상품의 이름과 재고를 조회하세요.
한 번도 주문되지 않은 상품의 이름과 재고를 조회하세요.
힌트 1: WHERE id NOT IN (SELECT product_id FROM order_items) — 주문 상세에 없는 상품을 필터링합니다.
정답
SELECT name, stock_qty
FROM products
WHERE id NOT IN (SELECT product_id FROM order_items)
ORDER BY stock_qty DESC;
실행 결과 (1행)
| name | stock_qty |
|---|---|
| FK 테스트 | 10 |
7. 평균 주문 금액보다 큰 주문의 주문번호, 금액, 주문일을 조회하세요. 취소 제외. 금액 내림차순, 상위 10
평균 주문 금액보다 큰 주문의 주문번호, 금액, 주문일을 조회하세요. 취소 제외. 금액 내림차순, 상위 10개.
힌트 1: 서브쿼리에서도 취소 주문을 제외해야 공정한 비교가 됩니다.
정답
SELECT order_number, total_amount, ordered_at
FROM orders
WHERE status NOT IN ('cancelled')
AND total_amount > (
SELECT AVG(total_amount)
FROM orders
WHERE status NOT IN ('cancelled')
)
ORDER BY total_amount DESC
LIMIT 10;
실행 결과 (총 10행 중 상위 7행)
| order_number | total_amount | ordered_at |
|---|---|---|
| ORD-20201121-08810 | 50,867,500.00 | 2020-11-21 12:04:42 |
| ORD-20250305-32265 | 46,820,024.00 | 2025-03-05 09:01:08 |
| ORD-20200209-05404 | 43,677,500.00 | 2020-02-09 23:36:36 |
| ORD-20251218-37240 | 38,626,400.00 | 2025-12-18 17:09:12 |
| ORD-20220106-15263 | 37,987,600.00 | 2022-01-06 17:24:14 |
| ORD-20200820-07684 | 37,518,200.00 | 2020-08-20 19:00:29 |
| ORD-20220224-15869 | 35,397,700.00 | 2022-02-24 23:01:50 |
8. 카테고리별 평균 가격을 구한 뒤, 자기 카테고리 평균보다 비싼 상품을 조회하세요. 상위 15개.
카테고리별 평균 가격을 구한 뒤, 자기 카테고리 평균보다 비싼 상품을 조회하세요. 상위 15개.
힌트 1: FROM products AS p INNER JOIN (SELECT category_id, AVG(price) AS avg_price FROM products GROUP BY category_id) AS ca ON ... — 인라인 뷰를 테이블처럼 JOIN합니다.
정답
SELECT p.name, p.price, ca.avg_price
FROM products AS p
INNER JOIN (
SELECT category_id, ROUND(AVG(price), 2) AS avg_price
FROM products
GROUP BY category_id
) AS ca ON p.category_id = ca.category_id
WHERE p.price > ca.avg_price
ORDER BY p.price DESC
LIMIT 15;
실행 결과 (총 15행 중 상위 7행)
| name | price | avg_price |
|---|---|---|
| ASUS TUF Gaming RTX 5080 화이트 | 4,526,600.00 | 2,406,500.00 |
| ASUS Dual RTX 5070 Ti [특별 한정판 에디션] 저소... | 4,496,700.00 | 2,406,500.00 |
| Razer Blade 18 블랙 | 4,353,100.00 | 2,684,477.78 |
| Razer Blade 16 실버 | 3,702,900.00 | 2,684,477.78 |
| ASUS ROG Strix G16CH 화이트 | 3,671,500.00 | 1,719,809.09 |
| ASUS ROG Zephyrus G16 | 3,429,900.00 | 2,684,477.78 |
| ASUS ROG Strix GT35 | 3,296,800.00 | 1,719,809.09 |
9. 각 고객의 주문 횟수를 SELECT 절 스칼라 서브쿼리로 구하세요. 주문 3회 이상인 고객만, 주문 횟수 내
각 고객의 주문 횟수를 SELECT 절 스칼라 서브쿼리로 구하세요. 주문 3회 이상인 고객만, 주문 횟수 내림차순 15개.
힌트 1: SELECT name, (SELECT COUNT(*) FROM orders WHERE customer_id = c.id) AS order_count FROM customers AS c — 외부 쿼리의 c.id를 서브쿼리에서 참조합니다.
정답
SELECT
c.name,
c.grade,
(SELECT COUNT(*)
FROM orders
WHERE customer_id = c.id
AND status NOT IN ('cancelled')) AS order_count
FROM customers AS c
WHERE (SELECT COUNT(*)
FROM orders
WHERE customer_id = c.id
AND status NOT IN ('cancelled')) >= 3
ORDER BY order_count DESC
LIMIT 15;
실행 결과 (총 15행 중 상위 7행)
| name | grade | order_count |
|---|---|---|
| 김병철 | VIP | 352 |
| 박정수 | VIP | 312 |
| 이영자 | VIP | 290 |
| 강명자 | VIP | 250 |
| 김성민 | VIP | 236 |
| 정유진 | VIP | 226 |
| 이미정 | VIP | 225 |
10. 상관 서브쿼리로 각 상품의 최근 리뷰 날짜를 함께 조회하세요. 상위 15개.
상관 서브쿼리로 각 상품의 최근 리뷰 날짜를 함께 조회하세요. 상위 15개.
힌트 1: (SELECT MAX(created_at) FROM reviews WHERE product_id = p.id) — 외부 쿼리의 p.id를 참조하는 상관 서브쿼리입니다.
정답
SELECT
p.name,
p.price,
(SELECT MAX(created_at)
FROM reviews
WHERE product_id = p.id) AS last_review_at
FROM products AS p
ORDER BY last_review_at DESC NULLS LAST
LIMIT 15;
실행 결과 (총 15행 중 상위 7행)
| name | price | last_review_at |
|---|---|---|
| Kingston FURY Beast DDR4 32GB 블랙 | 83,300.00 | 2026-01-19 09:22:49 |
| Super Flower Leadex VII XG 850W 화이트 | 71,200.00 | 2026-01-14 10:58:04 |
| 삼성 DDR4 16GB PC4-25600 | 83,400.00 | 2026-01-13 12:39:53 |
| Norton AntiVirus Plus 실버 | 74,800.00 | 2026-01-13 12:09:18 |
| Razer DeathAdder V4 Pro 화이트 | 52,500.00 | 2026-01-12 10:35:41 |
| 넷기어 GS308 실버 | 194,800.00 | 2026-01-11 21:02:15 |
| CORSAIR HX1200 실버 | 122,200.00 | 2026-01-11 15:23:03 |
11. 고객별 총 주문 금액을 FROM 서브쿼리로 구한 뒤, 상위 10명을 조회하세요.
고객별 총 주문 금액을 FROM 서브쿼리로 구한 뒤, 상위 10명을 조회하세요.
힌트 1: FROM (SELECT customer_id, SUM(total_amount) AS total_spent FROM orders WHERE ... GROUP BY customer_id) AS os — 파생 테이블을 별칭으로 사용합니다.
정답
SELECT c.name, c.grade, os.total_spent
FROM customers AS c
INNER JOIN (
SELECT customer_id, ROUND(SUM(total_amount), 2) AS total_spent
FROM orders
WHERE status NOT IN ('cancelled')
GROUP BY customer_id
) AS os ON c.id = os.customer_id
ORDER BY os.total_spent DESC
LIMIT 10;
실행 결과 (총 10행 중 상위 7행)
| name | grade | total_spent |
|---|---|---|
| 박정수 | VIP | 409,734,279.00 |
| 김병철 | VIP | 382,314,874.00 |
| 이미정 | VIP | 266,184,349.00 |
| 강명자 | VIP | 254,525,838.00 |
| 정유진 | VIP | 248,498,783.00 |
| 이영자 | VIP | 248,168,491.00 |
| 김성민 | VIP | 244,859,844.00 |
12. EXISTS를 사용하여 리뷰가 1건 이상 있는 상품만 조회하세요. 이름순, 상위 15개.
EXISTS를 사용하여 리뷰가 1건 이상 있는 상품만 조회하세요. 이름순, 상위 15개.
힌트 1: WHERE EXISTS (SELECT 1 FROM reviews WHERE product_id = p.id) — 서브쿼리가 한 행이라도 반환하면 TRUE입니다.
정답
SELECT p.name, p.price
FROM products AS p
WHERE EXISTS (
SELECT 1
FROM reviews
WHERE product_id = p.id
)
ORDER BY p.name
LIMIT 15;
실행 결과 (총 15행 중 상위 7행)
| name | price |
|---|---|
| AMD Ryzen 9 9900X | 335,700.00 |
| AMD Ryzen 9 9900X | 591,800.00 |
| APC Back-UPS Pro Gaming BGM1500B 블랙 | 516,300.00 |
| ASRock B850M Pro RS 블랙 | 201,000.00 |
| ASRock B850M Pro RS 실버 | 665,600.00 |
| ASRock B850M Pro RS 화이트 | 419,600.00 |
| ASRock B860M Pro RS 실버 | 351,700.00 |
13. 위시리스트에 담았지만 한 번도 주문하지 않은 고객-상품 조합을 찾으세요. 최근 20개.
위시리스트에 담았지만 한 번도 주문하지 않은 고객-상품 조합을 찾으세요. 최근 20개.
힌트 1: WHERE NOT EXISTS (SELECT 1 FROM order_items AS oi INNER JOIN orders AS o ON ... WHERE o.customer_id = w.customer_id AND oi.product_id = w.product_id) — 두 조건을 동시에 확인하는 상관 서브쿼리입니다.
정답
SELECT
c.name AS customer,
p.name AS product,
w.created_at AS wishlisted_at
FROM wishlists AS w
INNER JOIN customers AS c ON w.customer_id = c.id
INNER JOIN products AS p ON w.product_id = p.id
WHERE NOT EXISTS (
SELECT 1
FROM order_items AS oi
INNER JOIN orders AS o ON oi.order_id = o.id
WHERE o.customer_id = w.customer_id
AND oi.product_id = w.product_id
AND o.status NOT IN ('cancelled')
)
ORDER BY w.created_at DESC
LIMIT 20;
실행 결과 (총 20행 중 상위 7행)
| customer | product | wishlisted_at |
|---|---|---|
| 박성호 | 주연 리오나인 미니PC | 2025-12-30 19:11:10 |
| 김영숙 | 삼성 갤럭시북4 360 블랙 | 2025-12-30 17:42:08 |
| 백지후 | TP-Link TL-SG108 | 2025-12-30 11:47:20 |
| 이영수 | Seagate IronWolf 4TB 블랙 | 2025-12-30 10:41:18 |
| 이정순 | SK하이닉스 Platinum P41 2TB 블랙 | 2025-12-30 10:16:54 |
| 문하은 | TeamGroup T-Force Vulcan DDR5 32GB 52... | 2025-12-30 09:25:54 |
| 김현주 | APC Back-UPS Pro Gaming BGM1500B 블랙 | 2025-12-30 06:38:37 |
14. 각 카테고리에서 가장 비싼 상품의 이름과 가격을 구하세요.
각 카테고리에서 가장 비싼 상품의 이름과 가격을 구하세요.
힌트 1: WHERE p.price = (SELECT MAX(price) FROM products WHERE category_id = p.category_id) — 같은 카테고리 내 최대 가격을 상관 서브쿼리로 구합니다.
정답
SELECT p.name, p.price, p.category_id
FROM products AS p
WHERE p.price = (
SELECT MAX(price)
FROM products
WHERE category_id = p.category_id
)
ORDER BY p.price DESC;
실행 결과 (총 41행 중 상위 7행)
| name | price | category_id |
|---|---|---|
| MacBook Air 15 M3 실버 | 5,481,100.00 | 9 |
| ASUS TUF Gaming RTX 5080 화이트 | 4,526,600.00 | 28 |
| Razer Blade 18 블랙 | 4,353,100.00 | 7 |
| ASUS ROG Strix G16CH 화이트 | 3,671,500.00 | 3 |
| ASUS ExpertBook B5 [특별 한정판 에디션] RGB 라... | 2,121,600.00 | 6 |
| MSI Radeon RX 9070 XT GAMING X | 1,896,000.00 | 29 |
| ASUS ROG Swift PG32UCDM 실버 | 1,890,300.00 | 12 |
15. VIP 고객이 주문한 상품 중 평점 4 이상인 상품의 이름을 구하세요. 상위 15개.
VIP 고객이 주문한 상품 중 평점 4 이상인 상품의 이름을 구하세요. 상위 15개.
힌트 1: 3단계 중첩 IN 서브쿼리: VIP 고객 → 주문 → 상품. 평점 조건도 별도 서브쿼리로 필터링합니다.
정답
SELECT DISTINCT p.name, p.price
FROM products AS p
WHERE p.id IN (
SELECT oi.product_id
FROM order_items AS oi
WHERE oi.order_id IN (
SELECT o.id
FROM orders AS o
WHERE o.status NOT IN ('cancelled')
AND o.customer_id IN (
SELECT id FROM customers WHERE grade = 'VIP'
)
)
)
AND p.id IN (
SELECT product_id
FROM reviews
GROUP BY product_id
HAVING AVG(rating) >= 4.0
)
ORDER BY p.price DESC
LIMIT 15;
실행 결과 (총 15행 중 상위 7행)
| name | price |
|---|---|
| ASUS TUF Gaming RTX 5080 화이트 | 4,526,600.00 |
| Razer Blade 18 블랙 | 4,353,100.00 |
| ASUS ROG Strix G16CH 화이트 | 3,671,500.00 |
| Razer Blade 18 화이트 | 2,483,600.00 |
| ASUS ROG Strix Scar 16 | 2,452,500.00 |
| ASUS ROG Strix G16CH 실버 | 1,879,100.00 |
| 주연 리오나인 i9 하이엔드 | 1,849,900.00 |
16. 전체 평균 주문 금액과 자신의 평균 주문 금액을 비교하여, 전체 평균보다 높은 고객을 찾으세요. 상위 15개
전체 평균 주문 금액과 자신의 평균 주문 금액을 비교하여, 전체 평균보다 높은 고객을 찾으세요. 상위 15개.
힌트 1: 파생 테이블에서 고객별 평균 주문 금액을 구한 뒤, WHERE avg_amount > (SELECT AVG(total_amount) FROM orders ...) 조건으로 필터링합니다.
정답
SELECT
c.name,
c.grade,
ca.avg_amount,
ca.order_count
FROM customers AS c
INNER JOIN (
SELECT
customer_id,
ROUND(AVG(total_amount), 2) AS avg_amount,
COUNT(*) AS order_count
FROM orders
WHERE status NOT IN ('cancelled')
GROUP BY customer_id
) AS ca ON c.id = ca.customer_id
WHERE ca.avg_amount > (
SELECT AVG(total_amount)
FROM orders
WHERE status NOT IN ('cancelled')
)
ORDER BY ca.avg_amount DESC
LIMIT 15;
실행 결과 (총 15행 중 상위 7행)
| name | grade | avg_amount | order_count |
|---|---|---|---|
| 성진우 | VIP | 16,177,700.00 | 2 |
| 오서윤 | VIP | 13,895,400.00 | 1 |
| 오현준 | BRONZE | 10,428,340.00 | 5 |
| 김영미 | VIP | 9,889,465.75 | 4 |
| 한미영 | BRONZE | 8,659,350.00 | 2 |
| 이광수 | VIP | 8,319,100.00 | 1 |
| 손정자 | BRONZE | 8,301,220.00 | 5 |
17. 상품별 판매 수량이 전체 상품 평균 판매 수량보다 많은 상품을 찾으세요. 상위 15개.
상품별 판매 수량이 전체 상품 평균 판매 수량보다 많은 상품을 찾으세요. 상위 15개.
힌트 1: 먼저 상품별 총 판매량을 파생 테이블로 구합니다. 그 다음 WHERE total_qty > (SELECT AVG(total_qty) FROM (...)) 형태로 평균과 비교하세요.
정답
SELECT
p.name,
ps.total_qty
FROM products AS p
INNER JOIN (
SELECT product_id, SUM(quantity) AS total_qty
FROM order_items
GROUP BY product_id
) AS ps ON p.id = ps.product_id
WHERE ps.total_qty > (
SELECT AVG(total_qty)
FROM (
SELECT SUM(quantity) AS total_qty
FROM order_items
GROUP BY product_id
)
)
ORDER BY ps.total_qty DESC
LIMIT 15;
실행 결과 (총 15행 중 상위 7행)
| name | total_qty |
|---|---|
| Crucial T700 2TB 실버 | 1503 |
| AMD Ryzen 9 9900X | 1447 |
| SK하이닉스 Platinum P41 2TB 실버 | 1359 |
| 로지텍 G502 X PLUS | 1087 |
| Kingston FURY Beast DDR4 16GB 실버 | 1061 |
| SteelSeries Prime Wireless 블랙 | 1034 |
| SteelSeries Aerox 5 Wireless 실버 | 1030 |
18. 리뷰 평점이 해당 상품의 평균 평점보다 낮은 리뷰를 찾으세요. 최근 15개.
리뷰 평점이 해당 상품의 평균 평점보다 낮은 리뷰를 찾으세요. 최근 15개.
힌트 1: WHERE r.rating < (SELECT AVG(rating) FROM reviews WHERE product_id = r.product_id) — 각 리뷰에 대해 같은 상품의 평균 평점을 상관 서브쿼리로 구합니다.
정답
SELECT
r.id,
p.name AS product,
r.rating,
ROUND((SELECT AVG(rating) FROM reviews WHERE product_id = r.product_id), 2) AS avg_rating,
r.title,
r.created_at
FROM reviews AS r
INNER JOIN products AS p ON r.product_id = p.id
WHERE r.rating < (
SELECT AVG(rating)
FROM reviews
WHERE product_id = r.product_id
)
ORDER BY r.created_at DESC
LIMIT 15;
실행 결과 (총 15행 중 상위 7행)
| id | product | rating | avg_rating | title | created_at |
|---|---|---|---|---|---|
| 8546 | Kingston FURY Beast DDR4 32GB 블랙 | 2 | 3.96 | 개선 필요 | 2026-01-19 09:22:49 |
| 8538 | CORSAIR HX1200 실버 | 2 | 3.68 | 실망입니다 | 2026-01-11 15:23:03 |
| 8525 | SK하이닉스 Platinum P41 1TB | 2 | 4.13 | 개선 필요 | 2026-01-10 09:56:48 |
| 8544 | 로지텍 G715 화이트 | 4 | 4.09 | 좋아요 | 2026-01-09 20:41:38 |
| 8501 | Microsoft Ergonomic Keyboard 화이트 | 1 | 3.75 | NULL | 2026-01-05 20:37:52 |
| 8476 | TP-Link TL-SG108 | 3 | 3.88 | 평범해요 | 2025-12-31 10:11:39 |
| 8480 | V3 Endpoint Security 블랙 | 3 | 3.92 | NULL | 2025-12-30 09:05:01 |
19. 서브쿼리 vs JOIN 비교: "리뷰를 작성한 고객" 목록을 두 가지 방법으로 작성하세요.
서브쿼리 vs JOIN 비교: "리뷰를 작성한 고객" 목록을 두 가지 방법으로 작성하세요.
힌트 1: (A) WHERE id IN (SELECT customer_id FROM reviews), (B) INNER JOIN reviews ON ...에서 DISTINCT를 사용합니다. 두 쿼리의 결과는 동일하지만 실행 방식이 다릅니다.
정답
20. 3개 이상의 카테고리에서 상품을 구매한 고객의 이름과 구매 카테고리 수를 구하세요. 상위 15개.
3개 이상의 카테고리에서 상품을 구매한 고객의 이름과 구매 카테고리 수를 구하세요. 상위 15개.
힌트 1: orders → order_items → products를 JOIN하여 고객별 DISTINCT category_id 수를 구합니다. 이 집계를 파생 테이블로 만든 뒤 customers와 JOIN하세요.
정답
SELECT c.name, c.grade, cc.cat_count
FROM customers AS c
INNER JOIN (
SELECT
o.customer_id,
COUNT(DISTINCT p.category_id) AS cat_count
FROM orders AS o
INNER JOIN order_items AS oi ON o.id = oi.order_id
INNER JOIN products AS p ON oi.product_id = p.id
WHERE o.status NOT IN ('cancelled')
GROUP BY o.customer_id
HAVING COUNT(DISTINCT p.category_id) >= 3
) AS cc ON c.id = cc.customer_id
ORDER BY cc.cat_count DESC, c.name
LIMIT 15;
실행 결과 (총 15행 중 상위 7행)
| name | grade | cat_count |
|---|---|---|
| 강명자 | VIP | 38 |
| 김성민 | VIP | 38 |
| 박은서 | VIP | 38 |
| 이윤서 | VIP | 38 |
| 정유진 | VIP | 38 |
| 권정식 | VIP | 37 |
| 김민재 | BRONZE | 37 |