【SQL开发实战技巧】系列(四):从执行计划讨论UNION ALL与空字符串&UNION与OR的使用注意事项

简介: 本篇文章讲解的主要内容是:***有重复数据的数据集用UNION后得到的数据与预期不一致如何解决,当两个表中有重复数据时,UNION的去重功能被忽略,UNION过程中如何识别展示出来、空值与空字符串的关系以及在UNION ALL中的使用、UNION与OR可以互相改写以及使用中的注意事项。***

前言

本篇文章讲解的主要内容是:当两个表中有重复数据时,UNION的去重功能被忽略,UNION过程中如何识别展示出来、空值与空字符串的关系以及在UNION ALL中的使用、UNION与OR可以互相改写以及使用中的注意事项。
【SQL开发实战技巧】这一系列博主当作复习旧知识来进行写作,毕竟SQL开发在数据分析场景非常重要且基础,面试也会经常问SQL开发和调优经验,相信当我写完这一系列文章,也能再有所收获,未来面对SQL面试也能游刃有余~。


一、UNION ALL 与空字符串

通过前面博客的案例可以看到,我们多次使用了UNION ALLUNLON ALL通常用于合并多个数据集。
看下面的语句:

SELECT empno AS 编码, ename AS 名称, nvl(mgr, deptno) AS 上级编码
  FROM emp
 WHERE empno = 7788
UNION ALL
SELECT deptno AS 编码, dname AS 名称, null AS 上级编码
  FROM dept
 WHERE deptno = 10;
        编码 名称                 上级编码
---------- -------------- ----------
      7788 SCOTT                7566
        10 ACCOUNTING     

可以看到,当其中一个数据集列不够时,可以用null来填充该列的值,而空字符串在
Oracle中常常相当于null。

SQL>  select ''  as a from dual;

A
--------------------------------

null
己选择 1 行。

为什么不说空字符串等价于null呢?看下面的示例:

SELECT empno AS 编码, ename AS 名称, nvl(mgr, deptno) AS 上级编码
  FROM emp
 WHERE empno = 7788
UNION ALL
SELECT deptno AS 编码, dname AS 名称, '' AS 上级编码
  FROM dept
 WHERE deptno = 10;
ORA-01790: 表达式必须具有与对应表达式相同的数据类型

可以看到,空字符串本身是varchar2类型,这与null可以是任何类型不同,当然,它们也就不等价。

二、UNION 与 OR(有重复数据的数据集用UNION后得到的数据与预期不一致如何解决)

当在条件里有or时,经常会改写为UNION,例如,我们在表emp中建立下面两个索引。


create index IDX_EMPNO on EMP (EMPNO);
create index IDX_ENAME on EMP (ENAME);

然后执行下面查询:

SQL> SELECT empno, ename
  2    FROM emp
  3   WHERE empno = 7788
  4      OR ename = 'SCOTT';

EMPNO ENAME
----- ----------
 7788 SCOTT

如果改写为UNION ALL,则结果就是错的:

SQL> SELECT empno,ename FROM emp WHERE empno=7788 UNION ALL
  2  SELECT empno,ename FROM emp WHERE ename='SCOTT';

EMPNO ENAME
----- ----------
 7788 SCOTT
 7788 SCOTT

因为原语句中用的条件是or,是两个结果的合集而非并集,所以一般改写时需要改为
UNION来去掉重复的数据。

SQL> SELECT empno, ename FROM emp WHERE empno  =  7788 UNION
  2  SELECT empno , ename FROM emp WHERE ename = 'SCOTT' ;

EMPNO ENAME
----- ----------
 7788 SCOTT

这样两个语句分别可以用empno及ename上的索引。
我们对比一下 PLAN。
更改前(为了消除bitmapconvert的影响,先设置参数。)

SQL> alter session set "_b_tree_bitmap_plans" = false;

Session altered


SQL>  explain plan for SELECT/*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.4') */ empno, ename FROM emp WHERE empno = 7788 OR ename = 'SCOTT';


Explained


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

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 3956160932
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     2 |    20 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| EMP  |     2 |    20 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("EMPNO"=7788 OR "ENAME"='SCOTT')

13 rows selected

这时是 FULL TABLE。
更改后的 PLAN:

SQL> 
SQL> explain plan for SELECT empno, ename FROM emp WHERE empno  =  7788 UNION SELECT empno , ename FROM emp WHERE ename = 'SCOTT' ;

Explained


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

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2024585924
--------------------------------------------------------------------------------
| Id  | Operation                             | Name      | Rows  | Bytes | Cost
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |           |     2 |    40 |
|   1 |  SORT UNIQUE                          |           |     2 |    40 |
|   2 |   UNION-ALL                           |           |       |       |
|   3 |    TABLE ACCESS BY INDEX ROWID        | EMP       |     1 |    20 |
|*  4 |     INDEX UNIQUE SCAN                 | PK_EMP    |     1 |       |
|   5 |    TABLE ACCESS BY INDEX ROWID BATCHED| EMP       |     1 |    20 |
|*  6 |     INDEX RANGE SCAN                  | IDX_ENAME |     1 |       |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("EMPNO"=7788)
   6 - access("ENAME"='SCOTT')

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Note
-----
   - dynamic statistics used: dynamic sampling (level=2)

23 rows selected

可以看到,更改后分别用了两列中的索引。
但在改写时,UNION的去重功能有时会被忽略,从而使数据出现错误,如下面的语句。

SELECT empno, deptno FROM emp  WHERE  mgr  =  7698  ORDER  BY  1  ; 
EMPNO DEPTNO
----- ------
 7499     30
 7521     30
 7654     30
 7844     30
 7900     30
SELECT empno, deptno FROM emp  WHERE  job  ='SALESMAN'  ORDER  BY  1  ; 

EMPNO DEPTNO
----- ------
 7499     30
 7521     30
 7654     30
 7844     30

两个条件中有4行数据是重复的,使用or连接两个条件将得到5行数据:

SELECT empno, deptno FROM emp  WHERE  job  ='SALESMAN' or mgr=7698  ORDER  BY  1  ; 

SQL> SELECT empno, deptno FROM emp  WHERE  job  ='SALESMAN' or mgr=7698  ORDER  BY  1  ; 

EMPNO DEPTNO
----- ------
 7499     30
 7521     30
 7654     30
 7844     30
 7900     30

而改成union后

SELECT deptno FROM emp  WHERE  job  ='SALESMAN' 
union 
SELECT deptno FROM emp  WHERE mgr=7698  ; 
DEPTNO
------
    30

只剩下了一行数据,结果显然不对。
以上实验可以看出:

  • 不仅两个数据集间重复的数据会被去重,而且单个数据集里重复的数据也会被去重。
  • 有重复数据的数据集用UNION后得到的数据与预期会不一致。

UNION ALL来模拟UNION语句的过程,语句如下:

select distinct deptno 
from (
SELECT deptno FROM emp  WHERE  job  ='SALESMAN' 
union 
SELECT deptno FROM emp  WHERE mgr=7698  
)order by 1;

其实,就是合并->去重->排序这三步,那么对结果的影响也就可想而知了。既然如此,像这种数据还可以用UNION改写吗?答案是肯定的。
我们只需在去重前加入一个可以唯一标识各行的列即可。
例如,在这里可以加入"empno",再利用UNION,效果如下:

SELECT empno,deptno FROM emp  WHERE  job  ='SALESMAN' 
union 
SELECT empno,deptno FROM emp  WHERE mgr=7698  
 EMPNO DEPTNO
----- ------
 7499     30
 7521     30
 7654     30
 7844     30
 7900     30

加入唯一列empno后,既保证了正确的去重,又防止了不该发生的去重。在此基础上,再嵌套一层就是想要的结果。

select deptno from ( 
SELECT empno,deptno FROM emp  WHERE  job  ='SALESMAN' 
union 
SELECT empno,deptno FROM emp  WHERE mgr=7698)
order by 1;

DEPTNO
------
    30
    30
    30
    30
    30

除了用唯一列、主键列外, 还可 以使用 rowid:

select deptno from ( 
SELECT rowid,deptno FROM emp  WHERE  job  ='SALESMAN' 
union 
SELECT rowid,deptno FROM emp  WHERE mgr=7698)
order by 1;

如果数据不是取自表,而是取自VIEW或则没有唯一列,那么应怎么处理呢?
我们可以增加rownum来当作唯一列:

with t as (
select rownum as sn,deptno,mgr,job from emp
)
select deptno from ( 
SELECT sn,deptno FROM t   WHERE  job  ='SALESMAN' 
union 
SELECT sn,deptno FROM t   WHERE mgr=7698)
order by 1;
DEPTNO
------
    30
    30
    30
    30
    30

总结

本篇博客主要是介绍有重复数据集时使用UNION要特别小心!!!不早了,快一点了睡觉!!!困😪😪

相关文章
|
1月前
|
SQL Oracle 关系型数据库
Oracle SQL:了解执行计划和性能调优
Oracle SQL:了解执行计划和性能调优
45 1
|
1月前
|
SQL 关系型数据库 MySQL
sql注入原理与实战(三)数据库操作
sql注入原理与实战(三)数据库操作
sql注入原理与实战(三)数据库操作
|
2月前
|
SQL 安全 Go
SQL注入不可怕,XSS也不难防!Python Web安全进阶教程,让你安心做开发!
在Web开发中,安全至关重要,尤其要警惕SQL注入和XSS攻击。SQL注入通过在数据库查询中插入恶意代码来窃取或篡改数据,而XSS攻击则通过注入恶意脚本来窃取用户敏感信息。本文将带你深入了解这两种威胁,并提供Python实战技巧,包括使用参数化查询和ORM框架防御SQL注入,以及利用模板引擎自动转义和内容安全策略(CSP)防范XSS攻击。通过掌握这些方法,你将能够更加自信地应对Web安全挑战,确保应用程序的安全性。
84 3
|
1月前
|
SQL 数据处理 数据库
SQL语句优化与查询结果优化:提升数据库性能的实战技巧
在数据库管理和应用中,SQL语句的编写和查询结果的优化是提升数据库性能的关键环节
|
1月前
|
SQL 监控 关系型数据库
SQL语句性能分析:实战技巧与详细方法
在数据库管理中,分析SQL语句的性能是优化数据库查询、提升系统响应速度的重要步骤
|
1月前
|
SQL 关系型数据库 Serverless
sql注入原理与实战(四)数据表操作
sql注入原理与实战(四)数据表操作
|
1月前
|
SQL 存储 Java
sql注入原理与实战(二)数据库原理
sql注入原理与实战(二)数据库原理
|
1月前
|
SQL 前端开发 安全
sql注入原理与实战(一)
sql注入原理与实战(一)
|
2月前
|
SQL 分布式计算 大数据
大数据开发SQL代码编码原则和规范
这段SQL编码原则强调代码的功能完整性、清晰度、执行效率及可读性,通过统一关键词大小写、缩进量以及禁止使用模糊操作如select *等手段提升代码质量。此外,SQL编码规范还详细规定了代码头部信息、字段与子句排列、运算符前后间隔、CASE语句编写、查询嵌套、表别名定义以及SQL注释的具体要求,确保代码的一致性和维护性。
76 0
|
2月前
|
SQL 安全 数据库
基于SQL Server事务日志的数据库恢复技术及实战代码详解
基于事务日志的数据库恢复技术是SQL Server中一个非常强大的功能,它能够帮助数据库管理员在数据丢失或损坏的情况下,有效地恢复数据。通过定期备份数据库和事务日志,并在需要时按照正确的步骤恢复,可以最大限度地减少数据丢失的风险。需要注意的是,恢复数据是一个需要谨慎操作的过程,建议在执行恢复操作之前,详细了解相关的操作步骤和注意事项,以确保数据的安全和完整。
104 0