프로그래머스 SQL - 64번 (서브쿼리, 윈도우함수)

yeyeyeyeye·2025년 8월 13일

SQL 서브쿼리(CTE) 사용 판단 가이드

SQL에서 “서브쿼리를 써야 하나?”는 초보~중급 개발자가 가장 헷갈리는 포인트. 아래 패턴별 가이드를 참고하면 훨씬 쉽게 판단할 수 있다.


1. 집계 조건으로 원본을 거를 때 (세미조인 패턴)

집계 결과를 기반으로 원본에서 조건에 맞는 행만 가져오고 싶을 때.

-- 예: 공간을 2개 이상 등록한 host의 모든 공간
SELECT p.*
FROM places p
JOIN (
  SELECT host_id
  FROM places
  GROUP BY host_id
  HAVING COUNT(*) >= 2
) heavy ON p.host_id = heavy.host_id;
  • 대안: WHERE IN (SELECT …) 또는 EXISTS.

2. 그룹 내 최댓값/최신값과 원본 매칭

“카테고리별 favorites 최댓값인 행만” 같은 문제.

SELECT r.*
FROM rest_info r
JOIN (
  SELECT food_type, MAX(favorites) AS max_fav
  FROM rest_info
  GROUP BY food_type
) m ON r.food_type = m.food_type
   AND r.favorites = m.max_fav;
  • 대안: ROW_NUMBER() 또는 RANK()로 순위 필터.

3. 존재 여부만 확인할 때

해당 데이터가 있는지/없는지만 확인.

-- 주문 기록이 있는 유저
SELECT u.*
FROM users u
WHERE EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.user_id = u.user_id
);
  • 존재성: EXISTS / NOT EXISTS
  • 포함성: IN / NOT IN

4. 단일 값(스칼라) 컬럼처럼 붙이기

서브쿼리 결과를 컬럼으로 활용.

SELECT r.*,
       (SELECT AVG(amount)
        FROM orders o
        WHERE o.r_id = r.id) AS avg_amount
FROM restaurant r;
  • 주의: 스칼라 서브쿼리는 성능 이슈 가능 → 조인/CTE 고려.

5. 중간 결과를 단계적으로 쪼갤 때

복잡한 로직을 논리 단위로 나눔 → 가독성/디버깅 용이.

WITH base AS (...),
     cleaned AS (SELECT ... FROM base WHERE ...),
     scored AS (
       SELECT ..., DENSE_RANK() OVER (...) AS rnk
       FROM cleaned
     )
SELECT * FROM scored WHERE rnk = 1;

6. 안티조인(매칭이 없는 행만)

해당 테이블에 매칭 데이터가 없는 행만 찾기.

SELECT r.*
FROM restaurant r
WHERE NOT EXISTS (
  SELECT 1
  FROM reviews v
  WHERE v.r_id = r.id
);

7. 셀프조인 전, 특정 기준 데이터만 추출

같은 테이블 간 비교 전 필터링.

WITH first_last AS (
  SELECT user_id,
         MIN(order_date) AS first_dt,
         MAX(order_date) AS last_dt
  FROM orders
  GROUP BY user_id
)
SELECT o1.user_id, o1.amount AS first_amt, o2.amount AS last_amt
FROM first_last fl
JOIN orders o1 ON o1.user_id=fl.user_id AND o1.order_date=fl.first_dt
JOIN orders o2 ON o2.user_id=fl.user_id AND o2.order_date=fl.last_dt;

💡 서브쿼리 대신 윈도우 함수가 더 좋은 경우

그룹 내 통계/순위를 계산하고 각 행에 붙인 뒤 필터.

SELECT *
FROM (
  SELECT p.*,
         COUNT(*) OVER (PARTITION BY host_id) AS place_cnt
  FROM places p
) t
WHERE place_cnt >= 2;

PARTITION BY로 그룹 내 집계 → 외부에서 필터.


빠른 체크리스트

존재성만? → EXISTS / NOT EXISTS

집계 조건 필터? → 파생테이블 조인

그룹 내 1등/최신 행? → MAX 조인 or 순위 함수

그룹 내 통계 컬럼화? → 윈도우 함수

단일 값 붙이기? → 스칼라 서브쿼리 or 조인

복잡해서 읽기 어려움? → CTE

매칭 없는 행? → NOT EXISTS

profile
안녕하세요? 데이터분석가 되고 싶어요.

0개의 댓글