{code:SQL | title= 예제 | borderStyle=solid} SQL> SELECT e.employee_id, e.email, d.department_id FROM employee e LEFT OUTER JOIN department d ON ( e.department_id = d.department_id AND e.employee_id > 7600); |
EMPLOYE EMAIL DEPAR
107 rows selected. --사번이 7600보다 큰 건들만 조회되지 않고 전체가 조회되었다.
{code:SQL|title= 10053 Trace |borderStyle=solid}
*************************
Join Elimination (JE)
*************************
SQL:******* UNPARSED QUERY IS *******
SELECT "E"."EMPLOYEE_ID" "EMPLOYEE_ID",
"E"."EMAIL" "EMAIL",
"E"."DEPARTMENT_ID" "QCSJ_C000000000300000",
"from$_subquery$_004"."DEPARTMENT_ID_0" "QCSJ_C000000000300001"
FROM "TLO"."EMPLOYEE" "E",
LATERAL( (SELECT "D"."DEPARTMENT_ID" "DEPARTMENT_ID_0"
FROM "TLO"."DEPARTMENT" "D"
WHERE "E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID" AND "E"."EMPLOYEE_ID">7600))(+) "from$_subquery$_004"
--인라인뷰를 Scalar서브쿼리처럼 사용
Plan분석
============
Plan Table
============
----------------------------------------+-----------------------------------+
| Id | Operation | Name | Rows | Bytes | Cost | Time |
----------------------------------------+-----------------------------------+
| 0 | SELECT STATEMENT | | | | 3 | |
| 1 | NESTED LOOPS OUTER | | 107 | 1605 | 2 | 00:00:01 |
| 2 | TABLE ACCESS FULL | EMPLOYEE | 107 | 1284 | 2 | 00:00:01 |
| 3 | INDEX UNIQUE SCAN | DEPT_ID_PK| 1 | 3 | 0 | |
----------------------------------------+-----------------------------------+
Predicate Information:
----------------------
3 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
3 - filter("E"."EMPLOYEE_ID">CASE WHEN ("D"."DEPARTMENT_ID" IS NOT NULL) THEN 7600 ELSE 7600 END )
결론
1) Lateral View는 결과 건수에 영향을 미치지 못하는 스칼라 인라인뷰이다.
2) E.EMPLOYEE_ID > 7600 조건은 결과 건수에 영향을 못미치고 DEPARTMENT와의 조인건수에만 영향을 끼친다.
3) Lateral View는 마치 스칼라 서브쿼리처럼 동작하므로 Sort Merge Join이나 Hash Jon을 사용할수 없고 Nested Loop Join만 가능하다.
또한 Driving 집합이 될 수 없다.
| title= 예제1 | borderStyle=solid} a. 직원구분코드(EMP_KND)생성 b. 모든 직원을 정규직(EMP_KND=1)과 계약직(EMP_KIND=2)으로 Update c. 직원구분코드가 정규직인경우 정규직 테이블에 직원번호와 자택전화번호(HOME_PHON_NUMBER)를 Insert d. 계약직인 경우 계약직 테이블에 직원번호와 계약회사 전화번호(OFFICE_PHONE_NUMBER)를 Insert해준다. -- 직원구분코드 컬럼생성 ALTER TABLE employee ADD (emp_kind VARCHAR2(1) DEFAULT 1 NOT NULL); -- 직원구분코드 UPDATE UPDATE employee SET emp_kind = CASE WHEN MOD(EMPLOYEE_ID, 2) = 1 THEN 1 ELSE 2 END; |
COMMIT ;
--정규직 테이블 생성
CREATE TABLE employee_kind1 AS
SELECT employee_id, phone_number || TO_CHAR(ROWNUM) AS home_phone_number FROM employee WHERE emp_kind = '1';
--계약직 테이블 생성
CREATE TABLE employee_kind2 AS
SELECT employee_id , phone_number || TO_CHAR(ROWNUM) AS office_phone_number FROM employee WHERE emp_kind = '2';
--PK 생성
ALTER TABLE employee_kind1
ADD CONSTRAINT pk_employee_kind1
PRIMARY KEY (employee_id) USING INDEX;
ALTER TABLE employee_kind2
ADD CONSTRAINT pk_employee_kind2
PRIMARY KEY (employee_id) USING INDEX;
exec dbms_stats.gather_table_stats(user,'EMPLOYEE_KIND1',cascade=>true);
exec dbms_stats.gather_table_stats(user,'EMPLOYEE_KIND2',cascade=>true);
exec dbms_stats.gather_table_stats(user,'EMPLOYEE',cascade=>true);
SELECT /*+ GATHER_PLAN_STATISTICS */
e.employee_id, e.first_name, e.last_name, e.emp_kind,
k1.home_phone_number, --자택 전화번호
k2.office_phone_number --계약회사 사무실 전화번호
FROM employee e, employee_kind1 k1, employee_kind2 k2
WHERE e.employee_id = k1.employee_id(+)
AND e.employee_id = k2.employee_id(+)
AND e.employee_id = 133;
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
0 | SELECT STATEMENT | 1 | 1 | 00:00:00.01 | 5 | |||||||||||||||||||||
1 | NESTED LOOPS OUTER | 1 | 1 | 1 | 00:00:00.01 | 5 | ||||||||||||||||||||
2 | NESTED LOOPS OUTER | 1 | 1 | 1 | 00:00:00.01 | 3 | ||||||||||||||||||||
3 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE | 1 | 1 | 1 | 00:00:00.01 | 2 | |||||||||||||||||||
| INDEX UNIQUE SCAN | EMP_EMP_ID_PK | 1 | 1 | 1 | 00:00:00.01 | 1 | |||||||||||||||||||
5 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE_KIND2 | 1 | 1 | 0 | 00:00:00.01 | 1 | |||||||||||||||||||
| INDEX UNIQUE SCAN | PK_EMPLOYEE_KIND2 | 1 | 1 | 0 | 00:00:00.01 | 1 | -->비효율 | 7 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE_KIND1 | 1 | 1 | 1 | 00:00:00.01 | 2 | 133은 정규직 사원임에도 불구하고 |
| INDEX UNIQUE SCAN | PK_EMPLOYEE_KIND1 | 1 | 1 | 1 | 00:00:00.01 | 1 | 계약직 사원 테이블의 블럭을 Scan - |
Predicate Information (identified by operation id):
4 - access("E"."EMPLOYEE_ID"=133)
6 - access("K2"."EMPLOYEE_ID"=133)
8 - access("K1"."EMPLOYEE_ID"=133)
32 rows selected.
{code:SQL|title= 예제2 |borderStyle=solid}
SELECT /*+ GATHER_PLAN_STATISTICS ORDERED */
e.employee_id, e.first_name, e.last_name, e.emp_kind,
k1.home_phone_number, --자택주소
k2.office_phone_number --계약회사 사무실번호
FROM employee e
LEFT OUTER JOIN employee_kind1 k1
ON (e.employee_id = k1.employee_id AND e.emp_kind = '1')
LEFT OUTER JOIN employee_kind2 k2
ON (e.employee_id = k2.employee_id AND e.emp_kind = '2')
WHERE e.employee_id = 133;
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
-------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 5 |
| 1 | NESTED LOOPS OUTER | | 1 | 1 | 1 |00:00:00.01 | 5 |
| 2 | NESTED LOOPS OUTER | | 1 | 1 | 1 |00:00:00.01 | 4 |
| 3 | TABLE ACCESS BY INDEX ROWID| EMPLOYEE | 1 | 1 | 1 |00:00:00.01 | 2 |
|* 4 | INDEX UNIQUE SCAN | EMP_EMP_ID_PK | 1 | 1 | 1 |00:00:00.01 | 1 |
| 5 | TABLE ACCESS BY INDEX ROWID| EMPLOYEE_KIND1 | 1 | 1 | 1 |00:00:00.01 | 2 |
|* 6 | INDEX UNIQUE SCAN | PK_EMPLOYEE_KIND1 | 1 | 1 | 1 |00:00:00.01 | 1 |
| 7 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE_KIND2 | 1 | 1 | 0 |00:00:00.01 | 1 |
|* 8 | INDEX UNIQUE SCAN | PK_EMPLOYEE_KIND2 | 1 | 1 | 0 |00:00:00.01 | 1 |-->블록 scan이 없음...
-------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("E"."EMPLOYEE_ID"=133)
6 - access("K1"."EMPLOYEE_ID"=133)
filter(NVL("E"."EMP_KIND",'1')=CASE WHEN ("K1"."EMPLOYEE_ID" IS NOT NULL) THEN '1' ELSE '1'END )
8 - access("K2"."EMPLOYEE_ID"=133)
filter(NVL("E"."EMP_KIND",'1')=CASE WHEN ("K2"."EMPLOYEE_ID" IS NOT NULL) THEN '2' ELSE '2'END )
37 rows selected.
ANSI SQL을 이용함으로써 선택적으로 조인할 수 있게 되었고 계약직 사원 테이블(EMPLOYEE_KIND2)
쪽의 Block Scan 량이 사라졌다. 1블록(8KB)의 차이에 불과하지만 자주 사용하는 SQL이라면
차이는 기하급수적으로 증가할 것이므로 이런 경우 Lateral View를 반드시 고려하여야 한다.
| title= 같은효과 | borderStyle=solid} SELECT /*+ GATHER_PLAN_STATISTICS */ e.employee_id, e.first_name, e.last_name, e.emp_kind, k1.home_phone_number, --자택 전화번호 k2.office_phone_number --계약회사 사무실 전화번호 FROM employee e, employee_kind1 k1, employee_kind2 k2 WHERE DECODE (e.emp_kind, '1', e.employee_id) = k1.employee_id(+) AND DECODE (e.emp_kind, '2', e.employee_id) = k2.employee_id(+) AND e.employee_id = 133; |
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
0 | SELECT STATEMENT | 1 | 1 | 00:00:00.01 | 4 | ||
1 | NESTED LOOPS OUTER | 1 | 1 | 1 | 00:00:00.01 | 4 | |
2 | NESTED LOOPS OUTER | 1 | 1 | 1 | 00:00:00.01 | 4 | |
3 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE | 1 | 1 | 1 | 00:00:00.01 | 2 |
| INDEX UNIQUE SCAN | EMP_EMP_ID_PK | 1 | 1 | 1 | 00:00:00.01 | 1 |
5 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE_KIND1 | 1 | 1 | 1 | 00:00:00.01 | 2 |
| INDEX UNIQUE SCAN | PK_EMPLOYEE_KIND1 | 1 | 1 | 1 | 00:00:00.01 | 1 |
7 | TABLE ACCESS BY INDEX ROWID | EMPLOYEE_KIND2 | 1 | 1 | 0 | 00:00:00.01 | 0 |
| INDEX UNIQUE SCAN | PK_EMPLOYEE_KIND2 | 1 | 1 | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
4 - access("E"."EMPLOYEE_ID"=133)
6 - access("K1"."EMPLOYEE_ID"=DECODE(NVL("E"."EMP_KIND",'1'),'1',"E"."EMPLOYEE_ID"))
8 - access("K2"."EMPLOYEE_ID"=DECODE(NVL("E"."EMP_KIND",'1'),'2',"E"."EMPLOYEE_ID"))
33 rows selected.
{code:none|borderStyle=solid}Lateral View는 현재 오라클이 내부적으로만 사용하고 있다
ObjectType을 하나 선언하면 스칼라 인라인뷰를 사용할 수 있다
|
{code:SQL | title= ObjectType선언 | borderStyle=solid} CREATE OR REPLACE TYPE SALES_CUST_TYPE AS OBJECT ( PROD_COUNT NUMBER(5), CHANNEL_COUNT NUMBER(2), AMOUNT_TOT NUMBER ); / |
explain plan for
SELECT s.cust_id, s.cust_year_of_birth,
s.sales_cust.prod_count,
s.sales_cust.channel_count,
s.sales_cust.amount_tot
FROM (SELECT c.cust_id, c.cust_year_of_birth,
(SELECT sales_cust_type
(COUNT (DISTINCT s.prod_id), -->위에서 선언한 TYPE사용
COUNT (DISTINCT s.channel_id),
SUM (s.amount_sold)
)
FROM sales s
WHERE s.cust_id = c.cust_id) AS sales_cust
FROM customers c
WHERE c.cust_year_of_birth= 1987) s ;
select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
Id | Operation | Name | Rows | Bytes | Cost (%CPU) | Time | Pstart | Pstop |
0 | SELECT STATEMENT | 151 | 1359 | 4 (0) | 00:00:01 | |||
1 | SORT GROUP BY | 1 | 25 | |||||
2 | PARTITION RANGE ALL | 130 | 3250 | 6 (0) | 00:00:01 | 1 | 28 | |
3 | TABLE ACCESS BY LOCAL INDEX ROWID | SALES | 130 | 3250 | 6 (0) | 00:00:01 | 1 | 28 |
4 | BITMAP CONVERSION TO ROWIDS | |||||||
| BITMAP INDEX SINGLE VALUE | SALES_CUST_BIX | 1 | 28 | ||||
6 | TABLE ACCESS BY INDEX ROWID | CUSTOMERS | 151 | 1359 | 4 (0) | 00:00:01 | ||
7 | BITMAP CONVERSION TO ROWIDS | |||||||
| BITMAP INDEX SINGLE VALUE | CUSTOMERS_YOB_BIX |
Predicate Information (identified by operation id):
5 - access("S"."CUST_ID"=:B1)
8 - access("C"."CUST_YEAR_OF_BIRTH"=1987)
{code:none|borderStyle=solid}
Plan을 보면 스칼라 서브쿼리를 사용했을 때와 같음을 알 수 있다
여러가지의 문제로 Laleral View의 기능을 사용해야 하지만 그릴 수 없는 경우의 좋은 해결책이다.
||