[译] Oracle Database 21c 中的 SQL 集合运算符增强功能(EXCEPT、EXCEPT ALL、MINUS ALL、INTERSECT ALL)

2022-04-13 17:42:47 浏览数 (1)

原文地址:https://oracle-base.com/articles/21c/sql-set-operator-enhancements-21c 原文作者:Tim Hall

Oracle 21C 新增许多对 SQL 集合运算符的增强,包括 EXCEPTEXCEPT ALLMINUS ALLINTERSECT ALL

在以前的版本中,我们将 ALL 关键字添加到 UNION 以防止删除重复值,从而提高性能。在 Oracle 21C 中,ALL 关键字也可以添加到 MINUS 和 INTERSECT 运算符,因此它们的操作是基于相同行的,而不是基于不同行的。 Oracle 21C 还引入了 EXCEPT 和 EXCEPT ALL 运算符,它们在功能上分别等同于 MINUS 和 MINUS ALL。

目录

  • 环境准备
  • MINUS ALL
  • INTERSECT ALL
  • EXCEPT
  • EXCEPT ALL

环境准备

本文中的示例需要创建以下表和数据:

代码语言:javascript复制
--drop table departments purge;

create table departments (
  department_id   number(2) constraint departments_pk primary key,
  department_name varchar2(14),
  location        varchar2(13)
);

insert into departments values (10,'ACCOUNTING','NEW YORK');
insert into departments values (20,'RESEARCH','DALLAS');
insert into departments values (30,'SALES','CHICAGO');
insert into departments values (40,'OPERATIONS','BOSTON');
commit;

此表基于 SCOTT 用户中的 DEPT 表,修改为与 SQL for Beginners 系列中使用的表一致。

MINUS ALL

MINUS 集合运算符返回第一个查询的结果,但是不包含第二个查询结果的所有行。这在功能上等同于 ANSI 集合运算符 EXCEPT DISTINCT,MINUS ALL 集合运算符不会删除重复的行。

首先我们需要创建一些重复的行,通过以下查询,我们使用包含 UNION ALL 的 WITH 子句来复制部门表中的行,然后我们查询该重复数据:

代码语言:javascript复制
with d1 as (
  select department_id, department_name
  from   departments
  union all
  select department_id, department_name
  from   departments
)
select department_id, department_name
from   d1
where  department_id <= 30
order by 1;

DEPARTMENT_ID DEPARTMENT_NAM
------------- --------------
           10 ACCOUNTING
           10 ACCOUNTING
           20 RESEARCH
           20 RESEARCH
           30 SALES
           30 SALES

SQL>

通过以下查询,我们使用 MINUS 运算符后,由于 MINUS 自带去重,所以实际上第一个查询出的结果去重后只剩下 10,20,30,将第二个查询结果中的 20,30 都减掉后,最后结果集只显示一个 10:

代码语言:javascript复制
with d1 as (
  select department_id, department_name
  from   departments
  union all
  select department_id, department_name
  from   departments
)
select department_id, department_name
from   d1
where  department_id <= 30
minus
select department_id, department_name
from   departments
where  department_id >= 20
order by 1;

DEPARTMENT_ID DEPARTMENT_NAM
------------- --------------
           10 ACCOUNTING

SQL>

如果我们将 MINUS 替换成 MINUS ALL 后,由于 MINUS ALL 不会自动去重,因此第一个查询结果为 10,10,20,20,30,30,此时减去第二个查询结果后,剩下的就是 10,10,20,30:

代码语言:javascript复制
with d1 as (
  select department_id, department_name
  from   departments
  union all
  select department_id, department_name
  from   departments
)
select department_id, department_name
from   d1
where  department_id <= 30
minus all
select department_id, department_name
from   departments
where  department_id >= 20
order by 1;

DEPARTMENT_ID DEPARTMENT_NAM
------------- --------------
           10 ACCOUNTING
           10 ACCOUNTING
           20 RESEARCH
           30 SALES

SQL>

接下来我们对两个查询都使用 WITH 子句,这样我们在 MINUS ALL 操作的两侧都有重复项,去掉第二个查询的结果 20,20,30,30,最后剩下 2 个 10:

代码语言:javascript复制
with d1 as (
  select department_id, department_name
  from   departments
  union all
  select department_id, department_name
  from   departments
)
select department_id, department_name
from   d1
where  department_id <= 30
minus all
select department_id, department_name
from   d1
where  department_id >= 20
order by 1;

DEPARTMENT_ID DEPARTMENT_NAM
------------- --------------
           10 ACCOUNTING
           10 ACCOUNTING

SQL>

INTERSECT ALL

INTERSECT 集合运算符返回两个查询选择的所有不同行,这意味着只有两个查询共有的那些行才会出现在最终结果集中,INTERSECT ALL 集合运算符不会删除重复的行。

同样的,首先我们需要创建一些重复的行,通过以下查询,我们使用包含 UNION ALL 的 WITH 子句来复制部门表中的行,然后我们查询该重复数据:

代码语言:javascript复制
with d1 as (
  select department_id, department_name
  from   departments
  union all
  select department_id, department_name
  from   departments
)
select department_id, department_name
from   d1
where  department_id <= 30
order by 1;

DEPARTMENT_ID DEPARTMENT_NAM
------------- --------------
           10 ACCOUNTING
           10 ACCOUNTING
           20 RESEARCH
           20 RESEARCH
           30 SALES
           30 SALES

SQL>

下面的示例使用 INTERSECT 运算符,同样的 INTERSECT 也是自带去重,所以最后查询结果为 20,30:

代码语言:javascript复制
with d1 as (
  select department_id, department_name
  from   departments
  union all
  select department_id, department_name
  from   departments
)
select department_id, department_name
from   d1
where  department_id <= 30
intersect
select department_id, department_name
from   departments
where  department_id >= 20
order by 1;

DEPARTMENT_ID DEPARTMENT_NAM
------------- --------------
           20 RESEARCH
           30 SALES

SQL>

我们将 INTERSECT 替换为 INTERSECT ALL 后得到了相同的结果,因为 INTERSECT ALL 之后的查询仅包含部门 20 和 30 的单个副本,因此每个部门只有一个相交:

代码语言:javascript复制
with d1 as (
  select department_id, department_name
  from   departments
  union all
  select department_id, department_name
  from   departments
)
select department_id, department_name
from   d1
where  department_id <= 30
intersect all
select department_id, department_name
from   departments
where  department_id >= 20
order by 1;

DEPARTMENT_ID DEPARTMENT_NAM
------------- --------------
           20 RESEARCH
           30 SALES

SQL>

接下来我们对两个查询都使用 WITH 子句,由于两个查询均包含 20,20,30,30,使用 INTERSECT ALL 并不会删除重复行,因此最后结果为 20,20,30,30:

代码语言:javascript复制
with d1 as (
  select department_id, department_name
  from   departments
  union all
  select department_id, department_name
  from   departments
)
select department_id, department_name
from   d1
where  department_id <= 30
intersect all
select department_id, department_name
from   d1
where  department_id >= 20
order by 1;

DEPARTMENT_ID DEPARTMENT_NAM
------------- --------------
           20 RESEARCH
           20 RESEARCH
           30 SALES
           30 SALES

SQL>

此时,如果我们替换回 INTERSECT,重复项将再次被删除,结果变为 20,30:

代码语言:javascript复制
with d1 as (
  select department_id, department_name
  from   departments
  union all
  select department_id, department_name
  from   departments
)
select department_id, department_name
from   d1
where  department_id <= 30
intersect
select department_id, department_name
from   d1
where  department_id >= 20
order by 1;

DEPARTMENT_ID DEPARTMENT_NAM
------------- --------------
           20 RESEARCH
           30 SALES

SQL>

EXCEPT

EXCEPT 集合运算符返回第一个查询结果并且减去第二个查询结果的所有行。这在功能上等同于 ANSI 集合运算符 EXCEPT DISTINCT 和 MINUS 运算符。

在下面的示例中,第一个查询将返回部门 10、20、30,但第二个查询返回 20,30,EXPECT 去掉第二个查询结果,最后结果返回 10:

代码语言:javascript复制
select department_id, department_name
from   departments
where  department_id <= 30
except
select department_id, department_name
from   departments
where  department_id >= 20
order by 1;

DEPARTMENT_ID DEPARTMENT_NAM
------------- --------------
           10 ACCOUNTING

1 row selected.

SQL>

EXCEPT ALL

EXCEPT ALL 集合运算符返回第一个查询而不是第二个查询选择的所有行,在功能上等同于 MINUS ALL 运算符。

首先我们需要创建一些重复的行,在以下查询中,我们使用包含 UNION ALL 的 WITH 子句来复制部门表中的行,然后我们查询该重复数据:

代码语言:javascript复制
with d1 as (
  select department_id, department_name
  from   departments
  union all
  select department_id, department_name
  from   departments
)
select department_id, department_name
from   d1
where  department_id <= 30
order by 1;

DEPARTMENT_ID DEPARTMENT_NAM
------------- --------------
           10 ACCOUNTING
           10 ACCOUNTING
           20 RESEARCH
           20 RESEARCH
           30 SALES
           30 SALES

SQL>

在以下查询中,我们使用了 EXCEPT 运算符,第一个查询结果返回 10,10,20,20,30,30,第二个结果返回 20,30,去掉第二个查询结果后,由于 EXCEPT 等同于 MINUS 会自动去重,因此最后返回 10:

代码语言:javascript复制
with d1 as (
  select department_id, department_name
  from   departments
  union all
  select department_id, department_name
  from   departments
)
select department_id, department_name
from   d1
where  department_id <= 30
expect
select department_id, department_name
from   departments
where  department_id >= 20
order by 1;

DEPARTMENT_ID DEPARTMENT_NAM
------------- --------------
           10 ACCOUNTING

SQL>

如果我们将 EXCEPT 切换为 EXCEPT ALL,将不会自动去重,因此最后返回结果为:10,10,20,30:

代码语言:javascript复制
with d1 as (
  select department_id, department_name
  from   departments
  union all
  select department_id, department_name
  from   departments
)
select department_id, department_name
from   d1
where  department_id <= 30
expect all
select department_id, department_name
from   departments
where  department_id >= 20
order by 1;

DEPARTMENT_ID DEPARTMENT_NAM
------------- --------------
           10 ACCOUNTING
           10 ACCOUNTING
           20 RESEARCH
           30 SALES

SQL>

最后我们对两个查询都使用 WITH 子句,由于我们在 EXCEPT ALL 操作的两侧都有重复项,现在我们只看到部门 10 的副本,因为部门 20 和 30 的副本都被删除了:

代码语言:javascript复制
with d1 as (
  select department_id, department_name
  from   departments
  union all
  select department_id, department_name
  from   departments
)
select department_id, department_name
from   d1
where  department_id <= 30
except all
select department_id, department_name
from   d1
where  department_id >= 20
order by 1;

DEPARTMENT_ID DEPARTMENT_NAM
------------- --------------
           10 ACCOUNTING
           10 ACCOUNTING

SQL>

原文最后 expect all 那一段,由于 Tim Hall 的笔误,最后将 expect 写成了 minus,我这里翻译时已经更正!

0 人点赞