草庐IT

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

赵延东的一亩三分地 2023-04-18 原文

系列文章目录

【SQL开发实战技巧】系列(一):关于SQL不得不说的那些事
【SQL开发实战技巧】系列(二):简单单表查询
【SQL开发实战技巧】系列(三):SQL排序的那些事
【SQL开发实战技巧】系列(四):从执行计划讨论UNION ALL与空字符串&UNION与OR的使用注意事项
【SQL开发实战技巧】系列(五):从执行计划看IN、EXISTS 和 INNER JOIN效率,我们要分场景不要死记网上结论
【SQL开发实战技巧】系列(六):从执行计划看NOT IN、NOT EXISTS 和 LEFT JOIN效率,记住内外关联条件不要乱放
【SQL开发实战技巧】系列(七):从有重复数据前提下如何比较出两个表中的差异数据及对应条数聊起
【SQL开发实战技巧】系列(八):聊聊如何插入数据时比约束更灵活的限制数据插入以及怎么一个insert语句同时插入多张表
【SQL开发实战技巧】系列(九):一个update误把其他列数据更新成空了?Merge改写update!给你五种删除重复数据的写法!
【SQL开发实战技巧】系列(十):从拆分字符串、替换字符串以及统计字符串出现次数说起
【SQL开发实战技巧】系列(十一):拿几个案例讲讲translate|regexp_replace|listagg|wmsys.wm_concat|substr|regexp_substr常用函数
【SQL开发实战技巧】系列(十二):三问(如何对字符串字母去重后按字母顺序排列字符串?如何识别哪些字符串中包含数字?如何将分隔数据转换为多值IN列表?)
【SQL开发实战技巧】系列(十三):讨论一下常用聚集函数&通过执行计划看sum()over()对员工工资进行累加
【SQL开发实战技巧】系列(十四):计算消费后的余额&计算银行流水累计和&计算各部门工资排名前三位的员工
【SQL开发实战技巧】系列(十五):查找最值所在行数据信息及快速计算总和百之max/min() keep() over()、fisrt_value、last_value、ratio_to_report
【SQL开发实战技巧】系列(十六):数据仓库中时间类型操作(初级)日、月、年、时、分、秒之差及时间间隔计算
【SQL开发实战技巧】系列(十七):数据仓库中时间类型操作(初级)确定两个日期之间的工作天数、计算—年中周内各日期出现次数、确定当前记录和下一条记录之间相差的天数
【SQL开发实战技巧】系列(十八):数据仓库中时间类型操作(进阶)INTERVAL、EXTRACT以及如何确定一年是否为闰年及周的计算
【SQL开发实战技巧】系列(十九):数据仓库中时间类型操作(进阶)如何一个SQL打印当月或一年的日历?如何确定某月内第一个和最后—个周内某天的日期?
【SQL开发实战技巧】系列(二十):数据仓库中时间类型操作(进阶)获取季度开始结束时间以及如何统计非连续性时间的数据
【SQL开发实战技巧】系列(二十一):数据仓库中时间类型操作(进阶)识别重叠的日期范围,按指定10分钟时间间隔汇总数据
【SQL开发实战技巧】系列(二十二):数仓报表场景☞ 从分析函数效率一定快吗聊一聊结果集分页和隔行抽样实现方式
【SQL开发实战技巧】系列(二十三):数仓报表场景☞ 如何对数据排列组合去重以及通过如何找到包含最大值和最小值的记录这个问题再次用执行计划给你证明分析函数性能不一定高
【SQL开发实战技巧】系列(二十四):数仓报表场景☞通过案例执行计划详解”行转列”,”列转行”是如何实现的
【SQL开发实战技巧】系列(二十五):数仓报表场景☞结果集中的重复数据只显示一次以及计算部门薪资差异高效的写法以及如何对数据进行快速分组
【SQL开发实战技巧】系列(二十六):数仓报表场景☞聊聊ROLLUP、UNION ALL是如何分别做分组合计的以及如何识别哪些行是做汇总的结果行


文章目录


前言

本篇文章讲解的主要内容是:当两个表中有重复数据时,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要特别小心!!!不早了,快一点了睡觉!!!困😪😪

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

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  4. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  5. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  6. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  7. ruby-on-rails - rails : keeping DRY with ActiveRecord models that share similar complex attributes - 2

    这似乎应该有一个直截了当的答案,但在Google上花了很多时间,所以我找不到它。这可能是缺少正确关键字的情况。在我的RoR应用程序中,我有几个模型共享一种特定类型的字符串属性,该属性具有特殊验证和其他功能。我能想到的最接近的类似示例是表示URL的字符串。这会导致模型中出现大量重复(甚至单元测试中会出现更多重复),但我不确定如何让它更DRY。我能想到几个可能的方向...按照“validates_url_format_of”插件,但这只会让验证干给这个特殊的字符串它自己的模型,但这看起来很像重溶液为这个特殊的字符串创建一个ruby​​类,但是我如何得到ActiveRecord关联这个类模型

  8. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  9. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  10. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

随机推荐