•
서브쿼리의 개념과 실행 순서(안쪽 먼저)를 안다.
•
단일행 서브쿼리를 = > < 로 조건에 사용할 수 있다.
•
다중행 서브쿼리를 IN · ANY · ALL · EXISTS 로 사용할 수 있다.
•
NOT IN 서브쿼리에 NULL이 섞이면 0건이 되는 이유와 해결책을 안다.
1. 서브쿼리란
1) 쿼리 안의 쿼리
•
서브쿼리(Subquery): 하나의 SQL 문 안에 포함된 또 다른 SELECT 문
•
바깥쪽을 메인쿼리, 안쪽을 서브쿼리라 부른다
•
서브쿼리는 괄호 ( ) 로 감싼다
-- "평균 급여보다 많이 받는 사원" — 두 단계를 하나로 합친다
SELECT ENAME, SAL
FROM EMP
WHERE SAL > ( SELECT AVG(SAL) FROM EMP ); -- ← 이 괄호 안이 서브쿼리
-- └────────── 서브쿼리(먼저 실행) ──────────┘
SQL
복사
2) 실행 순서 — 안쪽(서브쿼리)이 먼저 
-- ============================================================
-- 【이해용 예제】 평균 급여보다 많이 받는 사원 ★
-- ============================================================
SELECT ENAME, SAL
FROM EMP
WHERE SAL > ( SELECT AVG(SAL) -- ① 먼저 평균을 계산 (약 2073)
FROM EMP ); -- ② 그 값보다 큰 사원을 조회
-- ENAME SAL
-- ------ -----
-- JONES 2975
-- BLAKE 2850
-- SCOTT 3000
-- KING 5000
-- FORD 3000
-- CLARK 2450 → 6건
--
-- ⭐ 서브쿼리 없이는 이걸 한 번의 SQL로 못 한다.
-- "평균을 눈으로 확인한 뒤 그 숫자를 WHERE에 직접 쓰는" 2단계 작업을 하나로 합친 것.
SQL
복사
서브쿼리에는 원칙적으로 ORDER BY 를 쓸 수 없습니다. 2. 서브쿼리 위치별 분류
위치 | 명칭 | 역할 | 강의 |
WHERE | 중첩 서브쿼리 | 조건에 사용 | 15강 (오늘) |
SELECT | 스칼라 서브쿼리 | 컬럼처럼 값 하나 | 16강 |
FROM | 인라인 뷰 | 임시 테이블처럼 | 16강 |
오늘은 WHERE 절 서브쿼리에 집중합니다. 반환하는 행 수에 따라 단일행 / 다중행으로 나뉩니다.
3. 단일행 서브쿼리 


1) 결과가 1건 → 단일행 연산자
서브쿼리 결과가 딱 1건이면 = > < >= <= <> 로 비교한다.
연산자 | 의미 |
= | 같다 |
> >= | 크다 / 크거나 같다 |
< <= | 작다 / 작거나 같다 |
<> | 다르다 |
-- ============================================================
-- 【시험용 예제】 SCOTT 과 같은 직무를 가진 사원 ★★
-- ============================================================
SELECT ENAME, JOB
FROM EMP
WHERE JOB = ( SELECT JOB -- SCOTT 의 직무를 먼저 알아냄
FROM EMP
WHERE ENAME = 'SCOTT' ) -- → 'ANALYST' (1건)
AND ENAME <> 'SCOTT'; -- 자기 자신은 제외
-- ENAME JOB
-- ----- -------
-- FORD ANALYST → 1건
--
-- ⭐ 서브쿼리가 'ANALYST' 한 건을 반환하므로 = 로 비교할 수 있다.
SQL
복사
-- ============================================================
-- 【시험용 예제】 집계 결과를 조건으로 (11강 회수) ★★
-- "가장 급여가 높은 사원"
-- ============================================================
SELECT ENAME, SAL
FROM EMP
WHERE SAL = ( SELECT MAX(SAL) FROM EMP ); -- 최고 급여(5000)와 같은 사원
-- ENAME SAL
-- ----- -----
-- KING 5000 → 1건
--
-- ⭐ MAX, MIN, AVG 는 항상 1건을 반환하므로 단일행 서브쿼리에 딱 맞는다.
SQL
복사
2)
단일행 자리에 다중행이 오면 오류 

-- ============================================================
-- 【시험용 예제】 단일행 연산자에 여러 건이 오면 오류 ★★
-- ============================================================
-- ❌ 서브쿼리가 여러 건을 반환하는데 = 로 비교
SELECT ENAME
FROM EMP
WHERE SAL = ( SELECT SAL FROM EMP WHERE DEPTNO = 30 ); -- 30번 부서 급여는 6건!
-- ORA-01427: single-row subquery returns more than one row
-- → = 는 '한 건' 과만 비교할 수 있는데 서브쿼리가 6건을 반환해서 오류
-- ✅ 여러 건이면 다중행 연산자(IN 등)를 써야 한다
SELECT ENAME
FROM EMP
WHERE SAL IN ( SELECT SAL FROM EMP WHERE DEPTNO = 30 ); -- IN 으로 변경
-- → 30번 부서 사원들의 급여와 같은 급여를 받는 모든 사원
SQL
복사
판단: 서브쿼리가 1건이면 =, 여러 건이면 IN/ANY/ALL.
확신이 없으면 다중행 연산자를 쓰는 게 안전합니다.4. 다중행 서브쿼리 


1) 네 가지 다중행 연산자
연산자 | 의미 |
IN | 집합 중 하나와 일치 |
ANY | 집합 중 어느 하나라도 만족 |
ALL | 집합의 전부 만족 |
EXISTS | 서브쿼리 결과가 존재하면 참 |
-- ============================================================
-- 【이해용 예제】 IN — 각 부서의 최고 급여자 ★
-- ============================================================
SELECT ENAME, SAL, DEPTNO
FROM EMP
WHERE SAL IN ( SELECT MAX(SAL) -- 부서별 최고 급여 목록 (여러 건)
FROM EMP
GROUP BY DEPTNO ); -- → 5000(10번), 3000(20번), 2850(30번)
-- ENAME SAL DEPTNO
-- ------ ----- ------
-- BLAKE 2850 30
-- SCOTT 3000 20
-- KING 5000 10
-- FORD 3000 20 → 4건
--
-- ⭐ 서브쿼리가 {5000, 3000, 2850} 세 건을 반환하고,
-- 그 중 하나와 같은 급여를 받는 사원을 모두 찾는다. (SCOTT, FORD 둘 다 3000)
SQL
복사
2) EXISTS — 존재 여부만 확인
-- ============================================================
-- 【이해용 예제】 EXISTS — 소속 사원이 있는 부서만 ★
-- ============================================================
SELECT D.DEPTNO, D.DNAME
FROM DEPT D
WHERE EXISTS ( SELECT 1 -- 값은 중요하지 않다. '존재하는지' 만 본다
FROM EMP E
WHERE E.DEPTNO = D.DEPTNO );
-- DEPTNO DNAME
-- ------ ----------
-- 10 ACCOUNTING
-- 20 RESEARCH
-- 30 SALES → 3건 (40번 OPERATIONS 는 사원 없어 제외)
--
-- ⭐ EXISTS 는 조건을 만족하는 행이 '하나라도 있으면' 참, 없으면 거짓.
-- 실제 값을 가져오지 않고 존재 여부만 보므로 성능에 유리하다.
-- SELECT 1 이든 SELECT * 든 결과는 같다 (무엇을 select하든 상관없음).
SQL
복사
5. ANY 와 ALL — 방향이 핵심 

1) 수직선으로 이해하기
집합 {1000, 2000, 3000} 에 대해:
1000 2000 3000
─────●───────────●───────────●─────→
↑ ↑
최솟값 최댓값
> ANY : 어느 하나보다 크면 → 최솟값(1000)보다 크면 참
> ALL : 전부보다 크면 → 최댓값(3000)보다 크면 참
< ANY : 어느 하나보다 작으면 → 최댓값(3000)보다 작으면 참
< ALL : 전부보다 작으면 → 최솟값(1000)보다 작으면 참
Plain Text
복사
연산자 | 의미 | 같은 뜻 |
> ANY | 최솟값보다 크면 | > MIN |
> ALL | 최댓값보다 크면 | > MAX |
< ANY | 최댓값보다 작으면 | < MAX |
< ALL | 최솟값보다 작으면 | < MIN |
-- ============================================================
-- 【시험용 예제】 > ANY 와 > ALL 의 차이 ★★★
-- 30번 부서의 급여는 {2850, 1600, 1500, 1250, 1250, 950}
-- (최솟값 950, 최댓값 2850)
-- ============================================================
-- > ANY : 30번 부서의 '누군가보다' 많이 받는 = 최솟값(950)보다 크면
SELECT ENAME, SAL FROM EMP
WHERE SAL > ANY ( SELECT SAL FROM EMP WHERE DEPTNO = 30 );
-- → 급여가 950 초과인 모든 사원 (거의 다 나옴)
-- > ALL : 30번 부서 '전부보다' 많이 받는 = 최댓값(2850)보다 크면
SELECT ENAME, SAL FROM EMP
WHERE SAL > ALL ( SELECT SAL FROM EMP WHERE DEPTNO = 30 );
-- ENAME SAL
-- ------ -----
-- JONES 2975
-- SCOTT 3000
-- KING 5000
-- FORD 3000 → 4건 (2850 초과)
--
-- ⭐ 헷갈리면 이렇게 기억: ALL(전부) 이니까 '가장 센 놈(MAX)' 을 이겨야 한다.
-- ANY(아무나) 니까 '가장 약한 놈(MIN)' 만 이기면 된다.
SQL
복사
2) 동치 관계 

다중행 연산자 | 같은 뜻 |
= ANY | IN |
<> ALL | NOT IN |
> ANY | > MIN |
> ALL | > MAX |
= ANY 는 IN 과 같습니다 (하나라도 같으면 참).
<> ALL 은 NOT IN 과 같습니다 (전부와 다르면 참).
이 동치 관계 때문에 다음 절의 NULL 함정이 IN 뿐 아니라 ANY/ALL 에도 적용됩니다.6. 서브쿼리와 NULL 함정 


1) NOT IN 서브쿼리에 NULL이 섞이면 0건
-- ============================================================
-- 【시험용 예제】 "관리자가 아닌 사원" 을 찾으려다 0건 ★★★
-- ============================================================
-- ❌ 관리자로 지정된 적 없는 사원을 찾으려는 의도
SELECT ENAME
FROM EMP
WHERE EMPNO NOT IN ( SELECT MGR FROM EMP ); -- MGR 목록에 없는 사원
-- → 0건! ⚠️
-- KING 의 MGR 이 NULL 이라, 서브쿼리 결과에 NULL 이 섞인다.
-- NOT IN (7902, 7698, ..., NULL) → AND 로 전개되며 NULL 비교가 UNKNOWN
-- → 전체가 UNKNOWN → 모든 행 탈락 (08강 원리 그대로)
SQL
복사
2) 두 가지 해결책 
-- ✅ 해결 ① 서브쿼리에서 NULL 을 제거한다
SELECT ENAME
FROM EMP
WHERE EMPNO NOT IN ( SELECT MGR FROM EMP
WHERE MGR IS NOT NULL ); -- NULL 을 걸러냄
-- ENAME
-- ------
-- SMITH
-- ALLEN
-- WARD
-- ... → 관리자가 아닌 사원들 ✅
-- ✅ 해결 ② NOT EXISTS 를 쓴다 (NULL 영향을 받지 않음)
SELECT E.ENAME
FROM EMP E
WHERE NOT EXISTS ( SELECT 1
FROM EMP M
WHERE M.MGR = E.EMPNO ); -- E가 누군가의 관리자면 존재 → 제외
-- → 동일한 결과 ✅
--
-- ⭐ NOT EXISTS 는 '존재하지 않으면 참' 이라 NULL 비교를 하지 않는다.
-- 그래서 NOT IN 의 NULL 함정을 원천적으로 피한다.
SQL
복사
3) NOT IN vs NOT EXISTS 정리 
NOT IN | NOT EXISTS | |
NULL 영향 | 받음 (NULL 섞이면 0건) ![]() | 안 받음 ![]() |
동작 | 값을 목록과 비교 | 존재 여부만 확인 |
실무 권장 | NULL 확실히 없을 때만 | NULL 가능성 있으면 이것 |
실무 원칙: NOT IN 에 서브쿼리를 쓸 때는 반드시 NULL 가능성을 점검하고,
애매하면 NOT EXISTS 를 쓴다.7. 서브쿼리 주의사항
규칙 | 내용 |
괄호 필수 | 서브쿼리는 ( ) 로 감싼다 |
ORDER BY 제한 | WHERE절 서브쿼리에는 원칙적으로 불가 (인라인뷰·Top-N 예외) |
단일행 vs 다중행 | 연산자와 반환 행 수를 맞춘다 (= (다중행) → 오류) |
서브쿼리 먼저 실행 | 안쪽이 먼저 (상관 서브쿼리는 예외 — 16강) |
8. 출제 포인트 한눈에 정리
# | 꼭 외울 것 | 핵심 |
1 | 서브쿼리 실행 순서 | 안쪽(서브쿼리) 먼저 |
2 | 위치별 분류 | WHERE=중첩 / SELECT=스칼라 / FROM=인라인뷰 |
3 | 단일행 서브쿼리 | 결과 1건 → = > < |
4 | 다중행에 단일행 연산자 | 오류 (ORA-01427) ![]() |
5 | 다중행 연산자 | IN ANY ALL EXISTS |
6 | > ANY | 최솟값보다 크면 (> MIN) ![]() |
7 | > ALL | 최댓값보다 크면 (> MAX) ![]() |
8 | = ANY | IN 과 동일 |
9 | <> ALL | NOT IN 과 동일 |
10 | EXISTS | 존재 여부만 (성능 유리) |
11 | NOT IN + NULL | 0건 (08강 회수) ![]() ![]() |
12 | NOT EXISTS | NULL 영향 안 받음 → 해결책 ![]() |
13 | 서브쿼리 ORDER BY | 원칙적 불가 |
14 | 서브쿼리 괄호 | 반드시 ( ) |
9. 확인 문제
Q1. 다음 SQL의 실행 순서를 설명하시오.
SELECT ENAME FROM EMP WHERE SAL > (SELECT AVG(SAL) FROM EMP);
SQL
복사
정답 보기
Q2. 다음 SQL이 오류가 나는 이유는?
SELECT ENAME FROM EMP WHERE SAL = (SELECT SAL FROM EMP WHERE DEPTNO = 30);
SQL
복사
정답 보기
Q3. SAL > ANY (2850, 1600, 950) 은 어떤 조건과 같은가?
① SAL > 2850 ② SAL > 950 ③ SAL > 1600 ④ SAL > (2850+1600+950)
정답 보기
Q4. SAL > ALL (2850, 1600, 950) 은 어떤 조건과 같은가?
정답 보기
Q5. = ANY 와 <> ALL 은 각각 무엇과 같은가?
정답 보기
Q6. EXISTS 서브쿼리에서 SELECT 1 과 SELECT * 중 무엇을 써야 하는가?
정답 보기
Q7. 서브쿼리에 대한 설명으로 옳지 않은 것은?
① 서브쿼리는 괄호로 감싼다
② WHERE절 서브쿼리에는 원칙적으로 ORDER BY를 쓸 수 없다
③ 단일행 서브쿼리에는 =, > 등을 쓴다
④ > ANY 는 서브쿼리 결과의 최댓값보다 커야 참이다
정답 보기







