| # | 문제 | 난이도 | 핵심 개념 |
|---|---|---|---|
| 1280 | Students and Examinations | Easy | CROSS JOIN, LEFT JOIN, COUNT(DISTINCT) |
| 1045 | Customers Who Bought All Products | Medium | HAVING COUNT(DISTINCT) = 전체 COUNT |
| 608 | Tree Node | Medium | CASE WHEN, IN + 서브쿼리, NULL 체크 |
| 1341 | Movie Ratings | Medium | UNION ALL, 다중 정렬, AVG |
모든 학생이 모든 과목 시험에 몇 번 참석했는지 반환한다. 한 번도 안 본 조합도 0으로 출력해야 한다.
두 테이블의 모든 조합을 생성하는 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으로 출력해야 하므로:
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
COUNT(*) 대신 COUNT(특정 컬럼) 사용해야 NULL 제외됨Product 테이블의 모든 제품을 구매한 customer_id를 반환한다.
특정 고객이 구매한 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 전체테이블) 패턴COUNT(DISTINCT)로 중복 제거 필수Tree 테이블에서 각 노드의 타입을 반환한다.
조건 분기 로직을 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
NOT IN이 항상 false → WHERE p_id IS NOT NULL 필수두 가지를 반환한다.
1. 가장 많은 영화를 평가한 유저 이름 (동점이면 사전순 앞)
2. 2020년 2월 평균 평점이 가장 높은 영화 제목 (동점이면 사전순 앞)
성격이 다른 두 결과를 하나로 합칠 때 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
)
ORDER BY 주기준 DESC, 보조기준 ASC 패턴| 개념 | 한 줄 요약 |
|---|---|
| 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보다 빠름 |