《Pro Oracle SQL》CHAPTER2--2.8 Subquery Unnesting

Subquery Unnesting  子查询反嵌套         (page 66)
    Subquery unnesting is similar to view merging in that just like a view a subquery is represented by a
separate query block.  The main difference between mergeable views and subqueries that can be
unnested is location:  Subqueries located within the WHERE clause are reviewed for unnesting by the
transformer. 
The most typical transformation is to convert the subquery into a join.  If a subquery isn’t
unnested, a separate subplan will be generated for it and executed in an order within the overall plan
that allows for optimal execution speed.   
    子查询反嵌套相似于视图合并,就像一个视图一样一个子查询代表一个单独的查询块。可合并的视图与可反嵌套的子查询之间主要的不同是位置:位于WHERE子句中的子查询由变换器检查(是否能)反嵌套。 最典型的变换是转换子查询成连接。如果一子查询不能反嵌套,将生成一单独的子计划且在整个计划中按一定顺序,以最佳的执行速度,执行。
    When the subquery is not correlated, the transformed query is very straightforward, as shown in Listing 2-6.
    当子查询不是相关的,变换的查询是非常直接的,如列表2-6所示。
Listing 2-6. Unnesting Transformation of an Uncorrelated Subquery      一不相关子查询的反嵌套查询
SQL> set autotrace traceonly explain
SQL>
SQL> select * from employees where department_id in (select department_id from departments);
 
Execution Plan
----------------------------------------------------------
Plan hash value: 169719308
 
---------------------------------------------------------------------------------
| Id  | Operation                         | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |                        |   106  |  7632 |     3   (0)| 00:00:01 |
|   1 |   NESTED LOOPS             |                       |   106  |  7632 |     3   (0)| 00:00:01 |
|   2 |      TABLE ACCESS FULL  | EMPLOYEES  |   107  |  7276 |     3   (0)| 00:00:01 |
|*  3 |      INDEX UNIQUE SCAN | DEPT_ID_PK   |     1    |     4    |     0   (0)| 00:00:01 |
---------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   3 - access("DEPARTMENT_ID"="DEPARTMENT_ID")
 
    The subquery in this case is simply merged into the main query block and converted to a table join. 
The query plan is derived as if the statement were written as follows:
    在本例的子查询直接合并入主查询块且转换成表连接。派生出的查询计划就像如下语句写的:
select e.* 
from   employees e, departments d
where  e.department_id = d.department_id
 
    Using the NO_UNNEST hint, I could have forced the query to be optimized as written, which would mean that a separate subplan would be created for the subquery (as shown in Listing 2-7).
    使用NO_UNNEST 提示,我就能强制优化查询如所写的,意味着将为子查询创建单独的子计划 (如列表2-7所示)
Listing  2-7. Using the NO_UNNEST Hint 
SQL> select employee_id, last_name, salary, department_id
  2  from   employees
  3  where  department_id in
  4             (select /*+ NO_UNNEST */department_id
  5                from departments where location_id > 1700);
 
Execution Plan
----------------------------------------------------------
Plan hash value: 4233807898
--------------------------------------------------------------------------------------------
| Id  | Operation                                           | Name                  | Rows  | Bytes  | Cost (%CPU)| Time       |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                          |                           |    10    |   190  |    14   (0)     | 00:00:01 |
|*  1 |   FILTER                                             |                          |            |           |                    |               |
|   2 |      TABLE ACCESS FULL                    | EMPLOYEES     |   107    |  2033 |     3   (0)      | 00:00:01 |
|*  3 |      TABLE ACCESS BY INDEX ROWID| DEPARTMENTS |     1     |     7    |     1   (0)      | 00:00:01 |
|*  4 |         INDEX UNIQUE SCAN                 | DEPT_ID_PK      |     1     |          |     0   (0)     | 00:00:01 |
--------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "HR"."DEPARTMENTS"
              "DEPARTMENTS" WHERE "DEPARTMENT_ID"=:B1 AND "LOCATION_ID">1700))
   3 - filter("LOCATION_ID">1700)
   4 - access("DEPARTMENT_ID"=:B1)
    The main difference between the plans is that without query transformation, a FILTER operation is
chosen instead of a NESTED LOOPS join.  I’ll discuss both of these operations in detail in Chapters 3 and 6, but for now just note that the FILTER operation typically represents a less efficient way of accomplishing a match, or join, between two tables.  You can see that the subquery remains intact if you look at the Predicate Information for step 1.  What happens with this “as is” version is that for each row in the employees table, the subquery must execute using the  employees table  department_id column as a bind variable for comparison with the list of  department_ids returned from the execution of the subquery.  Since there are 107 rows in the employees table, the subquery will execute once for each row.  That’s not precisely what happens due to a nice optimization feature Oracle uses called  subquery caching, but hopefully you can see that executing the query for each row isn’t as efficient as joining the two tables.  I’ll discuss the details of these operations and review why the choice of a NESTED LOOPS join is more efficient than the FILTER operation in the chapters ahead.
    两个计划的主要不同在于:没有查询变换,选择是过滤器(FILTER)操作而非嵌套循环(NESTED LOOPS)连接。我将在第3,6章深入讨论这些操作,但是现在仅需要注意:在两表之间进行完成一次匹配或连接,FILTER操作通常代表一种低效率方 式。 如果你看到第一步的谓词信息就发现子查询(在这里)原封不动 。在“等价”版的(执行计划)发生的是:对于employees表的每行,子查询必须执行使用 employees表的department_id列作为绑定变量与子查询执行返回的department_ids列表的比较。由于在employees 表中有107行,子查询将在每行都执行一次。由于Oracle使用了一好的优化特性“子查询缓存”,所发生的(执行107次)是不准确的。我将详细讨论这些操作和复习在本章开头(所述的)为什么选择嵌套循环连接比过滤器操作会更加有效率。
    The subquery unnesting transformation is a bit more complicated when a correlated subquery is involved.  In this case, the correlated subquery is typically transformed into a view, unnested, and then
joined to the table in the main query block.  Listing 2-8 shows an example of subquery unnesting of a
correlated subquery.

    当一个相关子查询涉及时子查询反嵌套变换就会有些复杂。在这种情况下,相关子查询通常变换成视图,反嵌套,然后同主查询块的表连接。列表2-8展示了一个子查询反嵌套一个相关子查询的例子。
Listing 2-8. Unnesting Transformation of a Correlated Subquery
SQL> select outer.employee_id, outer.last_name, outer.salary, outer.department_id
  2  from employees outer
  3  where outer.salary >
  4     (select avg(inner.salary)
  5        from employees inner
  6       where inner.department_id = outer.department_id)
  7  ;
 
Execution Plan
----------------------------------------------------------
Plan hash value: 2167610409
 ----------------------------------------------------------------------------------
| Id  | Operation                             | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |                      |    17     |   765  |     8  (25)       | 00:00:01 |
|*  1 |   HASH JOIN                        |                     |    17     |   765  |     8  (25)       | 00:00:01 |
|   2 |      VIEW                              | VW_SQ_1     |    11     |   286  |     4  (25)       | 00:00:01 |
|   3 |         HASH GROUP BY        |                      |    11     |    77   |     4  (25)       | 00:00:01 |
|   4 |           TABLE ACCESS FULL| EMPLOYEES |   107    |   749 |     3   (0)         | 00:00:01 |
|   5 |      TABLE ACCESS FULL     | EMPLOYEES |   107    |  2033 |     3   (0)        | 00:00:01 |
----------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("DEPARTMENT_ID"="OUTER"."DEPARTMENT_ID")
       filter("OUTER"."SALARY">"VW_COL_1") 
 
    Notice in this example how the subquery is transformed into an in-line view, then merged with the
outer query and joined.  The correlated column becomes the join condition and the rest of the subquery
is used to formulate an inline view.  The rewritten version of the query would look something like this:
     注意在本例中子查询是如何变换成内联视图的,然后同外部的查询块合并和连接的。相关列变成了连接条件然后子查询的其余部分用于形成一内联视图。查询的重写版本将看的像如下语句:
select outer.employee_id, outer.last_name, outer.salary, outer.department_id
  from employees outer, 
   (select department_id, avg(salary) avg_sal
      from employees
     group by department_id) inner
 where outer.department_id = inner.department_id 

    and outer.salary > inner.avg_sal;          (注:这里原文中少了这句条件,具体查看附件List_2-8.rar)
 
    Subquery unnesting behavior is controlled by the hidden parameter _unnest_subquery that defaults
to TRUE in version 9 and above.  This parameter is specifically described as controlling unnesting
behavior for correlated subqueries.  Just like with view merging, starting in version 10, transformed
queries are reviewed by the optimizer, and the costs are evaluated to determine whether or not an
unnested version would be the least costly. 

    子查询反嵌套行为被隐式参数_unnest_subquery所控制,在版本9之前默认值是TRUE。这个参数特别地描述控制对相关子查询的反嵌套行为。正如视图合并,从版本10开始,优化器检查变换过的查询,然后评估成本确定是否非反嵌套版本的成本更低。

猜你喜欢

转载自caohong286.iteye.com/blog/1370976