[SQL] LeetCode 문제풀이 - Filtering & Aggregation Operation Cabin (#586, #596, #1193, #1141, #1327)

Jinyoung Cheon·2026년 4월 14일

LeetCode

목록 보기
2/9

📋 오늘의 문제 목록

#문제난이도핵심 개념
586Customer Placing the Largest Number of OrdersEasyORDER BY + LIMIT, EXPLAIN
596Classes More Than 5 StudentsEasyHAVING vs WHERE
1193Monthly Transactions IMedium조건부 집계, 쿼리 최적화
1141User Activity for the Past 30 Days IEasyCOUNT(DISTINCT), 날짜 범위
1327List the Products Ordered in a PeriodEasySQL 절 실행 순서, WHERE vs HAVING

🟡 586. Customer Placing the Largest Number of Orders

문제 요약

Orders 테이블에서 주문을 가장 많이 한 customer_number를 반환한다.

💡 핵심 개념 - ORDER BY + LIMIT vs 서브쿼리

방법테이블 스캔성능
ORDER BY COUNT(*) DESC LIMIT 11회빠름
HAVING + 서브쿼리2회느림

직관과 반대로 ORDER BY + LIMIT이 더 빠르다. 이유는:

  • 정렬 대상이 전체 row가 아닌 그룹화된 결과(k개)
  • 옵티마이저가 LIMIT 1을 만나면 전체 정렬 없이 최솟값/최댓값 탐색으로 최적화함

💡 EXPLAIN으로 실행 계획 확인

EXPLAIN SELECT ...
컬럼의미주목할 값
type테이블 접근 방식ALL(풀스캔) → 인덱스 없을 때
rows예상 읽을 행 수작을수록 좋음
ExtraUsing 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

📝 배운 점

  • 쿼리 효율성을 고민할 때 "테이블을 몇 번 읽는가" 를 먼저 따질 것
  • ORDER BY + LIMIT 1 패턴은 옵티마이저가 최솟값/최댓값 탐색으로 최적화함
  • EXPLAIN / EXPLAIN ANALYZE로 실제 실행 계획을 확인하는 습관 들이기

🟢 596. Classes More Than 5 Students

문제 요약

Courses 테이블에서 수강생이 5명 이상인 수업명을 반환한다.

💡 핵심 개념 - WHERE vs HAVING

실행 시점집계 함수사용 상황
WHEREGROUP BY 이전❌ 불가개별 행 필터링
HAVINGGROUP 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

📝 배운 점

  • 간단한 집계 조건은 HAVING이 더 깔끔하고 성능도 좋음 (임시 테이블 생성 없음)
  • 집계 결과에 이름을 붙여 재사용해야 할 때는 서브쿼리가 유리

🔴 1193. Monthly Transactions I

문제 요약

월별/국가별로 전체 거래 수/금액, 승인된 거래 수/금액을 반환한다.

💡 핵심 개념 - 조건부 집계 (Conditional Aggregation)

같은 행을 조건에 따라 다르게 집계하는 기법. 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 사용.

❌ 처음 접근의 실수

  • CTE + JOIN 방식으로 approved 데이터를 분리 시도 → JOIN 후 approved 건만 남아 trans_count 오류
  • 조건부 집계로 한 테이블에서 한 번에 해결 가능

✅ 최종 풀이

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

📝 배운 점

  • 조건부 집계는 JOIN 없이 한 테이블에서 다양한 조건의 집계를 동시에 처리
  • LEFT(date, 7)DATE_FORMAT보다 빠름 (단순 문자열 연산)
  • 실무에서는 DB 호환성을 고려해 CASE WHEN을 선호하는 팀도 많음

🟡 1141. User Activity for the Past 30 Days I

문제 요약

2019-07-27 기준 30일간(포함) 일별 활성 유저 수를 반환한다.

💡 핵심 개념 - COUNT(DISTINCT)

테이블에 중복 행이 있을 수 있으므로 같은 날 여러 활동을 한 유저는 1명으로 세야 한다.

COUNT(user_id)          -- ❌ 활동 수를 셈 (중복 포함)
COUNT(DISTINCT user_id) -- ✅ 유저 수를 셈 (중복 제거)

30일 날짜 범위 정확히 계산하기

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'

❌ 처음 접근의 실수

  1. GROUP BY user_idGROUP BY activity_date 로 수정 (날짜별 집계 필요)
  2. INTERVAL 1 MONTHINTERVAL 29 DAY (30일 포함 범위)
  3. 상한 조건 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

📝 배운 점

  • 중복 데이터가 있는 테이블에서 유일한 값을 세려면 COUNT(DISTINCT) 사용
  • "N일간" 날짜 범위는 INTERVAL N-1 DAY (양 끝 포함 시)
  • GROUP BY 대상은 항상 "무엇을 기준으로 집계하는가"를 먼저 생각하기

🟡 1327. List the Products Ordered in a Period

문제 요약

2020년 2월에 100개 이상 주문된 제품의 이름과 수량을 반환한다.

💡 핵심 개념 - SQL 절 실행 순서

FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY

이 순서는 반드시 지켜야 한다. WHERE가 GROUP BY 뒤에 오면 문법 오류.

WHERE vs HAVING 최종 정리

WHEREGROUP BY 이전, 개별 행 필터링, 집계 함수 ❌
HAVINGGROUP BY 이후, 그룹 필터링,   집계 함수 ✅

이 문제에서:

  • LEFT(order_date, 7) = '2020-02' → 개별 행 조건 → WHERE
  • SUM(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

📝 배운 점

  • SQL 절 순서 FROM → WHERE → GROUP BY → HAVING → SELECT 는 반드시 암기
  • 날짜 필터는 WHERE, 집계 조건은 HAVING으로 명확히 구분
  • 두 테이블 모두 매칭되는 데이터만 필요할 때는 INNER JOIN이 의도를 명확하게 표현

🔑 오늘의 핵심 정리

개념한 줄 요약
ORDER BY + LIMIT서브쿼리보다 빠름 - 테이블 스캔 1회, 옵티마이저 최적화
EXPLAIN실행 계획 확인 - type(접근 방식), rows(예상 행 수) 주목
HAVINGGROUP BY 이후 집계 조건 필터링, 집계 함수 사용 가능
조건부 집계CASE WHEN을 SUM 안에 넣어 JOIN 없이 다중 조건 집계
COUNT(DISTINCT)중복 제거 후 카운트
SQL 절 실행 순서FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
profile
데이터를 향해, 한 걸음씩 천천히.

0개의 댓글