UNION ALL 本身不支持去重,需用嵌套查询包裹后加 DISTINCT 或 GROUP BY 实现;后者更可控,尤其处理 NULL、大文本或 JSON 字段时,且须为子查询显式指定别名。

UNION ALL 本身不支持去重,必须用嵌套查询包裹
UNION ALL 的设计目标就是“不检查重复、直接拼接”,它比 UNION 快正是因为跳过了去重逻辑。所以想在 UNION ALL 后去重,不能靠修改 UNION ALL 本身,而要把整个 UNION ALL 结果作为子查询,再用 DISTINCT 或 GROUP BY 处理。
常见错误是试图写成:SELECT * FROM A UNION ALL SELECT * FROM B DISTINCT —— 这语法非法,DISTINCT 不能放在 UNION ALL 后面。
- 正确结构是:
SELECT DISTINCT * FROM (SELECT ... UNION ALL SELECT ...) AS t - 如果字段多或含大文本(如
TEXT、JSON),DISTINCT可能报错(MySQL 8.0+ 对不可排序类型有限制),此时改用GROUP BY显式列出所有字段更稳妥 - 注意别名
AS t在大多数数据库中是强制要求的(如 MySQL、PostgreSQL),否则会提示 “every derived table must have its own alias”
用 GROUP BY 替代 DISTINCT 更可控,尤其涉及 NULL 或排序敏感字段时
当结果集中有 NULL 值、或某些字段语义上“相同但字面不同”(比如大小写不敏感的字符串、带空格的数值),DISTINCT 可能无法按业务预期去重。GROUP BY 允许你指定聚合逻辑,也更容易加 ORDER BY 或过滤条件。
- 示例(PostgreSQL/MySQL):
SELECT col1, col2 FROM (SELECT col1, col2 FROM A UNION ALL SELECT col1, col2 FROM B) AS t GROUP BY col1, col2 - 如果字段含
JSON或ARRAY(如 PostgreSQL),DISTINCT会直接报错,GROUP BY同样不支持,此时需先用函数标准化(如jsonb_normalize或TRIM+LOWER)再分组 -
GROUP BY在部分数据库(如 SQL Server)中对TEXT/NTEXT字段有限制,需转为VARCHAR(MAX)再操作
性能差异明显:UNION ALL + DISTINCT 几乎等于 UNION,但更难优化
表面上看,(SELECT ... UNION ALL SELECT ...) DISTINCT 和 SELECT ... UNION SELECT ... 效果一样,但执行计划往往不同。优化器通常无法把外层 DISTINCT 下推到 UNION ALL 子句中,导致全量结果先拼出来再扫一遍去重 —— 数据量大时内存和临时表压力远高于原生 UNION。
- 查执行计划时重点关注
Using temporary; Using filesort是否出现在外层查询 - 如果两个子查询各自已排好序且字段可比较(如都是按
id ASC),可用MERGE类算法去重,但目前仅 SQLite 原生支持,其他数据库需手动实现归并逻辑 - 真正的大数据场景(千万级以上),应优先考虑在应用层合并,或用物化视图/中间表预处理,避免每次查询都做全量去重
跨数据库兼容写法要注意 NULL 处理和字段别名规则
不同数据库对 NULL 在 GROUP BY / DISTINCT 中的行为一致,但子查询别名、字段引用方式有差异。比如 SQL Server 要求所有字段在 GROUP BY 中显式出现,而 MySQL 5.7+ 开启 ONLY_FULL_GROUP_BY 后也强制如此;PostgreSQL 则严格要求 SELECT 中非聚合字段必须出现在 GROUP BY 中。
- 安全写法:始终给子查询加别名,且
GROUP BY列表与SELECT字段一一对应(哪怕字段名相同) - 避免写
SELECT * FROM (...) AS t GROUP BY *—— 所有主流数据库都不支持GROUP BY * - 如果字段来自不同子查询且同名(如都叫
id),务必在UNION ALL中统一别名,否则外层GROUP BY会报歧义
实际去重逻辑是否成立,取决于你定义“重复”的粒度——是整行完全相同,还是某几个关键字段相同。这点常被忽略,但直接影响该用 DISTINCT 还是 GROUP BY,以及是否需要提前清洗字段。










