Search
moon
sun

15.서브쿼리 ① (단일행·다중행 서브쿼리)

•
서브쿼리의 개념과 실행 순서(안쪽 먼저)를 안다.
•
단일행 서브쿼리를 = > < 로 조건에 사용할 수 있다.
•
다중행 서브쿼리를 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 는 서브쿼리 결과의 최댓값보다 커야 참이다
정답 보기