-
[Database] SQL 서브쿼리(Subquery) 정리: 개념, 위치별 활용업무 자동화/Database 2025. 11. 15. 07:02

1. 서브쿼리 정의 및 분류 기준
1-1. 정의
- 서브쿼리(Subquery)
다른 SQL문 안에 중첩된 SELECT 문
→ “먼저 이 쿼리로 집합을 만들고, 그 결과를 밖에서 다시 사용”하는 구조 - 주로 사용되는 위치
- SELECT 절: 스칼라 서브쿼리 (값 1개)
- FROM 절: 인라인 뷰 / 파생 테이블
- WHERE / HAVING 절: 필터링 조건 (IN, EXISTS, =, >, ANY, ALL 등)
1-2. 분류 기준
- 상관 vs 비상관
- 비상관 서브쿼리:
안쪽 쿼리가 바깥 쿼리와 독립적. 먼저 한 번 실행 후 결과를 재사용 - 상관 서브쿼리:
안쪽 쿼리가 바깥 쿼리의 컬럼을 참조 → 바깥 행마다 반복 실행되는 형태
- 비상관 서브쿼리:
- 반환 형태
- 단일 행(single row): 값 1개 (=, >, < 등 비교에 사용)
- 다중 행(multi row): 여러 행 (IN, EXISTS, ANY, ALL 등과 함께 사용)
- 단일 컬럼 vs 다중 컬럼:
- 단일 컬럼: IN (SELECT col)
- 다중 컬럼: (col1, col2) IN (SELECT c1, c2 …)
2. 위치별 서브쿼리 활용 패턴
2-1. SELECT 절: 스칼라 서브쿼리
- 특징: 행마다 값 1개를 계산해서 컬럼처럼 붙이고 싶을 때 사용
-- 각 부서의 평균 급여를 함께 표시
SELECT e.emp_id,
e.emp_name,
e.salary,
(SELECT AVG(salary)
FROM emp e2
WHERE e2.dept_id = e.dept_id) AS dept_avg_salary
FROM emp e;- 장점: 간단하고 직관적
- 단점: 상관 서브쿼리 형태가 되기 쉬워서 부서 행 수 × 직원 수 만큼 반복 실행될 수 있음
2-2. FROM 절: 인라인 뷰(파생 테이블)
- 특징: 복잡한 집계/필터를 먼저 한 번에 처리하고, 그 결과를 테이블처럼 JOIN하고 싶을 때.
-- 부서별 평균 급여를 먼저 계산 후, 평균보다 높은 직원만 조회
SELECT e.emp_id, e.emp_name, e.salary, d.dept_avg_salary
FROM emp e
JOIN (
SELECT dept_id, AVG(salary) AS dept_avg_salary
FROM emp
GROUP BY dept_id
) d
ON e.dept_id = d.dept_id
WHERE e.salary > d.dept_avg_salary;- 장점:
- 복잡한 로직을 블록으로 분리 → 가독성·재사용성 좋음
- 집계 후 JOIN으로 바깥은 비교만 수행 → 상관 서브쿼리보다 성능 유리한 경우 많음
2-3. WHERE / HAVING 절: 필터용 서브쿼리
(1) IN + 서브쿼리
-- 주문이 존재하는 고객만
SELECT *
FROM customer
WHERE customer_id IN (
SELECT DISTINCT customer_id
FROM orders
);- 다중 행 반환 서브쿼리에 주로 사용
- 간단하지만 NOT IN + NULL 주의 (뒤에서 다시 언급)
(2) EXISTS / NOT EXISTS
-- 주문이 존재하는 고객(행 존재 여부만 확인)
SELECT c.*
FROM customer c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
);-- 주문이 전혀 없는 고객
SELECT c.*
FROM customer c
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
);- 보통 상관 서브쿼리 형태
- “존재 여부” 판단에 최적화된 패턴
(3) 단일 행 서브쿼리 비교
-- 회사 전체 평균 급여보다 높은 직원
SELECT *
FROM emp
WHERE salary > (
SELECT AVG(salary)
FROM emp
);- 서브쿼리는 딱 1행 1컬럼만 반환해야 함. (여러 행이면 오류)
3. 상관 서브쿼리 vs 비상관 서브쿼리
3-1. 비상관 서브쿼리
- 바깥 쿼리와 독립.
- 실행 순서 개념
- 서브쿼리 1번 실행 → 결과 집합 생성
- 바깥 쿼리가 그 결과를 이용해 필터링/비교
SELECT *
FROM emp
WHERE salary > (SELECT AVG(salary) FROM emp);- 장점: 실행 계획 최적화에 유리, 1회 실행
3-2. 상관 서브쿼리
- 서브쿼리 안에서 바깥 쿼리 컬럼을 참조
SELECT e.*
FROM emp e
WHERE salary > (
SELECT AVG(salary)
FROM emp e2
WHERE e2.dept_id = e.dept_id
);- 실행 개념: 바깥 행 한 줄 읽을 때마다 서브쿼리 다시 실행
- 장점: “행별 조건” 같은 유연한 필터링 가능
- 단점: 데이터량이 크면 성능 이슈 → 가능하면 인라인 뷰 + JOIN으로 대체 검토
4. IN vs EXISTS vs JOIN 간 차이
4-1. 비교
구분IN + 서브쿼리EXISTS / NOT EXISTSJOIN주 용도 특정 값이 집합에 포함되는지 확인 행 존재 여부 판단 두 테이블의 데이터 결합 반환 형태 바깥 테이블 컬럼 바깥 테이블 컬럼 양쪽 컬럼 모두(필요시 필터링) 성능 관점 집합 크기·인덱스에 따라 달라짐 조건 충족하는 행 발견 즉시 종료 가능 조인 방식(NL/Hash/Merge)에 영향 받음 특징 간단하고 직관적 “있냐/없냐”에 특화, 보안·제약조건 검증에 자주 레포트, 조회용 SELECT의 기본 패턴 4-2. NOT IN + NULL 주의
-- 이렇게 작성하면 위험
SELECT *
FROM customer
WHERE customer_id NOT IN (
SELECT customer_id
FROM orders
);- orders.customer_id에 NULL이 포함되어 있으면,
NOT IN 비교가 전부 **UNKNOWN(=FALSE 취급)**로 되어 결과가 비어버릴 수 있음 - 안전한 패턴
SELECT *
FROM customer c
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
);5. 서브쿼리 실전 패턴 모음
5-1. “부서 평균보다 높은 급여” (단일 행·상관 서브쿼리)
SELECT e.emp_id, e.emp_name, e.dept_id, e.salary
FROM emp e
WHERE e.salary > (
SELECT AVG(salary)
FROM emp e2
WHERE e2.dept_id = e.dept_id
);- “같은 부서 안에서 상대적으로 높은 급여” 케이스
5-2. “마지막 주문일 기준 최신 주문만” (NOT EXISTS 패턴)
SELECT o.*
FROM orders o
WHERE NOT EXISTS (
SELECT 1
FROM orders o2
WHERE o2.customer_id = o.customer_id
AND o2.order_date > o.order_date
);- 각 고객별로 가장 최근 주문 1건만 가져오는 대표 패턴
- “동일 키에 대해 더 나은(더 큰) 값이 존재하지 않는 행만 선택”할 때 NOT EXISTS 자주 사용
5-3. “집계 결과와 비교” (HAVING 서브쿼리)
-- 전체 평균 주문 금액보다 부서 평균이 높은 부서만
SELECT dept_id, AVG(amount) AS dept_avg
FROM orders
GROUP BY dept_id
HAVING AVG(amount) > (
SELECT AVG(amount)
FROM orders
);- HAVING 절에서 집계 결과를 서브쿼리와 비교하는 패턴
5-4. 인라인 뷰로 Top-N per Group 흉내내기
(실제 서비스에서는 Window 함수를 쓰는게 일반적이지만, 서브쿼리로도 가능)
-- 각 부서별 급여 상위 1명
SELECT e.*
FROM emp e
WHERE 1 = (
SELECT COUNT(*)
FROM emp e2
WHERE e2.dept_id = e.dept_id
AND e2.salary >= e.salary
);- 같은 부서 안에서 자기보다 급여가 크거나 같은 사람 수가 1명(=본인)인 경우만 선택
- 데이터 많으면 비효율 → 실제 운영에서는 ROW_NUMBER() OVER(PARTITION BY … ORDER BY …)가 훨씬 낫다.
'업무 자동화 > Database' 카테고리의 다른 글
[Database] SQL WHERE 구문 정리: 정의와 활용 예시 (0) 2025.11.18 [Database] SELECT 문 실행 순서 (0) 2025.11.16 [Database] SQL JOIN 총정리: INNER/OUTER/SEMI/ANTI/CROSS, NATURAL/USING, UNION (0) 2025.11.14 [Database] SQL 카테시안 곱: 원인, 증상, 재현 예시, 예방 체크리스트 (0) 2025.11.13 [Database] SQL의 정규표현식: 정의, 핵심 패턴 표, DBMS별 사용법, 이메일·전화번호·주소 예시 (0) 2025.11.12 - 서브쿼리(Subquery)