UNION
JOIN 把两张表左右接起来,一行变宽。UNION 是上下接:把两条查询的结果摞在一起,行变多,列不变。
把两个结果接起来
网店要给上海的客户和所有员工发一条通知,先要一份名单:
1SELECT name FROM customers WHERE city = '上海'2UNION3SELECT name FROM employees;
| name |
|---|
| 林晓 |
| 陈静 |
| 周婷 |
| 李华 |
| 马超 |
| 杨帆 |
| 黄磊 |
| 何琳 |
| 高远 |
上面一条查出 3 个客户,下面一条查出 6 个员工,UNION 把它们接成一张 9 行的表。两条查询本来各有各的结果,现在只有一个。
重复的只留一个
下面两条查询都查出客户编号:一条是 9 月下过单的客户,一条是上海的客户。
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 |
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;
UNION ALL 什么都不去掉:上面一条有 4 行(1 号出现两次),下面一条有 3 行,一共 7 行。1 号客户会出现 3 次。
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 |
去重要把所有行比一遍,表大了会慢。确定两边不会重复,或者重复的也要留下,就用 UNION ALL。
列数要一样
两条查询的列数不一样,就接不起来:
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 不会报错,只会把城市接到名字下面:
1SELECT name, city FROM customers WHERE id = 12UNION3SELECT city, name FROM customers WHERE id = 2;
| name | city |
|---|---|
| 林晓 | 上海 |
| 北京 | 王磊 |
所以两边同一个位置的列,要是同一种东西。
做一张时间线
UNION 常用来把不同的事情放进同一张表。下面把「新客户注册」和「下单」放在一起,按日期排成一条时间线。每条查询加一列固定的文字,说明这一行是哪种事:
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;
| day | name | event |
|---|---|---|
| 2025-08-30 | 吴昊 | 注册 |
| 2025-09-02 | 林晓 | 下单 |
| 2025-09-05 | 王磊 | 下单 |
| 2025-09-14 | 郑雪 | 注册 |
| 2025-09-18 | 林晓 | 下单 |
| 2025-09-21 | 张伟 | 下单 |
列名 day、event 都在第一条里起,第二条不用再写别名。每条查询可以有自己的 JOIN、WHERE;ORDER BY day 排的是整张时间线。
小技巧: 同一张表上的两个条件,用 OR 往往更简单。UNION 适合两边来自不同的表,或者查法很不一样的时候。
练一练
写好查询,点检查。提示随便看,实在想不出来再看答案。
仓库要留意的商品
✓查出仓库要留意的商品编号:在待发货订单里的商品,和库存少于 20 的商品。一列 product_id,不重复,从小到大。
| product_id |
|---|
| 1 |
| 2 |
| 3 |
| 5 |
| 6 |
| 8 |
| 10 |
1SELECT oi.product_id2FROM order_items AS oi3JOIN orders AS o ON o.id = oi.order_id4WHERE o.status = '待发货';
修好它:列数
✓想要一份名单:广州的客户,加上职位是「销售」的员工,只要一列 name。这条查询报错了,改好它。
| name |
|---|
| 张伟 |
| 孙强 |
| 黄磊 |
| 何琳 |
1SELECT name, city FROM customers WHERE city = '广州'2UNION ALL3SELECT name FROM employees WHERE title = '销售';
资料不全的客户
✓找出资料不全的客户:没留邮箱的,和没下过单的。显示 id、name、problem 三列,problem 写 没邮箱 或 没下单;两样都缺的客户出现两次。按 id 排,id 一样的再按 problem 排。
| id | name | problem |
|---|---|---|
| 3 | 陈静 | 没邮箱 |
| 6 | 赵敏 | 没下单 |
| 6 | 赵敏 | 没邮箱 |
| 9 | 吴昊 | 没下单 |
| 9 | 吴昊 | 没邮箱 |
| 10 | 郑雪 | 没下单 |
1SELECT id, name, '没邮箱' AS problem2FROM customers3WHERE email IS NULL;
「没下单」排在「没邮箱」前面,因为汉字按编码比,不是按拼音。
这一课学会了
UNION把两条查询的结果上下接起来,去掉重复的行;UNION ALL全部保留,也更快。- 两边列数要一样,否则
ERROR 1222;列按位置对,不看名字。 - 结果的列名用第一条查询的;
ORDER BY写在最后,排整张表。 - 加一列固定的文字,能看出每一行来自哪一边。
- 同一张表上的条件用
OR就好;UNION适合不同的表、不同的查法。