UNION 合并去重、UNION ALL 合并不去重、INTERSECT 取交集、EXCEPT 取差集。
三条硬规矩:
列数必须相同、类型要兼容、列名只看第一个 select。 order by 只能写在整段最后。
UNION 去重要排序,所以比 UNION ALL 慢——不需要去重时一律用 UNION ALL。
上一篇说「集合运算改行、连接改列」,这一篇把改行这件事讲清。 它的语法比连接简单得多,但规矩更死—— 列数、类型、排序位置三条错一条就报错。 好处是:报错得很干脆,不容易留坑。
一、union:合并并去重
三个班级名 + 十二个性别,去重之后剩五行:一班、三班、二班、女、男。
「去重」是整行去重:两个结果里完全一样的行只留一份。
所以 UNION 实际做了两件事:摞起来、再去掉重复。
代价是它得排序(或者建哈希表)才知道哪些是重复的,
所以 UNION 比 UNION ALL 慢,数据量大时差好几倍。
二、union all:合并但不去重
15 行:3 + 12,一行不少。 这是两者的唯一区别,但影响很大。
「确定没有重复」或者「重复也要留着」时,一律用 UNION ALL。
只有真的需要去重,才用 UNION。
一个容易忽略的点:两个 select 之间如果本来就不可能有重复,
用 UNION 等于白花一次排序的钱。 判断依据是「两边取的是不是同一类东西」——
班级名和性别混着摞,本来就不可能重复。
三、intersect 与 except:交集与差集
INTERSECT 给「两边都有」的:学生表里的班级号 ∩ 班级表的编号 = 1、2、3。
EXCEPT 给「左边有、右边没有」的:三个班都有学生,所以是空。
EXCEPT 有方向:A EXCEPT B 和 B EXCEPT A 完全不一样。
这一点和 INTERSECT 不同——交集两边一样。
实际工作中的用法:
「找出没有下过单的用户」SELECT id FROM users EXCEPT SELECT user_id FROM orders,
「找出两张表都有的人」用 INTERSECT。
注意 EXCEPT 那个需求也能用左外连接加 IS NULL 写,两条路都对,看哪个读起来更清楚。
四、三条硬规矩
一、列数必须相同。 左边两列、右边一列,报 do not have the same number of result columns。
二、对应列的类型要兼容。 数字和文本混着摞,
有的数据库报错、有的悄悄转换—— 别依赖这个转换,它会让结果变得不可预料。
三、列名只看第一个 select。 后面那些 select 里的别名不起作用,
排序和取值都按第一个的列名来。 所以习惯上把「想要的名字」写在第一个 select 上。
五、order by 只能写在最后
SELECT … ORDER BY … UNION SELECT … 报 ORDER BY clause should come after UNION not before。
中间那几个 select 不许单独排序。
要排只能整段摞完之后排一次。 这也合理:
摞起来之后行已经混在一起了,「前半段排过序」没有意义。
但如果真需要「每段各自排序再摞」怎么办:
套一层子查询——SELECT * FROM (SELECT … ORDER BY … LIMIT 3) UNION ALL SELECT * FROM (…)。
注意子查询里得带 LIMIT,否则优化器会把 ORDER BY 优化掉。
六、什么时候该用集合运算
典型场景:两张结构相同、来源不同的表要合并统计。
比如「线上订单」和「门店订单」,字段一样,要出月度总报表—— UNION ALL 摞起来再聚合。
另一种是「多条件取并集」:
一个 where 里 or 写太长、走不了索引时,拆成两个 select 再 UNION 反而更快。
这是优化手段,不是语法要求,别一开始就这么写。
七、动手 Demo:并集去重与不去重、交集差集、三条规矩各撞一次
Demo 六段:UNION 与 UNION ALL 的行数对照、INTERSECT、EXCEPT、列数不同报错、
ORDER BY 位置报错,最后按名字排序看结果。
第二段那个 15 和第一段那个 5 是全篇的核心对照。
import _q46 as Q
c = Q.con()
# 并集:去重
Q.show(c, "SELECT name FROM classes UNION SELECT gender FROM students")
# 并集:不去重
Q.show(c, "SELECT COUNT(*) AS n FROM (SELECT name FROM classes "
"UNION ALL SELECT gender FROM students)")
# 交集与差集
Q.show(c, "SELECT class_id FROM students INTERSECT SELECT id FROM classes")
Q.show(c, "SELECT id FROM classes EXCEPT SELECT class_id FROM students")
# 列数不同直接报错;order by 只能写在最后
c.execute("SELECT id, name FROM classes UNION SELECT name FROM classes")
c.execute("SELECT name FROM classes ORDER BY id "
"UNION SELECT name FROM students")
Q.show(c, "SELECT name FROM classes UNION SELECT name FROM students "
"ORDER BY name")
实跑输出:
--------------------------------------------------------------------
第五十三课:集合运算——把两个结果上下摞起来
--------------------------------------------------------------------
union:合并并去重
SQL> SELECT name FROM classes UNION SELECT gender FROM students
name
-----
一班
三班
二班
女
男
(5 行)
**三行班级名 + 十二行性别,去重之后剩五行**:一班、三班、二班、女、男。
**注意「去重」是整行去重**:两个结果里完全一样的行只留一份。
**所以 `UNION` 会偷偷排一次序,行数少、速度慢。**
union all:合并但不去重
SQL> SELECT COUNT(*) AS n
FROM (SELECT name FROM classes UNION ALL SELECT gender FROM students)
n
---
15
(1 行)
**15 行:3 + 12,一行不少。** **这是两者的唯一区别,但影响很大**——
**去重要排序,数据量大时 `UNION` 可能比 `UNION ALL` 慢好几倍。**
**「确定没有重复」或者「重复也要留着」时,一律用 `UNION ALL`。**
**只有真的需要去重,才用 `UNION`。**
intersect 与 except:交集与差集
SQL> SELECT class_id FROM students INTERSECT SELECT id FROM classes
class_id
---------
1
2
3
(3 行)
**`INTERSECT` 给「两边都有」的:1、2、3。** **`EXCEPT` 给「左边有、右边没有」的:这里是空。**
SQL> SELECT id FROM classes EXCEPT SELECT class_id FROM students
id
---
(0 行)
**空的原因是三个班都有学生。** **想看到结果,加一个没学生的班再跑一次就行。**
**注意 `EXCEPT` 有方向**:`A EXCEPT B` 和 `B EXCEPT A` 完全不一样。
**这一点和 `INTERSECT` 不同**,交集两边一样。
三条硬规矩
**一、列数必须相同。**
`SELECT id, name FROM classes UNION SELECT name FROM classes` **直接报错。**
**报错文字是 `do not have the same number of result columns`**——
**左边两列、右边一列,摞不起来。**
**二、对应列的类型要兼容。** 数字和文本混着摞,
**有的数据库报错,有的悄悄转换**——**别依赖这个转换。**
**三、列名只看第一个 `select`。** 后面那些 `select` 里的别名不起作用,
**排序和取值都按第一个的列名来。**
order by 只能写在最后
SQL> SELECT name FROM classes ORDER BY id UNION SELECT name FROM students
报错:ORDER BY clause should come after UNION not before
**报 `ORDER BY clause should come after UNION not before`。**
**中间那几个 `select` 不许单独排序**,**要排只能整段摞完之后排一次。**
SQL> SELECT name FROM classes UNION SELECT name FROM students ORDER BY name
name
-----
一班
三班
二班
刘洋
吴倩
周涛
孙悦
张伟
徐明
李娜
杨帆
王芳
赵敏
陈静
黄磊
(15 行)
**按中文排序出来是「一班、三班、二班」**——
**因为它比的是编码,不是笔画也不是拼音。**
**这是第 3 章讲过的排序规则问题,在集合运算里一样存在。**
什么时候该用集合运算
**典型场景:两张结构相同、来源不同的表要合并统计。**
**比如「线上订单」和「门店订单」两张表,字段一样,要出一个月度总报表**——
`UNION ALL` 摞起来再聚合。
**另一种是「多条件取并集」**:
**一个 `where` 里 `or` 写太长、走不了索引时,拆成两个 `select` 再 `UNION` 反而更快。**
**这是优化手段,不是语法要求,别一开始就这么写。**
第一段 UNION 给 5 行,去重后的结果。
第二段 UNION ALL 给 15 行,3 + 12 一行不少。
第三段 INTERSECT 给 1、2、3;EXCEPT 给 0 行,因为三个班都有学生。
第四段列数不同直接报错,第五段 ORDER BY 写在中间也报错。
第六段按名字排序:一班、三班、二班——比的是编码,不是笔画。
30 秒实验
SELECT name FROM classes UNION SELECT gender FROM students给几行?换成UNION ALL呢?- 为什么
UNION比UNION ALL慢? A EXCEPT B和B EXCEPT A结果一样吗?INTERSECT呢?
常见误区
- 该用
UNION ALL的地方用UNION。 白花一次去重排序的钱,数据量大时慢好几倍。 - 在中间的
select里写ORDER BY。 直接报错,要排只能写在整段最后,或者套子查询。 - 靠两边列的别名排序。 列名只看第一个
select,后面的别名不起作用。
自测三问(点开对答案)
1. `UNION` 和 `UNION ALL` 的唯一区别是什么?为什么这个区别重要? —— `UNION` 去重、`UNION ALL` 不去重。去重要排序,所以 `UNION` 慢得多。 2. 集合运算的三条硬规矩是什么? —— 列数必须相同、对应列类型要兼容、列名只看第一个 `select`。另外 `ORDER BY` 只能写在最后。 3. `EXCEPT` 和 `INTERSECT` 在「方向」上有什么区别? —— `EXCEPT` 有方向,`A EXCEPT B` 和 `B EXCEPT A` 不同;`INTERSECT` 没有方向,两边一样。上下摞讲完了,下一篇开始左右拼:10.3 内连接——最常用的一种连接。
- ☐ (本篇完,下一篇 10.3 内连接)