Skip to content

UNION 与集合操作:合并结果 ​

UNION 基础 ​

UNION 垂直合并两个或多个 SELECT 结果集。

基本语法:

sql
SELECT columns FROM table1
UNION
SELECT columns FROM table2;

示例:合并客户和供应商 ​

sql
-- 创建供应商表
CREATE TABLE suppliers (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50),
    email VARCHAR(100)
);

INSERT INTO suppliers (name, email) VALUES
    ('TechCorp', 'contact@techcorp.com'),
    ('Alice', 'alice@supplier.com'),  -- 与 users 中的 Alice 重名
    ('Global Parts', 'sales@globalparts.com');

-- 合并用户和供应商的姓名
SELECT name FROM users
UNION
SELECT name FROM suppliers;

结果:

text
     name
--------------
 Alice        -- 去重:只出现一次
 Bob
 Charlie
 Diana
 Global Parts
 TechCorp

关键点:

  • UNION 自动去重
  • 结果按某种顺序排列(不保证)

UNION ALL:保留重复 ​

UNION ALL 不去重,保留所有行。

sql
SELECT name FROM users
UNION ALL
SELECT name FROM suppliers;

结果:

text
     name
--------------
 Alice
 Bob
 Charlie
 Diana
 TechCorp
 Alice        -- 重复出现
 Global Parts

UNION vs UNION ALL:

  • UNION:去重(需要排序和比较,较慢)
  • UNION ALL:保留重复(更快)

推荐:如果确定没有重复或需要保留重复,使用 UNION ALL。

UNION 的规则 ​

规则 1:列数必须相同 ​

sql
-- 错误:列数不匹配
SELECT name FROM users
UNION
SELECT name, email FROM suppliers;  -- 2 列 vs 1 列

错误信息:

text
ERROR:  each UNION query must have the same number of columns

规则 2:列类型必须兼容 ​

sql
-- 正确:VARCHAR 和 VARCHAR
SELECT name FROM users
UNION
SELECT name FROM suppliers;

-- 正确:INT 和 INT
SELECT id FROM users
UNION
SELECT id FROM suppliers;

-- 错误:VARCHAR 和 INT
SELECT name FROM users
UNION
SELECT id FROM suppliers;

类型必须兼容或可隐式转换。

规则 3:列名来自第一个 SELECT ​

sql
SELECT name AS user_name FROM users
UNION
SELECT name AS supplier_name FROM suppliers;

结果列名是 user_name(第一个 SELECT 的列名)。

规则 4:ORDER BY 放在最后 ​

sql
SELECT name FROM users
UNION
SELECT name FROM suppliers
ORDER BY name;  -- ORDER BY 放在最后,作用于整个结果

不能在每个 SELECT 中单独排序(会被忽略):

sql
-- 无效:ORDER BY 会被忽略
SELECT name FROM users ORDER BY name
UNION
SELECT name FROM suppliers ORDER BY name;

实际应用场景 ​

场景 1:合并不同来源的数据 ​

sql
-- 合并本地订单和在线订单
SELECT order_id, customer_name, 'Local' AS source
FROM local_orders
UNION ALL
SELECT order_id, customer_name, 'Online' AS source
FROM online_orders;

场景 2:生成报表 ​

sql
-- 合并实际数据和汇总行
SELECT product, SUM(amount) AS total
FROM orders
GROUP BY product
UNION ALL
SELECT '总计', SUM(amount)
FROM orders
ORDER BY product;

结果:

text
  product   |  total
------------+---------
 Headphones |   50.00
 Keyboard   |   80.00
 Laptop     | 1200.00
 Monitor    |  300.00
 Mouse      |   25.00
 总计       | 1655.00

场景 3:处理历史数据 ​

sql
-- 合并当前表和归档表
SELECT * FROM current_orders
WHERE order_date >= '2026-01-01'
UNION ALL
SELECT * FROM archived_orders
WHERE order_date >= '2026-01-01';

INTERSECT:交集 ​

INTERSECT 返回两个结果集的交集(同时存在于两个查询中的行)。

PostgreSQL 支持,MySQL 不支持。

sql
-- 既是用户又是供应商的姓名
SELECT name FROM users
INTERSECT
SELECT name FROM suppliers;

结果:

text
 name
-------
 Alice

MySQL 替代方案(使用 INNER JOIN):

sql
SELECT DISTINCT u.name
FROM users u
INNER JOIN suppliers s ON u.name = s.name;

EXCEPT:差集 ​

EXCEPT 返回第一个结果集中存在但第二个结果集中不存在的行。

PostgreSQL 支持,MySQL 不支持(MySQL 用 MINUS,但也不支持)。

sql
-- 是用户但不是供应商的姓名
SELECT name FROM users
EXCEPT
SELECT name FROM suppliers;

结果:

text
  name
---------
 Bob
 Charlie
 Diana

MySQL 替代方案(使用 NOT IN 或 NOT EXISTS):

sql
SELECT name FROM users
WHERE name NOT IN (SELECT name FROM suppliers);

多个集合操作 ​

可以组合多个 UNION、INTERSECT、EXCEPT:

sql
-- 示例:A UNION B EXCEPT C
SELECT name FROM table_a
UNION
SELECT name FROM table_b
EXCEPT
SELECT name FROM table_c;

优先级:INTERSECT > UNION/EXCEPT,从左到右执行。

使用括号明确优先级(部分数据库支持):

sql
(SELECT name FROM table_a UNION SELECT name FROM table_b)
EXCEPT
SELECT name FROM table_c;

UNION 与 JOIN 的区别 ​

特性UNIONJOIN
方向垂直合并(增加行)水平合并(增加列)
列数必须相同可以不同
去重UNION 去重需要 DISTINCT
用途合并相似数据组合关联数据

示例对比:

sql
-- UNION:合并姓名(垂直)
SELECT name FROM users
UNION
SELECT name FROM suppliers;
-- 结果:1 列,多行

-- JOIN:组合用户和订单(水平)
SELECT u.name, o.product
FROM users u
INNER JOIN orders o ON u.id = o.user_id;
-- 结果:2 列,多行

性能考虑 ​

  • UNION:需要去重(排序),较慢
  • UNION ALL:不去重,更快
  • 大结果集:优先使用 UNION ALL
  • 索引:在 UNION 的源表列上创建索引可以提升性能

查看执行计划:

sql
EXPLAIN SELECT name FROM users
UNION
SELECT name FROM suppliers;

常见错误 ​

错误 1:列数不匹配 ​

sql
-- 错误
SELECT name FROM users
UNION
SELECT name, email FROM suppliers;

错误 2:ORDER BY 位置错误 ​

sql
-- 错误:ORDER BY 在 UNION 之前
SELECT name FROM users ORDER BY name
UNION
SELECT name FROM suppliers;

正确:

sql
SELECT name FROM users
UNION
SELECT name FROM suppliers
ORDER BY name;

错误 3:混淆 UNION 和 JOIN ​

sql
-- 错误意图:想要用户和订单的组合
SELECT name FROM users
UNION
SELECT product FROM orders;
-- 这只是合并姓名和产品名(无关联)

-- 正确:应该用 JOIN
SELECT u.name, o.product
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

练习 ​

  1. 基本 UNION:合并 users 和 suppliers 的邮箱地址(去重)。

  2. UNION ALL:合并 users 和 suppliers 的姓名(保留重复)。

  3. 添加来源标识:合并时添加一列标识数据来源('User' 或 'Supplier')。

  4. 带排序:合并 users 和 suppliers 的姓名,按字母顺序排序。

  5. INTERSECT:查询既是用户又是供应商的邮箱地址(PostgreSQL)。

  6. EXCEPT:查询是用户但不是供应商的姓名(PostgreSQL)。

  7. MySQL 替代:用 JOIN 和 NOT IN 实现 INTERSECT 和 EXCEPT 的效果。

  8. 实际应用:创建一个销售报表,包含每个产品的销售额和一个总计行。

完成练习后,打开本章答案页核对。

本章小结 ​

第三章学习了多表操作:

  • INNER JOIN:组合匹配的行
  • LEFT JOIN:保留左表所有行
  • 子查询:嵌套查询逻辑
  • EXISTS:高效检查存在性
  • UNION:合并结果集

这些是关系数据库的核心能力。第四章将学习窗口函数和高级查询。

回到第三章概览 · 返回SQL 学习路线

用心记录每一步成长