고객 분석
사용 테이블
customers — 고객 (등급, 포인트, 가입채널)
customer_addresses — 배송지 (주소, 기본 여부)
orders — 주문 (상태, 금액, 일시)
products — 상품 (이름, 가격, 재고, 브랜드)
tags — 태그 (이름, 카테고리)
product_tags — 상품-태그 연결
학습 범위
SELECT, WHERE, GROUP BY, COUNT, AVG, MAX, COALESCE, SUBSTR, INSTR, JOIN, HAVING, LIKE, IN, CASE WHEN
1. 활성 고객과 비활성 고객 수를 각각 구하세요.
활성 고객과 비활성 고객 수를 각각 구하세요.
힌트 1: GROUP BY is_active로 그룹화하고 COUNT(*)로 각 그룹의 수를 세세요
정답
실행 결과 (2행)
| is_active | customer_count |
|---|---|
| 0 | 1570 |
| 1 | 3660 |
2. 고객 등급(grade)별 인원 수를 구하세요.
고객 등급(grade)별 인원 수를 구하세요.
힌트 1: GROUP BY grade와 COUNT(*)를 사용하세요
정답
실행 결과 (4행)
| grade | cnt |
|---|---|
| BRONZE | 3859 |
| GOLD | 524 |
| SILVER | 479 |
| VIP | 368 |
3. VIP 등급 고객의 이름, 이메일, 적립금을 적립금 내림차순으로 조회하세요.
VIP 등급 고객의 이름, 이메일, 적립금을 적립금 내림차순으로 조회하세요.
힌트 1: WHERE grade = 'VIP'로 필터링하고 ORDER BY point_balance DESC로 정렬
정답
실행 결과 (총 368행 중 상위 7행)
| name | point_balance | |
|---|---|---|
| 박정수 | user226@testmail.kr | 3,955,828 |
| 김병철 | user97@testmail.kr | 3,518,880 |
| 강명자 | user162@testmail.kr | 2,450,166 |
| 정유진 | user356@testmail.kr | 2,383,491 |
| 김성민 | user227@testmail.kr | 2,297,542 |
| 이미정 | user549@testmail.kr | 2,276,622 |
| 이영자 | user98@testmail.kr | 2,218,590 |
4. 가장 최근에 가입한 고객 10명의 이름, 가입일, 등급을 조회하세요.
가장 최근에 가입한 고객 10명의 이름, 가입일, 등급을 조회하세요.
힌트 1: ORDER BY created_at DESC로 최신순 정렬 후 LIMIT 10
정답
실행 결과 (총 10행 중 상위 7행)
| name | created_at | grade |
|---|---|---|
| 이은지 | 2025-12-30 20:49:59 | BRONZE |
| 박성호 | 2025-12-30 18:50:02 | BRONZE |
| 김서연 | 2025-12-30 10:18:14 | BRONZE |
| 김영순 | 2025-12-30 06:02:53 | BRONZE |
| 문하은 | 2025-12-30 05:59:32 | BRONZE |
| 한윤서 | 2025-12-30 05:43:21 | BRONZE |
| 강성진 | 2025-12-29 17:18:36 | BRONZE |
5. 성별(gender)별 고객 수와 비율(%)을 구하세요. NULL도 포함합니다.
성별(gender)별 고객 수와 비율(%)을 구하세요. NULL도 포함합니다.
힌트 1: COALESCE(gender, '미입력')으로 NULL 처리. 비율은 100.0 * COUNT() / (SELECT COUNT() FROM customers)로 계산
정답
SELECT
COALESCE(gender, '미입력') AS gender,
COUNT(*) AS cnt,
ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM customers), 1) AS pct
FROM customers
GROUP BY gender;
실행 결과 (3행)
| gender | cnt | pct |
|---|---|---|
| 미입력 | 529 | 10.10 |
| F | 1669 | 31.90 |
| M | 3032 | 58.00 |
6. 적립금(point_balance)이 가장 많은 상위 20명의 이름, 등급, 적립금을 조회하세요.
적립금(point_balance)이 가장 많은 상위 20명의 이름, 등급, 적립금을 조회하세요.
힌트 1: ORDER BY point_balance DESC와 LIMIT 20을 사용하세요. 활성 고객만 대상
정답
SELECT name, grade, point_balance
FROM customers
WHERE is_active = 1
ORDER BY point_balance DESC
LIMIT 20;
실행 결과 (총 20행 중 상위 7행)
| name | grade | point_balance |
|---|---|---|
| 박정수 | VIP | 3,955,828 |
| 김병철 | VIP | 3,518,880 |
| 강명자 | VIP | 2,450,166 |
| 정유진 | VIP | 2,383,491 |
| 김성민 | VIP | 2,297,542 |
| 이미정 | VIP | 2,276,622 |
| 이영자 | VIP | 2,218,590 |
7. 생년월일(birth_date)이 NULL인 고객 수를 구하세요.
생년월일(birth_date)이 NULL인 고객 수를 구하세요.
힌트 1: WHERE birth_date IS NULL로 NULL을 확인하세요. = NULL은 동작하지 않음
정답
실행 결과 (1행)
| no_birthdate |
|---|
| 738 |
8. 가입 연도별 신규 고객 수를 구하세요.
가입 연도별 신규 고객 수를 구하세요.
힌트 1: SUBSTR(created_at, 1, 4)로 연도를 추출하고 GROUP BY로 그룹화
정답
SELECT
SUBSTR(created_at, 1, 4) AS join_year,
COUNT(*) AS new_customers
FROM customers
GROUP BY SUBSTR(created_at, 1, 4)
ORDER BY join_year;
실행 결과 (총 10행 중 상위 7행)
| join_year | new_customers |
|---|---|
| 2016 | 100 |
| 2017 | 180 |
| 2018 | 300 |
| 2019 | 450 |
| 2020 | 700 |
| 2021 | 800 |
| 2022 | 650 |
9. last_login_at이 NULL인 고객 수와 전체 대비 비율을 구하세요.
last_login_at이 NULL인 고객 수와 전체 대비 비율을 구하세요.
힌트 1: 비율은 서브쿼리 (SELECT COUNT(*) FROM customers)로 전체 수를 구해서 나누세요
정답
SELECT
COUNT(*) AS never_logged_in,
ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM customers), 1) AS pct
FROM customers
WHERE last_login_at IS NULL;
실행 결과 (1행)
| never_logged_in | pct |
|---|---|
| 281 | 5.40 |
10. 등급별 평균 적립금과 최대 적립금을 구하세요.
등급별 평균 적립금과 최대 적립금을 구하세요.
힌트 1: GROUP BY grade와 AVG(point_balance), MAX(point_balance)를 함께 사용하세요
정답
SELECT
grade,
COUNT(*) AS cnt,
ROUND(AVG(point_balance), 0) AS avg_points,
MAX(point_balance) AS max_points
FROM customers
GROUP BY grade
ORDER BY avg_points DESC;
실행 결과 (4행)
| grade | cnt | avg_points | max_points |
|---|---|---|---|
| VIP | 368 | 407,015.00 | 3,955,828 |
| GOLD | 524 | 147,711.00 | 2,007,717 |
| SILVER | 479 | 95,042.00 | 1,266,757 |
| BRONZE | 3859 | 16,779.00 | 956,983 |
11. 고객 이메일의 도메인(@뒤)별 고객 수를 구하세요.
고객 이메일의 도메인(@뒤)별 고객 수를 구하세요.
힌트 1: SUBSTR(email, INSTR(email, '@') + 1)로 @ 뒤의 도메인을 추출하세요
정답
SELECT
SUBSTR(email, INSTR(email, '@') + 1) AS domain,
COUNT(*) AS cnt
FROM customers
GROUP BY domain
ORDER BY cnt DESC
LIMIT 10;
실행 결과 (1행)
| domain | cnt |
|---|---|
| testmail.kr | 5230 |
12. 배송지를 2개 이상 등록한 고객의 이름과 배송지 수를 조회하세요.
배송지를 2개 이상 등록한 고객의 이름과 배송지 수를 조회하세요.
힌트 1: customers와 customer_addresses를 JOIN하고, HAVING COUNT(...) >= 2로 필터링
정답
SELECT
c.name,
COUNT(a.id) AS address_count
FROM customers AS c
INNER JOIN customer_addresses AS a ON c.id = a.customer_id
GROUP BY c.id, c.name
HAVING COUNT(a.id) >= 2
ORDER BY address_count DESC
LIMIT 20;
실행 결과 (총 20행 중 상위 7행)
| name | address_count |
|---|---|
| 정준호 | 3 |
| 김경수 | 3 |
| 김민재 | 3 |
| 장준서 | 3 |
| 윤유진 | 3 |
| 박수빈 | 3 |
| 윤명자 | 3 |
13. 2024년에 가입한 고객 중 등급이 VIP 또는 GOLD인 고객의 이름, 가입일, 등급을 조회하세요.
2024년에 가입한 고객 중 등급이 VIP 또는 GOLD인 고객의 이름, 가입일, 등급을 조회하세요.
힌트 1: LIKE '2024%'로 연도 필터링, IN ('VIP', 'GOLD')로 등급 필터링. 두 조건을 AND로 결합
정답
SELECT name, created_at, grade
FROM customers
WHERE created_at LIKE '2024%'
AND grade IN ('VIP', 'GOLD')
ORDER BY created_at;
실행 결과 (총 140행 중 상위 7행)
| name | created_at | grade |
|---|---|---|
| 이도현 | 2024-01-05 14:23:16 | GOLD |
| 성영환 | 2024-01-07 05:39:40 | GOLD |
| 박민재 | 2024-01-07 12:17:28 | VIP |
| 이중수 | 2024-01-07 19:33:43 | VIP |
| 이정숙 | 2024-01-11 06:40:22 | GOLD |
| 임정순 | 2024-01-16 06:45:22 | GOLD |
| 이종수 | 2024-01-23 05:29:21 | VIP |
14. 2024년 월별 가입자 수를 구하세요.
2024년 월별 가입자 수를 구하세요.
힌트 1: SUBSTR(created_at, 1, 7)로 'YYYY-MM' 형태를 추출하고 GROUP BY로 월별 집계
정답
SELECT
SUBSTR(created_at, 1, 7) AS month,
COUNT(*) AS signups
FROM customers
WHERE created_at LIKE '2024%'
GROUP BY SUBSTR(created_at, 1, 7)
ORDER BY month;
실행 결과 (총 12행 중 상위 7행)
| month | signups |
|---|---|
| 2024-01 | 52 |
| 2024-02 | 48 |
| 2024-03 | 71 |
| 2024-04 | 53 |
| 2024-05 | 43 |
| 2024-06 | 68 |
| 2024-07 | 62 |
15. 적립금을 0, 1~1000, 1001~5000, 5001~10000, 10001 이상으로 나누어 각 구간의
적립금을 0, 1~1000, 1001~5000, 5001~10000, 10001 이상으로 나누어 각 구간의 고객 수를 구하세요.
힌트 1: CASE WHEN으로 적립금 구간을 분류한 뒤 GROUP BY와 COUNT(*)로 집계. ORDER BY MIN(point_balance)로 구간 순 정렬
정답
SELECT
CASE
WHEN point_balance = 0 THEN '없음'
WHEN point_balance <= 1000 THEN '1~1,000'
WHEN point_balance <= 5000 THEN '1,001~5,000'
WHEN point_balance <= 10000 THEN '5,001~10,000'
ELSE '10,001 이상'
END AS point_range,
COUNT(*) AS cnt
FROM customers
GROUP BY point_range
ORDER BY MIN(point_balance);
실행 결과 (5행)
| point_range | cnt |
|---|---|
| 없음 | 2506 |
| 1~1,000 | 137 |
| 1,001~5,000 | 344 |
| 5,001~10,000 | 148 |
| 10,001 이상 | 2095 |
16. 다중 태그 상품 (GROUP BY HAVING)
태그가 3개 이상 달린 상품을 찾으세요. 상품명과 태그 목록(쉼표 구분)을 보여주세요.
힌트 1: product_tags GROUP BY product_id. HAVING COUNT(*) >= 3. GROUP_CONCAT(t.name, ', ')로 태그 목록 표시.
정답
SELECT
p.name AS product_name,
p.brand,
COUNT(pt.tag_id) AS tag_count,
GROUP_CONCAT(t.name, ', ') AS tags
FROM product_tags AS pt
INNER JOIN products AS p ON pt.product_id = p.id
INNER JOIN tags AS t ON pt.tag_id = t.id
GROUP BY p.id, p.name, p.brand
HAVING COUNT(pt.tag_id) >= 3
ORDER BY tag_count DESC
LIMIT 20;
SELECT
p.name AS product_name,
p.brand,
COUNT(pt.tag_id) AS tag_count,
GROUP_CONCAT(t.name SEPARATOR ', ') AS tags
FROM product_tags AS pt
INNER JOIN products AS p ON pt.product_id = p.id
INNER JOIN tags AS t ON pt.tag_id = t.id
GROUP BY p.id, p.name, p.brand
HAVING COUNT(pt.tag_id) >= 3
ORDER BY tag_count DESC
LIMIT 20;
SELECT
p.name AS product_name,
p.brand,
COUNT(pt.tag_id) AS tag_count,
STRING_AGG(t.name, ', ') AS tags
FROM product_tags AS pt
INNER JOIN products AS p ON pt.product_id = p.id
INNER JOIN tags AS t ON pt.tag_id = t.id
GROUP BY p.id, p.name, p.brand
HAVING COUNT(pt.tag_id) >= 3
ORDER BY tag_count DESC
LIMIT 20;
SELECT
p.name AS product_name,
p.brand,
COUNT(pt.tag_id) AS tag_count,
LISTAGG(t.name, ', ') WITHIN GROUP (ORDER BY t.name) AS tags
FROM product_tags pt
INNER JOIN products p ON pt.product_id = p.id
INNER JOIN tags t ON pt.tag_id = t.id
GROUP BY p.id, p.name, p.brand
HAVING COUNT(pt.tag_id) >= 3
ORDER BY tag_count DESC
FETCH FIRST 20 ROWS ONLY;
SELECT TOP 20
p.name AS product_name,
p.brand,
COUNT(pt.tag_id) AS tag_count,
STRING_AGG(t.name, ', ') AS tags
FROM product_tags AS pt
INNER JOIN products AS p ON pt.product_id = p.id
INNER JOIN tags AS t ON pt.tag_id = t.id
GROUP BY p.id, p.name, p.brand
HAVING COUNT(pt.tag_id) >= 3
ORDER BY tag_count DESC;
실행 결과 (총 20행 중 상위 7행)
| product_name | brand | tag_count | tags |
|---|---|---|---|
| Razer Blade 16 실버 | Razer | 8 | 방수/방진, 에르고노믹, 게이밍, 그래픽디자인, 서버/NAS, 휴대... |
| Razer Blade 18 블랙 | Razer | 8 | USB-C, 터치스크린, 게이밍, 학생용, 서버/NAS, 휴대용, ... |
| MSI GeForce RTX 4070 Ti Super GAMING X | MSI | 7 | 저소음, Wi-Fi 7, 터치스크린, 재택근무, 입문자용, 프리미엄... |
| Razer Blade 18 화이트 | Razer | 7 | QHD, Wi-Fi 6E, 게이밍, 휴대용, 하이엔드, 신제품, 듀얼채널 |
| 로지텍 K580 | 로지텍 | 7 | FHD, 영상편집, 그래픽디자인, 입문자용, 가성비, 프리미엄, 베... |
| SteelSeries Prime Wireless 블랙 | SteelSeries | 7 | RGB 라이팅, USB-C, PCIe 4.0, 게이밍, 입문자용, ... |
| SteelSeries Aerox 5 Wireless 실버 | SteelSeries | 7 | OLED, 고주사율, DDR5, 게이밍, 가성비, 프리미엄, 한정판 |
17. 가입 경로별 고객 수와 비율
고객의 가입 경로(acquisition_channel)별로 고객 수와 비율(%)을 구하세요. NULL은 '미분류'로 표시합니다.
힌트 1: COALESCE(acquisition_channel, '미분류')로 NULL 처리. 비율은 (SELECT COUNT(*) FROM customers)로 전체 수를 구해 계산.
정답
SELECT
COALESCE(acquisition_channel, '미분류') AS channel,
COUNT(*) AS customer_count,
ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM customers), 1) AS pct
FROM customers
GROUP BY COALESCE(acquisition_channel, '미분류')
ORDER BY customer_count DESC;
실행 결과 (5행)
| channel | customer_count | pct |
|---|---|---|
| search_ad | 1543 | 29.50 |
| social | 1425 | 27.20 |
| organic | 1146 | 21.90 |
| referral | 708 | 13.50 |
| direct | 408 | 7.80 |