【SQL开发实战技巧】系列(五):从执行计划看IN、EXISTS 和 INNER JOIN效率,我们要分场景不要死记网上结论

简介: 从执行计划角度分析IN、EXISTS 和 INNER JOIN效率而不是死记网上结论、表的5种关联:INNER JOIN、LEFT JOIN、RIGHT JOIN 和 FULL JOIN 解析【SQL开发实战技巧】这一系列博主当作复习旧知识来进行写作,毕竟SQL开发在数据分析场景非常重要且基础,面试也会经常问SQL开发和调优经验,相信当我写完这一系列文章,也能再有所收获,未来面对SQL面试也能游刃有余~。

前言

本篇文章讲解的主要内容是:从执行计划角度分析IN、EXISTS 和 INNER JOIN效率而不是死记网上结论、表的5种关联:INNER JOIN、LEFT JOIN、RIGHT JOIN 和 FULL JOIN 解析
【SQL开发实战技巧】这一系列博主当作复习旧知识来进行写作,毕竟SQL开发在数据分析场景非常重要且基础,面试也会经常问SQL开发和调优经验,相信当我写完这一系列文章,也能再有所收获,未来面对SQL面试也能游刃有余~。


一、组合相关的行

相对查询单表中的数据来说,平时更常见的需求是要在多个表中返回数据。比如,显示部门10的员工编码、姓名及所在部门名称和工作地址。

select a.empno,a.deptno,b.dname
from emp a inner join dept b
on(a.deptno=b.deptno)
where a.deptno=10;
EMPNO DEPTNO DNAME
----- ------ --------------
 7782     10 ACCOUNTING
 7839     10 ACCOUNTING
 7934     10 ACCOUNTING

另外有下面写法:

select a.empno,a.deptno,b.dname
from emp a,dept b
where a.deptno=b.deptno
EMPNO DEPTNO DNAME
----- ------ --------------
 7369     20 RESEARCH
 7499     30 SALES
 7521     30 SALES
 7566     20 RESEARCH
 7654     30 SALES
 7698     30 SALES
 7782     10 ACCOUNTING
 7788     20 RESEARCH
 7839     10 ACCOUNTING
 7844     30 SALES
 7876     20 RESEARCH
 7900     30 SALES
 7902     20 RESEARCH
 7934     10 ACCOUNTING

14 rows selected

其中,JOIN的写法是SQL-92的标准,当有多个表关联时,JOIN方式的写法能更清楚地看清各表之间的关系,因此,建议大家写查询语句时优先使用JOIN的写法。

二、从执行计划看IN、EXISTS 和 INNER JOIN效率

下面先 创建一个表 emp2.

drop index IDX_ENAME;
DROP TABLE emp2 PURGE ;
CREATE TABLE emp2 AS
SELECT ename,job,sal,comm FROM emp WHERE job ='CLERK';

要求返回与表emp2(empno,job,sal)中数据相匹配的emp(empno,ename,job,sal,deptno)
信息。
IN、EXISTS、INNER JOIN三种写法。为了加强理解,请大家看一下三种写法及其PLAN(此处用的是Oracle 11g)。

  • in写法
SQL> explain plan for select empno,ename,job,sal,deptno from emp where (ename,job,sal) in(select ename,job,sal from emp2);

Explained


SQL> select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 4039873364
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |    67 |     6   (0)| 00:00:01 |
|*  1 |  HASH JOIN SEMI    |      |     1 |    67 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| EMP  |    15 |   780 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| EMP2 |     4 |    60 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("ENAME"="ENAME" AND "JOB"="JOB" AND "SAL"="SAL")
Note
-----
   - dynamic statistics used: dynamic sampling (level=2)

19 rows selected
  • exists写法
SQL>  EXPLAIN PLAN FOR SELECT empno,ename,job,sal,deptno FROM emp a
  2  WHERE EXISTS (SELECT NULL
  3  FROM emp2 b
  4  WHERE b.ename = a.ename AND b.job = a.job
  5  AND b.sal = a.sal) ;

Explained


SQL> select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 4039873364
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |    67 |     6   (0)| 00:00:01 |
|*  1 |  HASH JOIN SEMI    |      |     1 |    67 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| EMP  |    15 |   780 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| EMP2 |     4 |    60 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("B"."ENAME"="A"."ENAME" AND "B"."JOB"="A"."JOB" AND
              "B"."SAL"="A"."SAL")
Note
-----
   - dynamic statistics used: dynamic sampling (level=2)

20 rows selected
  • 因为子查询的JOIN列(emp2.ename,emp2.job,ernp2.sal)没有重复行,所以这个查询可以直接改为INNER JOIN。
SQL> EXPLAIN PLAN  FOR  SELECT a.empno,a.ename,a.job,a.sal,a.deptno from emp a
  2  INNER JOIN emp2 b ON (b.ename = a.ename AND b.job = a.job AND b.sal =a.sal);

Explained


SQL> select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 166525280
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     4 |   268 |     6   (0)| 00:00:01 |
|*  1 |  HASH JOIN         |      |     4 |   268 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| EMP2 |     4 |    60 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| EMP  |    15 |   780 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("B"."ENAME"="A"."ENAME" AND "B"."JOB"="A"."JOB" AND
              "B"."SAL"="A"."SAL")
Note
-----
   - dynamic statistics used: dynamic sampling (level=2)

20 rows selected

或许与大家想象的不一样,以上三个PLAN中JOIN写法利用了HASH JOIN(哈希连接),其他两种运用的都是HASH JOIN SEMI(哈希半连接),说明在这个语句中的IN与EXISTS效率是一样的。所以,在不知哪种写法高效时应查看PLAN,而不是去记固定的结论

三、INNER JOIN、LEFT JOIN、RIGHT JOIN 、FULL JOIN、自关联解析

有很多人对这几种连接方式,特别是LEFT JOIN与RIGHT JOIN分不清,下面通过案例来解析一下。
首先建立两个测试用表。

DROP TABLE L PURGE ; DROP TABLE R PURGE;
--左表
CREATE TABLE L AS
SELECT 'left_1'  AS  str ,  '1' AS  v  FROM  dual  UNION  ALL 
SELECT 'left_2'  AS  str ,  '2' AS  v  FROM  dual  UNION  ALL 
SELECT 'left_3'  AS  str ,  '3' AS  v  FROM  dual  UNION  ALL 
SELECT 'left_4'  AS  str ,  '4' AS  v  FROM  dual;
--右表
CREATE TABLE R AS
SELECT 'right_3'  AS  str ,  '3' AS  v,1 as status  FROM  dual  UNION  ALL
SELECT 'right_4'  AS  str ,  '4' AS  v,0 as status  FROM  dual  UNION  ALL
SELECT 'right_5'  AS  str ,  '5' AS  v,0 as status  FROM  dual  UNION  ALL
SELECT 'right_6'  AS  str ,  '6' AS  v,0 as status  FROM  dual; 

1、INNER JOIN 的 特点

该方式返回两表相匹配的数据,左表的"1、2"以及右表的"5、6"都没有显示。
JOIN写法

SQL> 
SQL> select l.str as left_str, r.str as right_str
  2    from l
  3   inner join r
  4      on (l.v = r.v)
  5   order by 1, 2;

LEFT_STR RIGHT_STR
-------- ---------
left_3   right_3
left_4   right_4

2、LEFTJOIN的特点

该方式的左表为主表,左表返回所有的数据,右表中只返回与左表匹配的数据,"5、6"都没有显示。
join写法:

select l.str as left_str, r.str as right_str
  from l
 left join r
    on (l.v = r.v)
 order by 1, 2;
LEFT_STR RIGHT_STR
-------- ---------
left_1   
left_2   
left_3   right_3
left_4   right_4

加(+)写法

select l.str as left_str, r.str as right_str
  from l, r
    where l.v = r.v(+)
 order by 1, 2;

3、RIGHT JOIN的特点

该方式的右表为主表,左表中只返回与右表匹配的数据"3、4",而"1、2"都没有显示,右表返回所有的数据。
join写法

select l.str as left_str, r.str as right_str
  from l
 right join r
    on (l.v = r.v)
 order by 1, 2;
LEFT_STR RIGHT_STR
-------- ---------
left_3   right_3
left_4   right_4
         right_5
         right_6

加(+)写法

select l.str as left_str, r.str as right_str
  from l, r
    where l.v(+) = r.v
 order by 1, 2;

4、FULL JOIN的特点

该方式的左右表均返回所有的数据,但只有相匹配的数据显示在同一行,非匹配的行只显示一个表的数据。
JOIN写法

select l.str as left_str, r.str as right_str
  from l
 full join r
    on (l.v = r.v)
 order by 1, 2;
LEFT_STR RIGHT_STR
-------- ---------
left_1   
left_2   
left_3   right_3
left_4   right_4
         right_5
         right_6

6 rows selected

FULL JOIN 无(+ )的写法。

6、自关联

表emp中有一个字段mgr,其中是主管的编码(对应于emp.empno),如:
(EMPNO:7698,ENAME:BLAKE)-->(MGR:7839)-->(EMPNO:7839,ENAME:KING),说明BLAKE的主管就是KING
如何根据这个信息返回主管的姓名呢?
这里用到的就是自关联。也就是两次查询表emp,分别取不同的别名,这样就可以当作是两个表,后面的任务就是将这两个表和JOIN连接起来就可以。
为了方便理解,这里用汉字作为别名,并把相关列一起返回。

SELECT 员工.empno AS 职工编码,
       员工.ename AS 职工姓名,
       员工.job   AS 工作,
       员工.mgr   AS 员工表_主管编码,
       主管.empno AS 主管表_主管编码,
       主管.ename AS 主管姓名
  FROM emp 员工
  LEFT JOIN emp 主管
    ON (员工.mgr = 主管.empno)
 ORDER BY 1;
 职工编码 职工姓名   工作      员工表_主管编码 主管表_主管编码 主管姓名
----- ---------- --------- -------- -------- ----------
 1001 test                                   
 7369 SMITH      CLERK         7902     7902 FORD
 7499 ALLEN      SALESMAN      7698     7698 BLAKE
 7521 WARD       SALESMAN      7698     7698 BLAKE
 7566 JONES      MANAGER       7839     7839 KING
 7654 MARTIN     SALESMAN      7698     7698 BLAKE
 7698 BLAKE      MANAGER       7839     7839 KING
 7782 CLARK      MANAGER       7839     7839 KING
 7788 SCOTT      ANALYST       7566     7566 JONES
 7839 KING       PRESIDENT                   
 7844 TURNER     SALESMAN      7698     7698 BLAKE
 7876 ADAMS      CLERK         7788     7788 SCOTT
 7900 JAMES      CLERK         7698     7698 BLAKE
 7902 FORD       ANALYST       7566     7566 JONES
 7934 MILLER     CLERK         7782     7782 CLARK

15 rows selected

总结

这一章主要介绍两块,之所以拿出来这两块说是因为:

  • IN、EXISTS 和 INNER JOIN这三者或则说前两者的效率,博主在日常工作和面试过程中,经常遇到大家斩钉截铁的说in效率远远低于EXISTS 和 INNER JOIN,这类人大都是自己没有亲测,从网上搜了相关信息就记下来了,有些时候,网上的内容并不代表绝对正确,就像网上很多文章说scala的入参不能超过22个参数一样~
  • 其次,表的INNER JOIN、LEFT JOIN、RIGHT JOIN 、FULL JOIN、自关联这5种关联和简写方式,在工作中也很容易出错,所以在写这篇文章时候,博主自己也做个总结~
相关文章
|
1天前
|
SQL 监控 安全
sql注入场景与危害
sql注入场景与危害
|
2天前
|
SQL 关系型数据库 MySQL
sql注入原理与实战(三)数据库操作
sql注入原理与实战(三)数据库操作
sql注入原理与实战(三)数据库操作
|
2天前
|
SQL 关系型数据库 Serverless
sql注入原理与实战(四)数据表操作
sql注入原理与实战(四)数据表操作
|
2天前
|
SQL 存储 Java
sql注入原理与实战(二)数据库原理
sql注入原理与实战(二)数据库原理
|
2天前
|
SQL 前端开发 安全
sql注入原理与实战(一)
sql注入原理与实战(一)
|
2月前
|
SQL 存储 数据处理
"SQL触发器实战大揭秘:一键解锁数据自动化校验与更新魔法,让数据库管理从此告别繁琐,精准高效不再是梦!"
【8月更文挑战第31天】在数据库管理中,确保数据准确性和一致性至关重要。SQL触发器能自动执行数据校验与更新,显著提升工作效率。本文通过一个员工信息表的例子,详细介绍了如何利用触发器自动设定和校验薪资,确保其符合业务规则。提供的示例代码展示了在插入新记录时如何自动检查并调整薪资,以满足最低标准。这不仅减轻了数据库管理员的负担,还提高了数据处理的准确性和效率。触发器虽强大,但也需谨慎使用,以避免复杂性和性能问题。
38 1
|
1月前
|
SQL 安全 数据库
基于SQL Server事务日志的数据库恢复技术及实战代码详解
基于事务日志的数据库恢复技术是SQL Server中一个非常强大的功能,它能够帮助数据库管理员在数据丢失或损坏的情况下,有效地恢复数据。通过定期备份数据库和事务日志,并在需要时按照正确的步骤恢复,可以最大限度地减少数据丢失的风险。需要注意的是,恢复数据是一个需要谨慎操作的过程,建议在执行恢复操作之前,详细了解相关的操作步骤和注意事项,以确保数据的安全和完整。
74 0
|
2月前
|
测试技术 Java
全面保障Struts 2应用质量:掌握单元测试与集成测试的关键策略
【8月更文挑战第31天】Struts 2 的测试策略结合了单元测试与集成测试。单元测试聚焦于单个组件(如 Action 类)的功能验证,常用 Mockito 模拟依赖项;集成测试则关注组件间的交互,利用 Cactus 等框架确保框架拦截器和 Action 映射等按预期工作。通过确保高测试覆盖率并定期更新测试用例,可以提升应用的整体稳定性和质量。
62 0
|
2月前
|
数据库 Java 监控
Struts 2 日志管理化身神秘魔法师,洞察应用运行乾坤,演绎奇幻篇章!
【8月更文挑战第31天】在软件开发中,了解应用运行状况至关重要。日志管理作为 Struts 2 应用的关键组件,记录着每个动作和决策,如同监控摄像头,帮助我们迅速定位问题、分析性能和使用情况,为优化提供依据。Struts 2 支持多种日志框架(如 Log4j、Logback),便于配置日志级别、格式和输出位置。通过在 Action 类中添加日志记录,我们能在开发过程中获取详细信息,及时发现并解决问题。合理配置日志不仅有助于调试,还能分析用户行为,提升应用性能和稳定性。
41 0
|
2月前
|
前端开发 Java JSON
Struts 2携手AngularJS与React:探索企业级后端与现代前端框架的完美融合之道
【8月更文挑战第31天】随着Web应用复杂性的提升,前端技术日新月异。AngularJS和React作为主流前端框架,凭借强大的数据绑定和组件化能力,显著提升了开发动态及交互式Web应用的效率。同时,Struts 2 以其出色的性能和丰富的功能,成为众多Java开发者构建企业级应用的首选后端框架。本文探讨了如何将 Struts 2 与 AngularJS 和 React 整合,以充分发挥前后端各自优势,构建更强大、灵活的 Web 应用。
41 0