ABOUT

성능과 운영 안정성을 함께 끌어올리는 개발자입니다.

92% Positional Error Reduction
79% p95 Latency Improvement
90%+ Long Tasks Reduction

2022.02 · 한국장학재단

우수 멘티

한국장학재단 사회 리더 대학생 멘토링 IT

2022.10 · 동작구청

우수 인재상

동작구청 우수 SW 인재

2025.05 · (주) 그랩

프로그래밍 우수상

(주) 그랩 우수 프로그램 개발

2025.05 · AWSKRUG

AWS한국사용자모임 발표

AI agent 스크립트 튜닝 관련 발표

ComputerScience

Development

Engineering

Trouble Shooting

GUESTBOOK

첫 마음부터
함께 나누는 온기

방명록 작성하러 가기

SUBSCRIBE

최신소식을
편하게 만나보세요.

서브쿼리 (Subquery)

도입

서브쿼리는 “쿼리를 단계적으로 쌓아 올리는 도구”이며, 조건·집계·존재 여부를 정확히 표현하는 핵심 문법이다

SQL을 작성하다 보면 “먼저 A를 구하고 → 그 결과로 B를 걸러야 한다” 같은 단계적 사고가 자주 등장합니다. 이때 가장 직관적으로 문제를 풀게 해주는 문법이 서브쿼리(Subquery)입니다.

하지만 서브쿼리는 위치(WHERE/SELECT/FROM/HAVING)와 형태(스칼라/다중행/테이블), 그리고 상관 여부(비상관/상관)에 따라 성능과 결과가 크게 달라질 수 있습니다. 이 글에서는 “그냥 되는 쿼리”가 아니라, 의도/정확성/성능까지 챙기는 서브쿼리 사용법을 한 번에 정리합니다.

정의

서브쿼리는 “쿼리 안의 쿼리”이며, 반환 형태(1값/여러 행/테이블)와 상관 여부(외부 참조)에 따라 분류된다

서브쿼리(Subquery)는 다른 SQL 문 내부에 포함되는 SELECT 문입니다. 보통 필터 조건을 만들거나, 파생(derived) 데이터를 생성하거나, 존재 여부를 검사할 때 사용합니다.

서브쿼리 분류 기준 1) 반환 형태
  • 스칼라 서브쿼리(Scalar): 1행 1열(단일 값) 반환
  • 다중 행 서브쿼리(Multi-row): 여러 행(대개 1열) 반환 → IN/EXISTS/ANY/ALL 등에 사용
  • 테이블 서브쿼리(Table / Derived table): 여러 열/행 반환 → FROM 절 인라인 뷰 형태
서브쿼리 분류 기준 2) 상관 여부
  • 비상관 서브쿼리(Non-correlated): 내부 쿼리가 외부 컬럼을 참조하지 않음
  • 상관 서브쿼리(Correlated): 내부 쿼리가 외부 컬럼을 참조함 → 외부 행마다 평가될 수 있음
핵심 한 줄
서브쿼리는 “값을 만들고(파생) → 그 값으로 다시 판단(필터/비교)”하기 위한 SQL의 전략입니다.

필요성

서브쿼리는 “조건을 수치/집계로 바꾸는 능력”과 “존재 여부를 안전하게 표현하는 능력”을 제공한다
  • 집계 결과(평균/최대/최소/합계)를 기준으로 필터링할 수 있다. (예: 평균보다 큰 값)
  • 존재 여부를 간결하고 안전하게 표현할 수 있다. (EXISTS/NOT EXISTS)
  • 임시 결과를 테이블처럼 만들어 단계적으로 가독성 있게 쿼리를 구성할 수 있다. (FROM 서브쿼리/CTE)
  • UPDATE/DELETE 같은 DML에서도 “어떤 행을 바꿀지”를 정교하게 지정할 수 있다.

JOIN vs Subquery

JOIN은 “결합”, Subquery는 “단계적 판단”, 윈도우/CTE는 “가독성과 재사용”에 강점이 있다
구분 서브쿼리 JOIN 윈도우 함수 / CTE
강점 단계적 조건, 존재 여부, “먼저 구하고 비교” 관계 결합의 정석, 집계/결합 최적화에 강함 행 유지 + 통계 붙이기(윈도우), 구조 분리(CTE)
주의 상관 서브쿼리는 대량 데이터에서 비용 증가 가능 조인 조건 누락 시 곱집합/중복 폭증 DB별 지원/최적화 방식 차이 존재
추천 상황 존재/부재 검사(EXISTS), 단일 값 비교(AVG 등) 가져올 컬럼이 많고 결합이 명확할 때 “그룹 평균보다 큰 행”, “상위 N개” 같은 분석
실무 감각
“서브쿼리 vs JOIN 중 무엇이 더 빠른가”는 정답이 고정돼 있지 않습니다. 의도를 가장 명확히 표현하되, EXPLAIN(실행 계획)으로 실제 실행 방식과 인덱스 사용을 확인하는 습관이 가장 안전합니다.

기본 사용법

WHERE/SELECT/FROM/HAVING/DML에서 서브쿼리는 “값 만들기 → 비교/필터” 형태로 쓰인다
1) WHERE: 집계 결과로 비교(비상관)
-- 평균 급여보다 높은 직원
SELECT e.*
FROM employees e
WHERE e.salary > (SELECT AVG(salary) FROM employees);
2) SELECT: 스칼라 서브쿼리(반드시 1값)
-- 각 부서별 직원 수를 열로 붙이기(스칼라)
SELECT d.dept_id,
       d.dept_name,
       (SELECT COUNT(*)
        FROM employees e
        WHERE e.dept_id = d.dept_id) AS emp_cnt
FROM departments d;
3) FROM: 인라인 뷰(derived table)로 단계 분리
-- 부서별 평균을 만든 뒤 조건 적용
SELECT t.dept_id, t.avg_salary
FROM (
  SELECT dept_id, AVG(salary) AS avg_salary
  FROM employees
  GROUP BY dept_id
) t
WHERE t.avg_salary >= 7000;
4) HAVING: 그룹 조건에 서브쿼리 사용
-- 전체 평균보다 부서 평균이 높은 부서
SELECT dept_id, AVG(salary) AS dept_avg
FROM employees
GROUP BY dept_id
HAVING AVG(salary) > (SELECT AVG(salary) FROM employees);
5) UPDATE: “바꿀 대상”을 서브쿼리로 지정
-- 부서 평균이 전체 평균보다 높은 부서의 직원 급여 5% 인상
UPDATE employees e
SET e.salary = e.salary * 1.05
WHERE e.dept_id IN (
  SELECT dept_id
  FROM employees
  GROUP BY dept_id
  HAVING AVG(salary) > (SELECT AVG(salary) FROM employees)
);

서브쿼리 패턴

IN / EXISTS / 상관 서브쿼리 패턴을 이해하면 “정확한 조건”을 짧고 안전하게 만들 수 있다

실무에서 가장 많이 쓰는 서브쿼리 패턴은 사실 몇 가지로 압축됩니다. 아래 패턴을 익히면 “뭘로 써야 하는지”가 훨씬 빨리 결정됩니다.

1) IN: “목록에 포함되는가?”
-- 주문을 한 번이라도 한 고객
SELECT c.*
FROM customers c
WHERE c.customer_id IN (
  SELECT o.customer_id
  FROM orders o
);
2) EXISTS: “조건을 만족하는 행이 존재하는가?”
-- 존재 여부만 중요할 때(상관 서브쿼리로 표현)
SELECT c.*
FROM customers c
WHERE EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.customer_id = c.customer_id
);
TIP
EXISTS는 “있냐/없냐”만 판단하면 되기 때문에, DB가 조건을 만족하는 행을 찾는 순간 더 이상 읽지 않는 방식으로 최적화될 수 있습니다. (실제 동작은 실행 계획에서 확인)
3) 상관 서브쿼리: “그룹 평균보다 큰 행” 같은 비교
-- 각 부서의 평균 급여보다 높은 직원
SELECT e.*
FROM employees e
WHERE e.salary > (
  SELECT AVG(e2.salary)
  FROM employees e2
  WHERE e2.dept_id = e.dept_id
);

상관 서브쿼리는 의도가 직관적이지만, 외부 행마다 평가될 수 있어 대량 데이터에서 비용이 커질 수 있습니다. 아래처럼 집계를 먼저 만들고 JOIN하거나, 가능하면 윈도우 함수로 바꾸는 패턴도 함께 기억해두면 좋습니다.

-- (대체) 부서 평균을 먼저 만든 뒤 JOIN
SELECT e.*
FROM employees e
JOIN (
  SELECT dept_id, AVG(salary) AS dept_avg
  FROM employees
  GROUP BY dept_id
) a ON a.dept_id = e.dept_id
WHERE e.salary > a.dept_avg;

null 처리

NOT IN은 서브쿼리 결과에 NULL이 섞이면 논리적으로 “모두 UNKNOWN”이 될 수 있어, NOT EXISTS가 더 안전하다

서브쿼리에서 가장 유명한 함정은 NOT IN + NULL 입니다. 서브쿼리 결과에 NULL이 포함되면, NOT IN은 의도와 다르게 동작할 수 있습니다. (SQL의 3값 논리: TRUE / FALSE / UNKNOWN)

BAD: NOT IN + NULL 함정
-- 의도: “주문이 없는 고객”
-- 위험: orders.customer_id에 NULL이 존재하면 결과가 비정상적일 수 있음
SELECT c.*
FROM customers c
WHERE c.customer_id NOT IN (
  SELECT o.customer_id
  FROM orders o
);
GOOD: NOT EXISTS로 안전하게 표현(안티 조인)
SELECT c.*
FROM customers c
WHERE NOT EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.customer_id = c.customer_id
);
핵심
“없음”을 찾는 쿼리는 NOT EXISTS로 표현하면 NULL 이슈를 피하기 쉽고 의도가 명확합니다.

주의점

스칼라 서브쿼리는 “1값”을 보장해야 하고, 상관 서브쿼리는 데이터가 많을수록 비용이 커질 수 있다
1) 스칼라 서브쿼리: 1행 1열 보장
-- BAD: 여러 행이 나오면 에러(또는 DB에 따라 예측 불가한 동작)
SELECT e.*
FROM employees e
WHERE e.dept_id = (SELECT dept_id FROM departments);

해결은 단순합니다. “여러 개일 수 있다”면 IN을 쓰거나, 서브쿼리 자체가 단일 행이 되도록 조건을 제한해야 합니다.

2) FROM 서브쿼리: alias(별칭) 누락 주의
-- DB에 따라 alias가 없으면 오류가 발생할 수 있음
SELECT *
FROM (
  SELECT dept_id, AVG(salary) AS avg_salary
  FROM employees
  GROUP BY dept_id
) t; -- alias 필수(권장)

자주 하는 실수

서브쿼리 실수는 “1값 보장 실패”, “NOT IN + NULL”, “상관 서브쿼리 남용”, “조인 대체 가능성 미검토”에서 많이 터진다
실수 1) 스칼라 서브쿼리인데 여러 행 반환

SELECT/WHERE에서 = (subquery) 형태를 쓰면 “반드시 1값”이어야 합니다. 현실에서는 데이터가 늘면서 단일 값이 깨지는 순간이 자주 생깁니다. (예: 중복 데이터)

실수 2) NOT IN 사용 시 NULL 고려 누락

서브쿼리 결과에 NULL이 포함될 가능성이 있다면, “부재” 판정은 NOT EXISTS가 더 안전합니다.

실수 3) 상관 서브쿼리를 습관처럼 남용

상관 서브쿼리는 “읽기 쉬운 대신” 대량 데이터에서 비용이 커질 수 있습니다. 같은 목적이라면 집계 결과를 먼저 만들고 JOIN하는 방식이 더 안정적인 경우가 많습니다.

성능 팁

성능은 “실행 계획과 인덱스”가 좌우하며, EXISTS/사전 집계/윈도우 함수가 대표적인 개선 카드다
  • 존재 여부만 필요하면 EXISTS / NOT EXISTS를 우선 고려한다.
  • 상관 서브쿼리로 반복 계산되는 집계가 있다면, 사전 집계(derived table/CTE) 후 JOIN을 고려한다.
  • “행을 유지한 채 그룹 통계를 붙이는” 요구라면 윈도우 함수가 더 간결하고 빠를 수 있다.
  • 서브쿼리/조인의 키 컬럼에 인덱스가 있는지 확인한다. (특히 EXISTS의 상관 조건)
  • DB마다 최적화가 다르므로 EXPLAIN으로 실제 계획(스캔 방식, 조인 방식, 인덱스 사용)을 확인한다.

요약

서브쿼리는 “단계적 판단”을 가장 자연스럽게 표현하는 도구이며, NULL/상관 비용/1값 보장만 챙기면 매우 강력해진다
  • ✅ 서브쿼리는 반환 형태(스칼라/다중행/테이블)와 상관 여부(비상관/상관)로 나뉜다.
  • ✅ 스칼라 서브쿼리는 반드시 1행 1열을 보장해야 한다.
  • ✅ “존재/부재”는 EXISTS/NOT EXISTS가 의도 명확 + NULL 이슈에 안전하다.
  • ✅ 상관 서브쿼리는 편하지만 대량 데이터에서 비용이 커질 수 있어 사전 집계 + JOIN/윈도우 함수로 대체를 검토한다.
  • ✅ 최종 판단은 실행 계획(EXPLAIN)과 인덱스 확인으로 한다.
728x90