绝大多数时候该用 union all,因其无去重开销、性能高且避免隐式转换和 null 处理问题;仅当明确需要去重时才用 union。

UNION 和 UNION ALL 到底该用哪个?
绝大多数时候,该用 UNION ALL。除非你明确需要去重,否则 UNION 不仅慢,还容易因隐式类型转换或 NULL 处理出错。
根本原因在于:UNION 会额外执行去重(DISTINCT)逻辑,相当于对合并结果做一次排序 + 去重;而 UNION ALL 只是简单拼接,零额外开销。
- 如果两个子查询结果天然无重复(比如查不同日期的订单、不同状态的用户),强行用
UNION是纯性能浪费 -
UNION要求所有列的数据类型兼容,遇到TEXT和VARCHAR混用、或JSON列在某些 MySQL 版本中可能直接报错Operand should contain 1 column(s) - PostgreSQL 中
UNION对 NULL 的判等更严格,可能导致本该合并的行被当成不同行保留下来
列名和顺序不一致导致 UNION 失败
UNION 系列操作只看「位置」,不看列名。第一行 SELECT 的列名决定最终结果集的列名,后续 SELECT 必须列数相同、对应位置类型尽量兼容。
常见错误现象:column "xxx" does not exist(别名没生效)、UNION types text and integer cannot be matched(类型冲突)。
- 别名只需写在第一个 SELECT,后面 SELECT 不能加
AS别名——写了也无效,还可能被某些数据库(如 SQL Server)报语法错 - 类型不一致时,数据库会尝试隐式转换:PostgreSQL 较严格,常需显式
::text或CAST();MySQL 更宽松但可能截断或精度丢失 - 推荐做法:所有 SELECT 都用相同顺序、显式转换类型、补
NULL::text占位(如某分支没有该字段)
示例:
SELECT id::text, name FROM users UNION ALL SELECT CAST(order_id AS text), NULL::text FROM orders
ORDER BY 和 LIMIT 怎么加才不报错?
ORDER BY 和 LIMIT 只能出现在整个 UNION 语句的最后,不能写在单个分支里。
否则会触发类似 syntax error at or near "ORDER"(PostgreSQL)或 This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' 的错误。
- 想对某个分支单独排序?不行——UNION 是集合操作,不是管道。必须把那个分支包成子查询再 UNION
- 需要分页?
OFFSET和LIMIT一定要放在最外层,且注意:UNION ALL 结果集未排序,不加 ORDER BY 直接 LIMIT 是不确定行为 - 性能提示:如果只取前 N 行,且各分支数据量很大,先在分支内
LIMIT N再 UNION,比 UNION 后再 LIMIT 更快(但结果语义不同,慎用)
嵌套 UNION 容易忽略的优先级问题
SQL 标准中 UNION 和 UNION ALL 优先级相同,**从左到右依次执行**,没有隐含括号。这会导致意料之外的执行顺序。
比如 A UNION ALL B UNION C 等价于 (A UNION ALL B) UNION C,而非 A UNION ALL (B UNION C)。一旦 B UNION C 有去重需求,而 A UNION ALL B 先算,就可能漏掉本该合并的行。
- 只要涉及混合使用(尤其是
UNION ALL和UNION),务必用括号明确逻辑边界 - MySQL 8.0+ 和 PostgreSQL 支持括号,但 SQLite 不支持子查询外层括号,此时只能拆成 CTE 或临时表
- 复杂场景建议统一用
UNION ALL+ 外层DISTINCT控制去重时机,反而更可控
真正麻烦的不是语法,而是当业务逻辑依赖“合并后全局去重”,却因括号缺失或执行顺序误解,让数据少了几百条——这种问题上线后极难定位。










