SQL UNION 与 UNION ALL 高效使用指南

13次阅读

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

SQL UNION 与 UNION ALL 高效使用指南

UNION 和 UNION ALL 到底该用哪个?

绝大多数时候,该用 UNION ALL。除非你明确需要去重,否则 UNION 不仅慢,还容易因隐式类型转换或 NULL 处理出错。

根本原因在于:UNION 会额外执行去重(DISTINCT)逻辑,相当于对合并结果做一次排序 + 去重;而 UNION ALL 只是简单拼接,零额外开销。

  • 如果两个子查询结果天然无重复(比如查不同日期的订单、不同状态的用户),强行用 UNION 是纯性能浪费
  • UNION 要求所有列的数据类型兼容,遇到 TEXTVARCHAR 混用、或 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 较严格,常需显式 ::textCAST();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 BYLIMIT 只能出现在整个 UNION 语句的最后,不能写在单个分支里。

否则会触发类似 syntax error at or near "ORDER"(PostgreSQL)或 This version of MySQL doesn't yet support'LIMIT & IN/ALL/ANY/SOME subquery' 的错误。

  • 想对某个分支单独排序?不行——UNION 是集合操作,不是管道。必须把那个分支包成子查询再 UNION
  • 需要分页?OFFSETLIMIT 一定要放在最外层,且注意:UNION ALL 结果集未排序,不加 ORDER BY 直接 LIMIT 是不确定行为
  • 性能提示:如果只取前 N 行,且各分支数据量很大,先在分支内 LIMIT N 再 UNION,比 UNION 后再 LIMIT 更快(但结果语义不同,慎用)

嵌套 UNION 容易忽略的优先级问题

SQL 标准中 UNIONUNION 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 ALLUNION),务必用括号明确逻辑边界
  • MySQL 8.0+ 和 PostgreSQL 支持括号,但 SQLite 不支持子查询外层括号,此时只能拆成 CTE 或临时表
  • 复杂场景建议统一用 UNION ALL + 外层 DISTINCT 控制去重时机,反而更可控

真正麻烦的不是语法,而是当业务逻辑依赖“合并后全局去重”,却因括号缺失或执行顺序误解,让数据少了几百条——这种问题上线后极难定位。

text=ZqhQzanResources