Scalar Subquery Filter Push Down 으로 풀어내기
인라인 뷰 안에 scalar subquery 가 있고 그 결과 컬럼을 인라인 뷰 바깥에서 filter 조건으로 사용하면, 옵티마이저는 View Merging 으로 인라인 뷰를 메인쿼리에 통합한 뒤 FPD (Filter Push Down) 로 outer 컬럼을 새 subquery 안으로 밀어 넣어 scalar subquery 를 일반 subquery filter 로 변환합니다.
핵심 정리
- 변환 트리거: scalar subquery 의 결과 컬럼이 인라인 뷰 바깥에서 조건절 로 사용되어야 합니다. SELECT list 에서만 참조되면 변환되지 않습니다.
- 변환 단계: (1) View Merging 으로 인라인 뷰가 메인쿼리에 통합 → (2) FPD 가 outer 컬럼을 변환된 subquery 안으로 push down.
- 결과: scalar subquery 가 사라지고 일반 subquery (correlated subquery) filter 형태로 plan 이 풀립니다. 가독성을 위해 인라인 뷰로 감싼 표기가 옵티마이저 입장에서는 같은 결과로 정리됩니다.
시나리오
employee 의 IT 직군 (job_id = 'IT_PROG') 사원 중, 소속 부서가 위치 정보 (location_id > 0) 를 가진 사원만 조회하는 쿼리입니다. 인덱스는 다음과 같습니다.
| 테이블 | 인덱스 | 컬럼 |
|---|---|---|
employee | EMP_XB_IX | job_id |
department | DEPT_ID_PK1 | department_id (PK) |
department.location_id 는 PK 검색 후 가져오는 컬럼이라 인덱스 lookup 후 1 회 access. scalar subquery 가 일반 subquery 로 변환되면 이 lookup 이 EXISTS 형태의 correlated subquery 로 풀립니다.
1) 원본 — Scalar Subquery + 인라인 뷰
1
2
3
4
5
6
7
8
SELECT a.employee_id, a.first_name, a.last_name, a.email
FROM (SELECT e.employee_id, e.first_name, e.last_name, e.email,
(SELECT location_id
FROM department d
WHERE d.department_id = e.department_id) AS location_id
FROM employee e
WHERE e.job_id = 'IT_PROG') a
WHERE a.location_id > 0;
흐름상 “직원 정보를 모은 다음 location_id 가 양수인 행만” 이 자연스럽지만, 옵티마이저는 이를 그대로 두지 않고 view merging + FPD 변환으로 풀어버립니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-------------------------------------------------------------------------------------
| Id | Operation | Name | E-Rows | E-Bytes | Cost (%CPU) |
-------------------------------------------------------------------------------------
|* 1 | FILTER | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE | 5 | 195 | 2 (0) |
|* 3 | INDEX RANGE SCAN | EMP_XB_IX | 5 | | 1 (0) |
| 4 | TABLE ACCESS BY INDEX ROWID | DEPARTMENT | 1 | 7 | 1 (0) |
|* 5 | INDEX UNIQUE SCAN | DEPT_ID_PK1 | 1 | | 0 (0) |
-------------------------------------------------------------------------------------
Predicate Information:
----------------------
1 - filter(>0)
3 - access("E"."JOB_ID"='IT_PROG')
5 - access("D"."DEPARTMENT_ID"=:B1)
Id 1의FILTER연산이 scalar subquery 가 일반 subquery filter 로 변환되었습니다.Id 4/Id 5가Id 1의 두 번째 자식이며, outerEMPLOYEE의 row 마다DEPARTMENT를 PK 로 lookup 하는 correlated subquery 형태입니다.Id 5의 access predicate"D"."DEPARTMENT_ID"=:B1에서:B1이 outere.department_id이며, 이것이 FPD 가 적용된 흔적입니다. 만약 변환되지 않았다면 scalar subquery 의 출력 컬럼을 매번 계산한 뒤 outer 에서>0비교를 했을 것입니다.
원본 쿼리에 인라인 뷰 a 가 분명히 있는데 plan 에 view operation 이 없는 것도 view merging 이 일어났다는 signature 입니다.
2) 변환 후 형태 (수정 쿼리 = 옵티마이저 변환 결과)
옵티마이저가 자동으로 풀어낸 결과를 사람 손으로 명시적으로 작성하면 다음과 같습니다.
1
2
3
4
5
6
SELECT e.employee_id, e.first_name, e.last_name, e.email
FROM employee e
WHERE e.job_id = 'IT_PROG'
AND (SELECT location_id
FROM department d
WHERE d.department_id = e.department_id) > 0;
scalar subquery 였던 (SELECT location_id FROM department ...) 가 그대로 WHERE 절로 이동하여 일반 subquery filter 가 되었습니다.
분석
View Merging
원본 쿼리의 인라인 뷰 a 는 단순한 column projection + filter 만 가지므로 옵티마이저가 view merging 대상으로 판단합니다. View merging 후 쿼리는 다음과 같은 모습이 됩니다.
1
2
3
4
5
6
SELECT e.employee_id, e.first_name, e.last_name, e.email
FROM employee e
WHERE e.job_id = 'IT_PROG'
AND (SELECT location_id
FROM department d
WHERE d.department_id = e.department_id) > 0;
이 단계에서 이미 scalar subquery 가 outer WHERE 절로 빠져나옵니다. 이후 옵티마이저가 이 형태를 standard correlated subquery filter 로 처리합니다.
Filter Push Down
엄밀히 말하면 본 케이스의 “FPD” 는 d.department_id = e.department_id 라는 correlation 조건이 inner subquery 안에 이미 들어 있는 형태 라 별도의 push-down 단계가 필요 없습니다. View merging 만으로 scalar subquery 가 outer filter 로 변환되면 plan 의 FILTER 연산이 자연스럽게 형성됩니다.
다만 사용자 원본 자료의 표현을 살리면, “outer > 0 조건이 변환된 subquery 의 outer filter 로 push down 된다” 는 구도로 이해할 수 있습니다. 즉 다음 변환 흐름입니다.
1
2
3
4
5
6
7
[원본] [view merging 후] [FPD 후]
inline_view a scalar subquery 가 correlated subquery
├─ select scalar subquery outer WHERE 로 빠짐 filter 형태 (Id 1)
└─ filter location_id > 0 → AND <ss> > 0 →
outer
plan: FILTER + EMPLOYEE + DEPARTMENT
Scalar subquery 를 인라인 뷰로 감싸 가독성을 높이는 패턴은 OK 입니다. 옵티마이저가 view merging + FPD 로 풀어내므로 결과 plan 은 똑같이 효율적입니다. 단 scalar subquery 결과를 filter 로 사용하지 않는다면 변환이 일어나지 않고, Plan 내 Inline View 가 그대로 남게 됩니다.
정리
- Scalar subquery + 인라인 뷰 + 바깥 filter 패턴은 옵티마이저가 view merging + FPD 로 풀어 일반 subquery filter 로 변환합니다.
- 변환 트리거 조건: scalar subquery 결과가 인라인 뷰 바깥에서 filter 로 사용 되어야 합니다. SELECT list 출력만이면 변환되지 않습니다.
- 튜닝 의의: 가독성 vs plan 결과의 trade-off 가 없습니다. 인라인 뷰로 감싸도 옵티마이저가 같은 plan 을 만들어주므로 자유롭게 표기를 선택해도 됩니다.