SQLD 서브쿼리 종류 — 4가지 분류와 실행 차이
한 줄 요약 — 서브쿼리는 위치(SELECT/FROM/WHERE)와 실행 방식(독립 vs 상관)으로 분류한다. 스칼라·인라인뷰·중첩·상관 4종류의 결과 형태와 실행 횟수가 달라서, 한 번 정리해두면 SQLD 서브쿼리 문제는 거의 다 풀린다.
서브쿼리는 SQLD 2과목에서 JOIN 다음으로 자주 나오는 영역이다. 단순히 "쿼리 안의 쿼리"라는 정의로는 시험 문제를 풀 수 없다. 어디에 들어갔는지, 외부 쿼리와 어떻게 상호작용하는지, 몇 번 실행되는지 세 축으로 분류해야 답이 보인다.
서브쿼리의 정의
서브쿼리(Subquery)는 다른 SQL 문장 안에 포함되는 SELECT 문장이다. 메인 쿼리(외부 쿼리)에 데이터를 공급하거나, 조건을 동적으로 만들어주는 역할을 한다. 위치에 따라 반환 형태가 달라지고, 외부 컬럼 참조 여부에 따라 실행 방식이 달라진다.
분류 기준은 두 가지다.
- 위치 기준: SELECT 절(스칼라) · FROM 절(인라인뷰) · WHERE/HAVING 절(중첩)
- 실행 방식 기준: 비상관(외부와 독립, 한 번만 실행) vs 상관(외부 행마다 재실행)
서브쿼리의 4가지 종류
| 종류 | 위치 | 반환 형태 | 실행 횟수 | 대표 사용 예 |
|---|---|---|---|---|
| 스칼라 서브쿼리 | SELECT 절 | 단일 행·단일 컬럼 | 외부 행마다 1회 | 각 행에 부서명 붙이기 |
| 인라인 뷰 | FROM 절 | 결과 집합(가상 테이블) | 1회 | 집계 후 재조인 |
| 중첩 서브쿼리 | WHERE / HAVING | 단일 값 또는 다중 값 | 비상관이면 1회 | IN, EXISTS, ANY/ALL |
| 상관 서브쿼리 | 주로 WHERE/SELECT (FROM은 LATERAL/APPLY 필요) | 외부 행 참조 | 외부 행마다 N회 | 행별 조건 비교 |
스칼라 서브쿼리는 단일 값(1행 1컬럼)을 반드시 반환해야 하고, 0행이면 NULL, 2행 이상이면 에러다. 중첩 서브쿼리는 IN/EXISTS/ANY/ALL/= 같은 연산자에 따라 단일 또는 다중 값 반환이 허용된다.
예제 코드 (실행 가능)
다음 두 테이블로 시작한다.
-- employee
emp_id | emp_name | dept_id | salary
1 | 김민수 | 10 | 4000
2 | 이서연 | 20 | 5000
3 | 박지훈 | 10 | 3500
4 | 최유진 | 30 | 6000
-- department
dept_id | dept_name
10 | 개발팀
20 | 디자인팀
30 | 인사팀스칼라 서브쿼리 — SELECT 절
SELECT e.emp_name,
e.salary,
(SELECT d.dept_name
FROM department d
WHERE d.dept_id = e.dept_id) AS dept_name
FROM employee e;각 사원의 행마다 부서명을 한 번씩 가져온다. 단일 값(1행 1컬럼) 반환이 필수이며, 부서가 없으면 자동으로 NULL이 들어간다.
인라인 뷰 — FROM 절
SELECT d.dept_name, t.avg_salary
FROM department d,
(SELECT dept_id, AVG(salary) AS avg_salary
FROM employee
GROUP BY dept_id) t
WHERE d.dept_id = t.dept_id;서브쿼리의 결과 집합을 가상 테이블 t로 두고 외부에서 다시 조인한다. 집계 결과를 다른 테이블과 합칠 때 자주 쓴다. 표준 표기는 JOIN ... ON을 권장.
중첩 서브쿼리 (비상관) — WHERE 절 IN
SELECT emp_name, salary
FROM employee
WHERE dept_id IN (SELECT dept_id
FROM department
WHERE dept_name IN ('개발팀', '디자인팀'));내부 서브쿼리가 외부 컬럼을 참조하지 않으므로 딱 한 번만 실행되어 (10, 20)을 반환하고, 외부 쿼리가 그 값들로 필터링한다.
상관 서브쿼리 — 외부 행마다 재실행
-- 각 사원의 급여가 같은 부서 평균보다 높은지
SELECT e.emp_name, e.salary
FROM employee e
WHERE e.salary > (SELECT AVG(e2.salary)
FROM employee e2
WHERE e2.dept_id = e.dept_id);내부 서브쿼리가 외부의 e.dept_id를 참조하기 때문에 외부 행마다 다시 실행된다. 4개 사원 행이 있으면 서브쿼리가 4번 실행된다. 그래서 외부 행이 많을수록 비용이 크게 늘어난다.
EXISTS — 존재 여부 체크
SELECT d.dept_name
FROM department d
WHERE EXISTS (SELECT 1
FROM employee e
WHERE e.dept_id = d.dept_id);EXISTS는 서브쿼리에서 행이 1건이라도 있으면 TRUE. 일반적으로 IN보다 성능 면에서 유리한데, 매칭되는 첫 행을 찾으면 즉시 종료하기 때문이다. 단, 옵티마이저가 동등하게 변환하는 경우도 있어 실측이 정답.
SQLD 출제 패턴
SQLD는 50문항 중 데이터 모델링의 이해 10문항 + SQL 기본 및 활용 40문항으로 구성된다. 서브쿼리는 2과목 중후반에 배치되는 단골 출제 영역이다.
자주 나오는 형태는 네 가지.
- 결과 행 수 예측: 주어진 데이터에 IN/EXISTS 서브쿼리를 적용했을 때 결과 행 개수
- 상관 vs 비상관 구분: 서브쿼리 안에 외부 컬럼 참조가 있는지 찾기
- 스칼라 서브쿼리 NULL/에러: 서브쿼리가 0행 또는 2행 이상 반환할 때의 결과
- EXISTS vs IN 변환: 동일 의미의 쿼리를 두 표기로 변환
회차별 정확한 출제 비율은 KDATA가 공개하지 않는다. SQL 전문가 가이드(KDATA 공식 수험서)와 합격 후기 종합으로는 서브쿼리가 보통 회차마다 출제되는 단골 영역으로 알려져 있으나, 공식 회차별 문항 수는 비공개다.
흔한 함정
1. 스칼라 서브쿼리가 2행 이상 반환
스칼라 서브쿼리는 반드시 1행 1컬럼이어야 한다. 2행 이상 반환되면 Oracle에서는 ORA-01427: single-row subquery returns more than one row 에러. SQLD 문제에서 의도적으로 2행이 나오게 만든 보기를 던지고 결과를 묻는 유형이 자주 등장한다.
2. NOT IN과 NULL의 함정
NOT IN은 비교 대상에 NULL이 포함되면 일치 값이 없는 행에 대해 결과가 UNKNOWN으로 평가되어, 예상보다 결과가 줄거나 0행이 될 수 있다 (PostgreSQL 공식 문서 기준).
-- dept_id 컬럼에 NULL이 있는 employee를 IN/NOT IN의 서브쿼리로 쓰면
SELECT *
FROM department
WHERE dept_id NOT IN (SELECT dept_id FROM employee);
-- 일치 값이 없는 행은 NULL 비교로 인해 UNKNOWN이 되어 결과에서 빠진다NOT EXISTS 또는 명시적 NULL 제외(WHERE dept_id IS NOT NULL)로 우회한다. 일치 값이 명확하게 있으면 FALSE로 평가될 수 있으므로 "항상 0행"은 아니지만, 예측 불가능한 결과를 만든다는 점이 핵심.
3. 상관 서브쿼리 비용
상관 서브쿼리는 개념적으로 외부 N행마다 재실행되므로, 외부가 100만 행이면 서브쿼리가 100만 번 평가되는 셈이다. 실제 실행계획에서는 옵티마이저가 조인 등으로 변환해 비용을 줄이는 경우도 있지만, 학습 단계에서는 "행별 재실행"으로 비용을 가정하는 편이 안전하다. 같은 의미를 윈도우 함수나 인라인 뷰 + JOIN으로 바꿀 수 있는지 검토하면 시험에서 응용 문제 풀이에도 도움이 된다.
4. EXISTS 안의 SELECT 컬럼은 무의미
EXISTS (SELECT 1 FROM …)
EXISTS (SELECT * FROM …)
EXISTS (SELECT emp_name FROM …)세 표기 모두 동일하다. EXISTS는 행의 존재 여부만 보고 컬럼 값은 보지 않는다. SQLD 시험에서는 SELECT 1을 정통 표기로 취급하지만, 다른 표기도 정답 처리된다.
SQLmate에서 직접 실행
서브쿼리는 머리로 결과를 그리기 어려운 영역이다. SQLmate iOS·Android 앱의 WASM SQLite 샌드박스에서 위 예제를 직접 실행하면 IN과 EXISTS의 결과가 같은지, 상관 서브쿼리가 외부 컬럼을 어떻게 참조하는지 한 번에 확인할 수 있다. 회차 카운트다운과 함께 홈에서 앱 받기.
FAQ
Q. 스칼라 서브쿼리가 0행을 반환하면 에러인가요?
에러가 아니라 NULL이 됩니다. 2행 이상 반환되면 그때 에러(Oracle: ORA-01427)가 발생합니다. SQLD에서는 이 차이를 묻는 문제가 종종 나오니 "0행 → NULL, 2행 이상 → 에러" 패턴을 외워두면 좋습니다. 실무에서는 0행 반환을 막기 위해 COALESCE로 기본값을 감싸는 경우가 많습니다.
Q. EXISTS가 IN보다 항상 빠른가요?
아닙니다. 옵티마이저가 두 형태를 동등 변환하는 경우가 많아 실제 성능은 같거나 비슷합니다. 다만 서브쿼리 결과가 매우 크고 NULL을 포함할 수 있다면 EXISTS가 안전한 선택이고, NOT IN은 NULL 함정이 있어 NOT EXISTS로 바꾸는 게 권장됩니다. 시험에서는 "성능"보다 결과 집합이 같은지를 묻는 문제가 더 자주 나옵니다.
Q. 인라인 뷰 안에서 ORDER BY를 쓰면 어떻게 되나요?
ANSI 표준에서는 인라인 뷰의 ORDER BY가 외부 쿼리 결과 순서를 보장하지 않습니다. 일부 DBMS는 허용하고, 일부는 무시하거나 에러를 냅니다. 시험에서는 "인라인 뷰 안에 ORDER BY가 있을 때 외부 쿼리 결과 순서"를 묻는 함정 문제가 나옵니다. 외부 정렬이 필요하면 외부 SELECT에서 ORDER BY를 명시해야 안전합니다.
Q. 상관 서브쿼리는 어떻게 알아보나요?
서브쿼리 안에 외부 쿼리의 테이블 별칭이 등장하면 상관 서브쿼리입니다. 예를 들어 외부에서 employee e로 별칭을 줬는데 서브쿼리 안에서 e.dept_id를 참조하면 상관입니다. 시험에서 "이 서브쿼리는 몇 번 실행되는가"를 묻는 문제는 이 참조 여부로 판단합니다.
마무리
서브쿼리는 위치와 참조 여부 두 축으로 분류하면 4가지로 깔끔하게 정리된다. 결과 형태(스칼라 단일 값 vs 다중 값)와 실행 횟수(비상관 1회 vs 상관 N회) 두 차이만 명확히 잡으면 시험 문제 대부분이 빠르게 풀린다. 직접 데이터를 만들어 쿼리를 실행해보는 게 머릿속에 그림을 그리는 가장 빠른 방법이다.
같이 읽으면 좋은 글
- SQLD 조인 정리 — 6가지 JOIN 비교
- SQLD 윈도우 함수 정리 — OVER 절 한 번에 이해하기
- SQLD 기출문제 — 회차별 유형 분포
- SQLD 자격증 — 시험 구조와 응시 가이드
- SQLmate 홈 — 시험일정 · 회차별 카운트다운
출처 / 참고
한국데이터산업진흥원(KDATA) dataq.or.kr · SQL 전문가 가이드(공식 수험서) · ANSI SQL 92/99 표준.
합격을 보장하지 않습니다. 본인 준비 상황에 맞춰 판단해 주세요.