| # | 문제 | 난이도 | 핵심 개념 |
|---|---|---|---|
| 586 | Customer Placing the Largest Number of Orders | Easy | ORDER BY + LIMIT, EXPLAIN |
| 596 | Classes More Than 5 Students | Easy | HAVING vs WHERE |
| 1193 | Monthly Transactions I | Medium | 조건부 집계, 쿼리 최적화 |
| 1141 | User Activity for the Past 30 Days I | Easy | COUNT(DISTINCT), 날짜 범위 |
| 1327 | List the Products Ordered in a Period | Easy | SQL 절 실행 순서, WHERE vs HAVING |
Orders 테이블에서 주문을 가장 많이 한 customer_number를 반환한다.
| 방법 | 테이블 스캔 | 성능 |
|---|---|---|
| ORDER BY COUNT(*) DESC LIMIT 1 | 1회 | 빠름 |
| HAVING + 서브쿼리 | 2회 | 느림 |
직관과 반대로 ORDER BY + LIMIT이 더 빠르다. 이유는:
EXPLAIN SELECT ...
| 컬럼 | 의미 | 주목할 값 |
|---|---|---|
type | 테이블 접근 방식 | ALL(풀스캔) → 인덱스 없을 때 |
rows | 예상 읽을 행 수 | 작을수록 좋음 |
Extra | Using filesort, Using temporary | 주의 신호 |
type 성능 순서: const > ref > range > index > ALL(최악)
SELECT customer_number
FROM (
SELECT customer_number, COUNT(order_number) AS order_count
FROM Orders
GROUP BY customer_number
) AS cnt
ORDER BY order_count DESC
LIMIT 1
Courses 테이블에서 수강생이 5명 이상인 수업명을 반환한다.
| 절 | 실행 시점 | 집계 함수 | 사용 상황 |
|---|---|---|---|
WHERE | GROUP BY 이전 | ❌ 불가 | 개별 행 필터링 |
HAVING | GROUP BY 이후 | ✅ 가능 | 그룹 결과 필터링 |
-- 서브쿼리 방식 ✅
SELECT class
FROM (
SELECT class, COUNT(*) AS student_cnt
FROM Courses
GROUP BY class
) AS cnt
WHERE student_cnt >= 5
-- HAVING 방식 ✅ (더 간결)
SELECT class
FROM Courses
GROUP BY class
HAVING COUNT(*) >= 5
서브쿼리가 유리한 경우 → 집계 결과를 여러 조건에서 재사용할 때
SELECT class
FROM Courses
GROUP BY class
HAVING COUNT(*) >= 5
월별/국가별로 전체 거래 수/금액, 승인된 거래 수/금액을 반환한다.
같은 행을 조건에 따라 다르게 집계하는 기법. JOIN 없이 한 번에 해결 가능.
-- 기본 패턴
SUM(CASE WHEN state = 'approved' THEN 1 ELSE 0 END) -- 승인 건수
SUM(CASE WHEN state = 'approved' THEN amount ELSE 0 END) -- 승인 금액
-- MySQL 최적화 버전
SUM(state = 'approved') -- Boolean 평가 (0/1)
SUM(IF(state = 'approved', amount, 0)) -- IF 함수
| 항목 | 느린 버전 | 빠른 버전 | 이유 |
|---|---|---|---|
| 날짜 포맷 | DATE_FORMAT(date, '%Y-%m') | LEFT(date, 7) | 단순 문자열 연산 |
| 조건부 카운트 | SUM(CASE WHEN ... THEN 1 ELSE 0 END) | SUM(state = 'approved') | Boolean 평가 |
| 조건부 합계 | SUM(CASE WHEN ... THEN amount ELSE 0 END) | SUM(IF(..., amount, 0)) | IF가 CASE보다 경량 |
실제 결과: 3818ms (하위 5%) → 565ms (상위 40%) 개선
실무 주의:
SUM(state = 'approved')는 MySQL 전용 문법. 타 DB 호환이 필요하면 CASE WHEN 사용.
SELECT
LEFT(trans_date, 7) AS month,
country,
COUNT(*) AS trans_count,
SUM(amount) AS trans_total_amount,
SUM(state = 'approved') AS approved_count,
SUM(IF(state = 'approved', amount, 0)) AS approved_total_amount
FROM Transactions
GROUP BY month, country
LEFT(date, 7)가 DATE_FORMAT보다 빠름 (단순 문자열 연산)2019-07-27 기준 30일간(포함) 일별 활성 유저 수를 반환한다.
테이블에 중복 행이 있을 수 있으므로 같은 날 여러 활동을 한 유저는 1명으로 세야 한다.
COUNT(user_id) -- ❌ 활동 수를 셈 (중복 포함)
COUNT(DISTINCT user_id) -- ✅ 유저 수를 셈 (중복 제거)
2019-07-27 기준 30일 → 2019-06-28 ~ 2019-07-27 (양 끝 포함)
= INTERVAL 29 DAY (30일이 아님!)
-- ❌ INTERVAL 1 MONTH = 2019-06-27~ → 31일
WHERE activity_date >= DATE_SUB('2019-07-27', INTERVAL 1 MONTH)
-- ✅ 정확히 30일
WHERE activity_date >= DATE_SUB('2019-07-27', INTERVAL 29 DAY)
AND activity_date <= '2019-07-27'
GROUP BY user_id → GROUP BY activity_date 로 수정 (날짜별 집계 필요)INTERVAL 1 MONTH → INTERVAL 29 DAY (30일 포함 범위)AND activity_date <= '2019-07-27' 누락SELECT activity_date AS day,
COUNT(DISTINCT user_id) AS active_users
FROM Activity
WHERE activity_date >= DATE_SUB('2019-07-27', INTERVAL 29 DAY)
AND activity_date <= '2019-07-27'
GROUP BY activity_date
2020년 2월에 100개 이상 주문된 제품의 이름과 수량을 반환한다.
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
이 순서는 반드시 지켜야 한다. WHERE가 GROUP BY 뒤에 오면 문법 오류.
WHERE → GROUP BY 이전, 개별 행 필터링, 집계 함수 ❌
HAVING → GROUP BY 이후, 그룹 필터링, 집계 함수 ✅
이 문제에서:
LEFT(order_date, 7) = '2020-02' → 개별 행 조건 → WHERESUM(unit) >= 100 → 집계 후 조건 → HAVING-- ❌ 오류 쿼리
GROUP BY product_name
WHERE LEFT(o.order_date, 7) = '2020-02' -- WHERE가 GROUP BY 뒤에 위치
AND SUM(unit) >= 100 -- 집계 함수를 WHERE에 사용
SELECT p.product_name, SUM(o.unit) AS unit
FROM Products p
JOIN Orders o ON p.product_id = o.product_id
WHERE LEFT(o.order_date, 7) = '2020-02'
GROUP BY p.product_name
HAVING SUM(o.unit) >= 100
| 개념 | 한 줄 요약 |
|---|---|
| ORDER BY + LIMIT | 서브쿼리보다 빠름 - 테이블 스캔 1회, 옵티마이저 최적화 |
| EXPLAIN | 실행 계획 확인 - type(접근 방식), rows(예상 행 수) 주목 |
| HAVING | GROUP BY 이후 집계 조건 필터링, 집계 함수 사용 가능 |
| 조건부 집계 | CASE WHEN을 SUM 안에 넣어 JOIN 없이 다중 조건 집계 |
| COUNT(DISTINCT) | 중복 제거 후 카운트 |
| SQL 절 실행 순서 | FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY |