Kodewick
第 14 课,共 21 课目录

UNION

第 14 课,共 21 课 · 第 3 部分 · 多张表 · 约 15 分钟

JOIN 把两张表左右接起来,一行变宽。UNION 是上下接:把两条查询的结果摞在一起,行变多,列不变。

把两个结果接起来

网店要给上海的客户和所有员工发一条通知,先要一份名单:

main.sql
1SELECT name FROM customers WHERE city = '上海'2UNION3SELECT name FROM employees;
输出
name
林晓
陈静
周婷
李华
马超
杨帆
黄磊
何琳
高远
9 行

上面一条查出 3 个客户,下面一条查出 6 个员工,UNION 把它们接成一张 9 行的表。两条查询本来各有各的结果,现在只有一个。

重复的只留一个

下面两条查询都查出客户编号:一条是 9 月下过单的客户,一条是上海的客户。

main.sql
1SELECT customer_id FROM orders WHERE order_date < '2025-10-01'2UNION3SELECT id FROM customers WHERE city = '上海'4ORDER BY customer_id;
输出
customer_id
1
2
3
4
8
5 行

9 月的订单有 4 个,其中两个是 1 号客户的;上海的客户是 1、3、8 号。接起来本该有 7 个编号,UNION 把重复的都去掉了,只剩 5 个。它不光去掉两边之间重复的,一边自己重复的也去掉。

还有两点:

  • 结果的列名用第一条查询的:这里是 customer_id,下面那条的 id 不算。
  • ORDER BY 只写一次,写在最后,排的是接好的整张表。

UNION ALL

UNION ALL 不去重,两边的行一行不少全部留下。

先猜一猜

结果有几行?

1SELECT customer_id FROM orders WHERE order_date < '2025-10-01'2UNION ALL3SELECT id FROM customers WHERE city = '上海'4ORDER BY customer_id;
main.sql
1SELECT customer_id FROM orders WHERE order_date < '2025-10-01'2UNION ALL3SELECT id FROM customers WHERE city = '上海'4ORDER BY customer_id;
输出
customer_id
1
1
1
2
3
4
8
7 行

去重要把所有行比一遍,表大了会慢。确定两边不会重复,或者重复的也要留下,就用 UNION ALL。

列数要一样

两条查询的列数不一样,就接不起来:

main.sql
1SELECT name, city FROM customers2UNION3SELECT name FROM employees;
输出
ERROR 1222 (21000): The used SELECT statements have a different number of columns

UNION 前后的 SELECT 选出来的列数要一样多。

ERROR 1222 是说两条 SELECT 的列数不同。员工表没有城市,可以在下面补一个 NULL:SELECT name, NULL FROM employees。

列是按位置对的,不看名字。顺序写反了,MySQL 不会报错,只会把城市接到名字下面:

main.sql
1SELECT name, city FROM customers WHERE id = 12UNION3SELECT city, name FROM customers WHERE id = 2;
输出
namecity
林晓上海
北京王磊
2 行

所以两边同一个位置的列,要是同一种东西。

做一张时间线

UNION 常用来把不同的事情放进同一张表。下面把「新客户注册」和「下单」放在一起,按日期排成一条时间线。每条查询加一列固定的文字,说明这一行是哪种事:

main.sql
1SELECT joined AS day, name, '注册' AS event2FROM customers3WHERE joined >= '2025-08-01'4UNION ALL5SELECT o.order_date, c.name, '下单'6FROM orders AS o7JOIN customers AS c ON c.id = o.customer_id8WHERE o.order_date < '2025-10-01'9ORDER BY day;
输出
daynameevent
2025-08-30吴昊注册
2025-09-02林晓下单
2025-09-05王磊下单
2025-09-14郑雪注册
2025-09-18林晓下单
2025-09-21张伟下单
6 行

列名 day、event 都在第一条里起,第二条不用再写别名。每条查询可以有自己的 JOIN、WHERE;ORDER BY day 排的是整张时间线。

小技巧: 同一张表上的两个条件,用 OR 往往更简单。UNION 适合两边来自不同的表,或者查法很不一样的时候。

练一练

写好查询,点检查。提示随便看,实在想不出来再看答案。

练习 1

仓库要留意的商品

查出仓库要留意的商品编号:在待发货订单里的商品,和库存少于 20 的商品。一列 product_id,不重复,从小到大。

要显示
product_id
1
2
3
5
6
8
10
7 行
exercise1.sql
1SELECT oi.product_id2FROM order_items AS oi3JOIN orders AS o ON o.id = oi.order_id4WHERE o.status = '待发货';
输出
点「运行」或按 Ctrl+Enter
练习 2

修好它:列数

想要一份名单:广州的客户,加上职位是「销售」的员工,只要一列 name。这条查询报错了,改好它。

要显示
name
张伟
孙强
黄磊
何琳
4 行
exercise2.sql
1SELECT name, city FROM customers WHERE city = '广州'2UNION ALL3SELECT name FROM employees WHERE title = '销售';
输出
点「运行」或按 Ctrl+Enter
练习 3

资料不全的客户

找出资料不全的客户:没留邮箱的,和没下过单的。显示 id、name、problem 三列,problem 写 没邮箱 或 没下单;两样都缺的客户出现两次。按 id 排,id 一样的再按 problem 排。

要显示
idnameproblem
3陈静没邮箱
6赵敏没下单
6赵敏没邮箱
9吴昊没下单
9吴昊没邮箱
10郑雪没下单
6 行
exercise3.sql
1SELECT id, name, '没邮箱' AS problem2FROM customers3WHERE email IS NULL;
输出
点「运行」或按 Ctrl+Enter

「没下单」排在「没邮箱」前面,因为汉字按编码比,不是按拼音。

这一课学会了

  • UNION 把两条查询的结果上下接起来,去掉重复的行;UNION ALL 全部保留,也更快。
  • 两边列数要一样,否则 ERROR 1222;列按位置对,不看名字。
  • 结果的列名用第一条查询的;ORDER BY 写在最后,排整张表。
  • 加一列固定的文字,能看出每一行来自哪一边。
  • 同一张表上的条件用 OR 就好;UNION 适合不同的表、不同的查法。