当一个子查询介于 select 与 from 之间,这种子查询就叫标量子查询,例子如下。

    1. select e.ename,
    2. e.sal,
    3. (select d.dname from dept d where d.deptno = e.deptno) dname
    4. from emp e;

    我们在测试账号 scott 中运行如下 SQL。

    1. select /*+ gather_plan_statistics */ e.ename
    2. e.sal
    3. select d.dname from dept d where d.deptno = e.deptno dname
    4. from emp e;
    1. select * from tabledbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
    1. PLAN_TABLE_OUTPUT
    2. ------------------------------------------------------------------------------
    3. SQL_ID ggmw3tv6xypx1 child number 0
    4. -------------------------------------
    5. select /*+ gather_plan_statistics */ e.ename e.sal
    6. select d.dname from dept d where d.deptno = e.deptno dname from emp e
    7. Plan hash value: 2981343222
    8. -------------------------------------------------------------------------------------
    9. | Id |Operation |Name |Starts|E-Rows|A-Rows| A-Time | Buffers |
    10. -------------------------------------------------------------------------------------
    11. | 0 |SELECT STATEMENT | | 1| | 14|00:00:00.01| 8 |
    12. | 1 | TABLE ACCESS BY INDEX ROWID|DEPT | 3| 1| 3|00:00:00.01| 5 |
    13. |* 2 | INDEX UNIQUE SCAN |PK_DEPT| 3| 1| 3|00:00:00.01| 2 |
    14. | 3 | TABLE ACCESS FULL |EMP | 1| 14| 14|00:00:00.01| 8 |
    15. -------------------------------------------------------------------------------------
    16. Predicate Information identified by operation id):
    17. ---------------------------------------------------
    18. 2 - access(「D」.「DEPTNO」=:B1

    标量子查询类似一个天然的嵌套循环,而且驱动表固定为主表。大家是否还记得:嵌套循环被驱动表的连接列必须包含在索引中。同理,标量子查询中子查询的表连接列也必须包含在索引中。主表 EMP 通过连接列(DEPTNO)传值给子查询中的表(DEPT),执行计划中:B1 就表示传值,这个传值过程一共进行了 3 次,因为主表(EMP)的连接列(DEPTNO)基数等于 3。

    1. select count(distinct deptno) from emp;
    1. COUNT(DISTINCTDEPTNO)
    2. ---------------------
    3. 3

    我们建议在工作中,尽量避免使用标量子查询,假如主表返回大量数据,主表的连接列基数很高,那么子查询中的表会被多次扫描,从而严重影响 SQL 性能。如果主表数据量小,或者主表的连接列基数很低,那么这个时候我们也可以使用标量子查询,但是记得要给子查询中表的连接列建立索引。

    当 SQL 里面有标量子查询,我们可以将标量子查询等价改写为外连接,从而使它们可以进行 HASH 连接。为什么要将标量子查询改写为外连接而不是内连接呢?因为标量子查询是一个传值的过程,如果主表传值给子查询,子查询没有查询到数据,这个时候会显示 NULL。如果将标量子查询改写为内连接,会丢失没有关联上的数据。

    现有如下标量子查询。

    1. select d.dname,
    2. d.loc,
    3. (select max(e.sal) from emp e where e.deptno = d.deptno) max_sal
    4. from dept d;
    1. DNAME LOC MAX_SAL
    2. -------------- ------------- ----------
    3. ACCOUNTING NEW YORK 5000
    4. RESEARCH DALLAS 3000
    5. SALES CHICAGO 2850
    6. OPERATIONS BOSTON ---NULL

    我们可以将其等价改写为外连接:

    1. select d.dname d.loc e.max_sal
    2. from dept d
    3. left join select max(sal) max_sal,
    4. deptno
    5. from emp
    6. group by deptnoe
    7. on d.deptno = e.deptno
    1. DNAME LOC MAX_SAL
    2. -------------- ------------- ----------
    3. ACCOUNTING NEW YORK 5000
    4. RESEARCH DALLAS 3000
    5. SALES CHICAGO 2850
    6. OPERATIONS BOSTON ---NULL

    当然了,如果主表的连接列是外键,而子查询的连接列是主键,我们就没必要改写为外连接了,因为外键不可能存 NULL 值,可以直接改写为内连接。例如本书中所用的标量子查询示例就可以改写为内连接,因为 DEPT 与 EMP 有主外键关系。

    1. select e.ename, e.sal, d.dname
    2. from emp e
    3. inner join dept d on e.deptno = d.deptno;

    在 Oracle12c 中,简单的标量子查询会被优化器等价改写为外连接。