[SQL] LeetCode 문제풀이 Grouping & Aggregation (#1280, #1045, #608, #1341)

Jinyoung Cheon·2026년 4월 16일

LeetCode

목록 보기
3/9

📋 오늘의 문제 목록

#문제난이도핵심 개념
1280Students and ExaminationsEasyCROSS JOIN, LEFT JOIN, COUNT(DISTINCT)
1045Customers Who Bought All ProductsMediumHAVING COUNT(DISTINCT) = 전체 COUNT
608Tree NodeMediumCASE WHEN, IN + 서브쿼리, NULL 체크
1341Movie RatingsMediumUNION ALL, 다중 정렬, AVG

🟢 1280. Students and Examinations

문제 요약

모든 학생이 모든 과목 시험에 몇 번 참석했는지 반환한다. 한 번도 안 본 조합도 0으로 출력해야 한다.

💡 핵심 개념 - CROSS JOIN

두 테이블의 모든 조합을 생성하는 JOIN. 조건(ON) 없이 사용한다.

-- Students 2명 × Subjects 3개 = 6가지 조합 생성
SELECT s.student_id, s.student_name, sub.subject_name
FROM Students s
CROSS JOIN Subjects sub

시험을 안 본 조합도 0으로 출력해야 하므로:

  • CROSS JOIN → 모든 학생-과목 조합 생성
  • LEFT JOIN → Examinations와 연결, 없으면 NULL
  • COUNT(e.student_id) → NULL은 카운트 안 함 → 자동으로 0 반환
COUNT(e.student_id)  -- ✅ NULL 제외 → 0 반환
COUNT(*)             -- ❌ NULL도 카운트 → 1 반환

✅ 최종 풀이

SELECT
    s.student_id,
    s.student_name,
    sub.subject_name,
    COUNT(e.student_id) AS attended_exams
FROM Students s
CROSS JOIN Subjects sub
LEFT JOIN Examinations e
    ON s.student_id = e.student_id
    AND sub.subject_name = e.subject_name
GROUP BY s.student_id, s.student_name, sub.subject_name
ORDER BY s.student_id, sub.subject_name

📝 배운 점

  • CROSS JOIN : 모든 조합 생성, 0을 포함한 집계 문제에서 활용
  • LEFT JOIN 후 COUNT 할 때 COUNT(*) 대신 COUNT(특정 컬럼) 사용해야 NULL 제외됨
  • "모든 조합을 보여줘야 할 때" → CROSS JOIN + LEFT JOIN 패턴 기억하기

🟡 1045. Customers Who Bought All Products

문제 요약

Product 테이블의 모든 제품을 구매한 customer_id를 반환한다.

💡 핵심 개념 - HAVING COUNT(DISTINCT) = 전체 COUNT

특정 고객이 구매한 distinct 제품 수가 전체 제품 수와 같으면 모든 제품을 산 것.

HAVING COUNT(DISTINCT p.product_key) = (SELECT COUNT(*) FROM Product)

처음 접근 (오답)

-- ❌ EXISTS 방식 - 하나라도 일치하면 통과 → "모든 제품" 조건 충족 불가
SELECT DISTINCT c.customer_id
FROM Customer c
WHERE EXISTS (
    SELECT 1 FROM Product p
    WHERE c.product_key = p.product_key
);

EXISTS는 조건을 하나라도 만족하면 TRUE를 반환하므로 "모든 제품"을 검증할 수 없다.

✅ 최종 풀이

SELECT c.customer_id
FROM Customer c
JOIN Product p
ON c.product_key = p.product_key
GROUP BY c.customer_id
HAVING COUNT(DISTINCT p.product_key) = (SELECT COUNT(*) FROM Product)

📝 배운 점

  • "모든 ~를 포함하는" 조건 → HAVING COUNT(DISTINCT) = (SELECT COUNT(*) FROM 전체테이블) 패턴
  • EXISTS는 "하나라도 존재하면" 조건, 전체 포함 여부 검증에는 부적합
  • 중복 구매 가능성이 있으므로 COUNT(DISTINCT)로 중복 제거 필수

🟡 608. Tree Node

문제 요약

Tree 테이블에서 각 노드의 타입을 반환한다.

  • Root : p_id가 NULL (부모 없음)
  • Inner : 자식이 있는 노드 (다른 노드의 p_id로 등장)
  • Leaf : 자식이 없는 노드 (나머지)

💡 핵심 개념 - CASE WHEN + IN 서브쿼리

조건 분기 로직을 CASE WHEN으로, 자식 존재 여부를 IN 서브쿼리로 확인한다.

-- 자식이 있는 노드 = 다른 노드의 p_id 목록에 내 id가 있는 경우
WHEN id IN (SELECT p_id FROM Tree WHERE p_id IS NOT NULL) THEN 'Inner'

주의: WHERE p_id IS NOT NULL을 빠뜨리면 서브쿼리 결과에 NULL이 포함되어
NOT IN 사용 시 전체 결과가 빈값이 될 수 있음.

✅ 최종 풀이

SELECT id,
    CASE
        WHEN p_id IS NULL
            THEN 'Root'
        WHEN id IN (SELECT p_id FROM Tree WHERE p_id IS NOT NULL)
            THEN 'Inner'
        ELSE 'Leaf'
    END AS type
FROM Tree

📝 배운 점

  • CASE WHEN은 위에서 아래로 순서대로 평가 → Root 먼저 걸러야 Inner/Leaf 구분 가능
  • IN 서브쿼리에 NULL이 포함되면 NOT IN이 항상 false → WHERE p_id IS NOT NULL 필수
  • 트리 구조 문제는 "부모 여부(p_id IS NULL)", "자식 여부(id IN p_id 목록)" 두 가지로 분류

🟡 1341. Movie Ratings

문제 요약

두 가지를 반환한다.
1. 가장 많은 영화를 평가한 유저 이름 (동점이면 사전순 앞)
2. 2020년 2월 평균 평점이 가장 높은 영화 제목 (동점이면 사전순 앞)

💡 핵심 개념 - UNION ALL + 다중 정렬

성격이 다른 두 결과를 하나로 합칠 때 UNION ALL을 사용한다.

-- UNION    : 중복 제거 (느림)
-- UNION ALL : 중복 유지 (빠름) ← 두 결과가 애초에 다른 종류이므로 UNION ALL이 적합

동점 처리ORDER BY 기준1 DESC, 기준2 ASC로 우선순위 지정

ORDER BY COUNT(mr.movie_id) DESC, u.name ASC
-- 평가 수 많은 순 → 같으면 이름 사전순

✅ 최종 풀이

-- 가장 많은 영화를 평가한 유저
(
    SELECT u.name AS results
    FROM Users u
    JOIN MovieRating mr ON u.user_id = mr.user_id
    GROUP BY mr.user_id
    ORDER BY COUNT(mr.movie_id) DESC, u.name ASC
    LIMIT 1
)
UNION ALL
-- 2020년 2월 평균 평점이 가장 높은 영화
(
    SELECT m.title AS results
    FROM MovieRating mr
    JOIN Movies m ON mr.movie_id = m.movie_id
    WHERE YEAR(mr.created_at) = 2020
      AND MONTH(mr.created_at) = 02
    GROUP BY mr.movie_id
    ORDER BY AVG(mr.rating) DESC, m.title ASC
    LIMIT 1
)

📝 배운 점

  • UNION ALL : 성격이 다른 두 결과를 합칠 때, 중복 제거 불필요하면 UNION보다 빠름
  • 동점 처리는 ORDER BY 주기준 DESC, 보조기준 ASC 패턴
  • 각 서브쿼리를 괄호로 감싸야 ORDER BY + LIMIT이 각각 적용됨

🔑 오늘의 핵심 정리

개념한 줄 요약
CROSS JOIN두 테이블의 모든 조합 생성, 0 포함 집계 문제에 활용
COUNT(컬럼) vs COUNT(*)LEFT JOIN 후 NULL 제외 카운트는 COUNT(컬럼)
HAVING COUNT(DISTINCT) = 전체수"모든 ~를 포함하는" 조건 처리 패턴
CASE WHEN 순서위에서 아래로 평가, 좁은 조건 먼저 작성
IN 서브쿼리 + NULL서브쿼리에 NULL 포함 시 NOT IN 오작동 → IS NOT NULL 필수
UNION ALL성격 다른 두 결과 합치기, UNION보다 빠름
profile
데이터를 향해, 한 걸음씩 천천히.

0개의 댓글