MySQL【复合查询】

【MySQL 笔记】复合查询(Compound Queries / Set Operations)详解

在 MySQL 中,“复合查询”一词在不同语境下有两种常见含义:

  1. 广义:任何把多个查询逻辑组合起来的查询(JOIN + 子查询 + 集合运算等)
  2. 狭义(最常指):集合运算(Set Operations),即用 UNIONINTERSECTEXCEPT 等把多个 SELECT 的结果集进行并、交、差操作

今天我们重点讲狭义的复合查询——集合运算(MySQL 8.0+ 已完整支持),这是面试和数据分析中非常高频的内容。

一、MySQL 集合运算符对比表(2026 年现状)

运算符含义是否去重MySQL 支持版本结果特点典型场景
UNION并集所有版本去重后合并合并同类数据(如历史表+新表)
UNION ALL并集(不去重)所有版本保留所有重复行,性能最好日志合并、报表数据累加
INTERSECT交集8.0.31+只保留两边都出现的行找出同时满足 A 和 B 条件的数据
INTERSECT ALL交集(保留重复)8.0.31+保留最小重复次数极少用,精确重复计数场景
EXCEPT差集(A – B)8.0.31+只保留出现在 A、不在 B 的行找出只在 A 表有的记录(差异对比)
EXCEPT ALL差集(不去重)8.0.31+减去对应重复次数精确多对多差集

注意:MySQL 8.0.31 之前只有 UNION / UNION ALLINTERSECTEXCEPT 是 8.0.31(2022 年 10 月)才正式加入的。

二、基本语法结构

SELECT column_list FROM table1 [WHERE ...]
UNION [ALL | DISTINCT]
SELECT column_list FROM table2 [WHERE ...]
[ORDER BY ...]
[LIMIT ...];
  • 多个 SELECT 的列数必须相同
  • 列的数据类型兼容(可隐式转换)
  • 列名以第一个 SELECT 为准
  • ORDER BYLIMIT 只能出现在最后(整个复合查询的末尾)

三、实战示例表(建议自己建表跑)

-- 用户表 A(老数据)
CREATE TABLE users_old (
  id INT PRIMARY KEY,
  name VARCHAR(20),
  city VARCHAR(20)
);

INSERT INTO users_old VALUES
(1,'张三','北京'), (2,'李四','上海'), (3,'王五','广州'), (4,'赵六','北京');

-- 用户表 B(新数据)
CREATE TABLE users_new (
  id INT PRIMARY KEY,
  name VARCHAR(20),
  city VARCHAR(20)
);

INSERT INTO users_new VALUES
(3,'王五','深圳'), (4,'赵六','北京'), (5,'孙七','成都'), (6,'周八','杭州');

四、各种复合查询演示

-- 1. UNION(去重并集)
SELECT name, city FROM users_old
UNION
SELECT name, city FROM users_new;
-- 结果:张三北京、李四上海、王五深圳、赵六北京、孙七成都、周八杭州
-- (王五和赵六只出现一次)

-- 2. UNION ALL(不去重,推荐性能更好)
SELECT name, city FROM users_old
UNION ALL
SELECT name, city FROM users_new;
-- 结果:8 行,王五和赵六各出现两次

-- 3. INTERSECT(交集,找出两表都有的用户)
SELECT name, city FROM users_old
INTERSECT
SELECT name, city FROM users_new;
-- 结果:赵六 北京(因为 city 也必须完全相同)
-- 如果只比 id,可先 SELECT id

-- 4. EXCEPT(差集:老表有、新表没有的)
SELECT name, city FROM users_old
EXCEPT
SELECT name, city FROM users_new;
-- 结果:张三 北京、李四 上海、王五 广州

-- 5. 复杂组合 + 排序(常用于分页报表)
(SELECT name, city, 'old' AS source FROM users_old)
UNION ALL
(SELECT name, city, 'new' AS source FROM users_new)
ORDER BY name
LIMIT 5;

-- 6. 找出“只在新表出现”的用户(新用户)
SELECT name, city FROM users_new
EXCEPT
SELECT name, city FROM users_old;
-- 结果:孙七 成都、周八 杭州

五、高频使用场景总结(面试+实战)

场景推荐写法为什么推荐
历史表 + 当月表 合并报表UNION ALL不想去重,数据量大时性能关键
全量用户列表(去重)UNION天然去重
找出新增用户新表 EXCEPT 老表清晰、直观
找出共同活跃用户INTERSECT8.0.31+ 直接写,很优雅
数据对账 / 差异比对EXCEPT + UNION 组合快速定位只在一边的记录
多数据源合并(日志、爬虫等)UNION ALL + source 列保留原始记录 + 标记来源

六、性能 & 注意事项(2026 年视角)

  1. UNION ALL > UNION:不去重性能远高于去重(去重需要排序+判重)
  2. 列数 & 类型必须一致:否则报错 The used SELECT statements have a different number of columns
  3. ORDER BY 放最后:且只能用列序号第一个 SELECT 的列别名
   ... UNION ... ORDER BY 1 DESC;   -- 第一列
  1. 索引利用:每个 SELECT 单独优化,复合后整体优化有限
  2. 替代方案(老版本或性能敏感时):
  • 交集 → INNER JOINEXISTS
  • 差集 → LEFT JOIN ... WHERE right.id IS NULL
  • 但 8.0.31+ 后,INTERSECT / EXCEPT 可读性更好

下一期预告建议:MySQL 子查询(Subquery)全家桶 + 相关子查询 vs 非相关 + 性能对比

有复合查询写法特别头疼的业务场景(比如多表合并去重、分库分表后聚合等),欢迎留言,我们一起分析~
祝大家复合查询写得又稳又快!📊

文章已创建 5186

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部