SQL을 쓰다 보면, 하나의 쿼리 안에서 중간 계산 결과를 재활용하거나 복잡한 조건을 깔끔하게 정리해야 하는 순간이 많습니다. 이때 주로 선택하는 방법이 바로 서브쿼리(Subquery)와 공통 테이블 표현식(CTE)입니다. 둘 다 "쿼리 안에 또 다른 쿼리를 쓰는 방식"이지만, 구조와 활용 방법, 그리고 성능 면에서 차이가 존재합니다. 그 차이를 지금부터 한 번 알아보도록 하겠습니다!
서브쿼리(Subquery)란?
서브쿼리는 말 그대로 쿼리 안에 포함된 또 다른 쿼리입니다. 보통 SELECT, FROM, WHERE 절 안에 들어가며, 하나의 쿼리 실행 중에 단 한 번만 평가되는 경우가 많습니다.
📌 서브쿼리 기본 구조
SELECT 컬럼명
FROM 테이블
WHERE 컬럼명 = (
SELECT MAX(컬럼명)
FROM 다른_테이블
);
1.1. 서브쿼리 종류
단일 행 서브쿼리 (Single-row Subquery) : 결과가 한 행만 나오는 서브쿼리입니다.
SELECT first_name, salary
FROM employees
WHERE salary = (
SELECT MAX(salary)
FROM employees
);
→ 급여가 가장 높은 직원 조회
다중 행 서브쿼리 (Multi-row Subquery) : IN, ANY, ALL 과 함께 사용합니다.
SELECT first_name
FROM employees
WHERE department_id IN (
SELECT department_id
FROM departments
WHERE location_id = 1700
);
→ location_id가 1700인 부서의 모든 직원 조회
상관 서브쿼리 (Correlated Subquery) : 외부 쿼리의 값을 참조하며 반복 실행됩니다.
SELECT e1.first_name, e1.salary
FROM employees e1
WHERE salary > (
SELECT AVG(salary)
FROM employees e2
WHERE e1.department_id = e2.department_id
);
→ 각 부서 평균 급여보다 더 많이 받는 직원 조회
1.2. 서브쿼리 장점
* 간단한 조건이나 한 번만 계산하는 값에 적합
* 기존 쿼리를 크게 변경하지 않고도 로직 추가 가능
* 대부분의 SQL 엔진에서 지원
1.3. 서브쿼리 단점
* 복잡해지면 가독성이 떨어짐
* 상관 서브쿼리는 반복 실행으로 인해 성능 저하 가능
* 재사용이 어려움 (같은 서브쿼리를 여러 번 쓰면 매번 실행됨)
CTE(Common Table Expressions)란?
CTE는 WITH 키워드로 선언하는 임시 결과 집합입니다. 이름을 붙여 쿼리 상단에 정의하고, 이후 메인 쿼리에서 마치 테이블처럼 재사용할 수 있습니다.
📌 CTE 기본 구조
WITH cte_name AS (
SELECT 컬럼명
FROM 테이블
WHERE 조건
)
SELECT *
FROM cte_name
WHERE 다른_조건;
2.1. CTE 예시
기본 CTE 사용
WITH HighSalary AS (
SELECT first_name, salary
FROM employees
WHERE salary > 10000
)
SELECT *
FROM HighSalary
WHERE first_name LIKE 'A%';
→ 급여가 10000 이상인 직원 중 이름이 A로 시작하는 사람 조회
다중 CTE
WITH DeptSalary AS (
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
),
AboveAvg AS (
SELECT e.first_name, e.department_id
FROM employees e
JOIN DeptSalary ds
ON e.department_id = ds.department_id
WHERE e.salary > ds.avg_salary
)
SELECT *
FROM AboveAvg;
→ 각 부서 평균보다 급여가 높은 직원 조회
재귀 CTE (Recursive CTE)
WITH RECURSIVE EmployeeHierarchy AS (
SELECT employee_id, manager_id, first_name
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT e.employee_id, e.manager_id, e.first_name
FROM employees e
JOIN EmployeeHierarchy eh
ON e.manager_id = eh.employee_id
)
SELECT *
FROM EmployeeHierarchy;
→ 직원-관리자 계층 구조 조회
2.2. CTE 장점
* 복잡한 쿼리를 단계별로 나누어 가독성 향상
* 같은 CTE 이름으로 여러 번 참조 가능 (일부 DBMS에서는 매번 실행될 수 있음)
* 재귀 쿼리 작성 가능
* 유지보수와 디버깅이 용이
2.3. CTE 단점
* 일부 DBMS에서 CTE는 항상 물리적 테이블처럼 실행되어 성능 손해 가능
* 재사용 시에도 매번 실행되는 경우가 있어 성능상 불리할 수 있음
* 인덱스를 직접 적용할 수 없음
[항목 서브쿼리 CTE]
가독성 | 짧은 쿼리엔 좋음, 복잡하면 난해 | 복잡한 쿼리를 단계별로 깔끔하게 |
재사용성 | 거의 없음 | 동일 CTE 이름을 여러 번 참조 가능 |
성능 | 상관 서브쿼리는 성능 저하 가능 | DB 엔진에 따라 매번 실행되거나 캐시됨 |
지원 기능 | 모든 DB에서 가능 | 일부 DB에서 재귀 지원, 성능 차이 있음 |
적합한 상황 | 간단한 조건, 단일 계산 | 복잡한 로직, 단계별 계산, 재귀 구조 |
실무에서 선택 가이드
* 간단한 조건 → 서브쿼리
예) 최대값, 최소값, 특정 조건 필터링
* 복잡한 다단계 로직 → CTE
예) 여러 집계 결과를 결합, 재귀 계층 구조
* 성능 우선 → 실행 계획 확인
EXPLAIN으로 실제 쿼리 계획을 분석하고, 필요시 인덱스 추가
마무리
서브쿼리와 CTE는 둘 다 "쿼리 안의 쿼리"라는 점에서 비슷하지만, 코드 구조, 재사용성, 성능 면에서 차이가 있습니다. 실무에서는 둘을 대체 관계로만 보지 말고, 상황에 맞게 병행해서 쓰는 것이 가장 좋습니다. 특히 대용량 데이터 환경에서는 실행 계획을 꼭 확인하고, 불필요한 반복 실행을 피하는 것이 핵심입니다.