☰
PostgreSQL 中的 UNION 与 UNION ALL:合并结果集时如何控制重复行
2026/10/8 1:27:41 网站建设 项目流程
  • 文档
  • 教程
  • 知识库

【免费下载链接】til

:memo: Today I Learned

项目地址:https://gitcode.com/gh_mirrors/ti/til
点击查看免费下载

在 PostgreSQL 中,UNION与UNION ALL都是将两个查询(或表)的结果集纵向拼接成单个结果集的操作符,两者的唯一区别在于是否保留重复行:UNION会去重,UNION ALL会保留全部重复行。本文以 til 仓库中 union-all-rows-including-duplicates.md 为核心,结合仓库内其他 PostgreSQL 笔记,完整演示这两种操作符的行为差异、适用场景,并延伸到与之协同使用的VALUES、generate_series()、CTE 等集合构造技巧,读完即可在实际查询中正确选择去重与否的合并方式。

用 UNION 合并结果集:默认去重

两张表或两个查询的结果集可以用UNION操作符合并为一个结果集。合并时,所有重复行都会被自动移除,最终结果中每条记录只出现一次。

下面的例子把两段generate_series(1,4)与generate_series(3,6)产生的序列合并:

select generate_series(1,4) union select generate_series(3,6) order by 1 asc; generate_series ----------------- 1 2 3 4 5 6 (6 rows)

注意观察:左半边包含 1、2、3、4,右半边包含 3、4、5、6。尽管两侧各自都有 3 和 4,但在UNION的结果里,3 和 4 各只出现一次——这正是UNION的去重语义在起作用,3和4这两条重复记录被折叠为单条。

去重的实现代价

UNION之所以能去重,是因为 PostgreSQL 在执行时需要比较结果集里的每一行、剔除重复项。从实现原理看,这通常意味着对合并后的结果做一次去重排序(sort/unique 或 hash unique)。因此,当两侧结果集本身很大、且并不需要去重时,UNION会产生不必要的计算开销。这也是引入UNION ALL的根本原因。

用 UNION ALL 合并结果集:保留全部重复行

如果不想排除重复行,改用UNION ALL。它在合并时不做任何去重,两侧产生的每一行都会被原样保留在结果集中:

select generate_series(1,4) union all select generate_series(3,6) order by 1 asc; generate_series ----------------- 1 2 3 3 4 4 5 6 (8 rows)

同样的两个序列,这次得到 8 行而非 6 行:3 和 4 各自出现了两次,分别来自两侧输入。可以看到UNION ALL的结果正是"两个结果集的简单拼接",语义上等价于把两个查询的输出直接堆叠在一起。

UNION 与 UNION ALL 行为对比

行为UNIONUNION ALL
重复行处理自动去重,每条记录只出现一次原样保留,重复行全部出现
结果行数示例中 6 行示例中 8 行
是否需要去重计算需要(排序/哈希去重)不需要
典型适用场景需要唯一集合(如合并两个查询的结果、求并集)保留明细、拼接日志、分区表全量读取、性能敏感的合并

两条核心经验:需要"数学意义上的并集"(不重复)时用UNION;需要"物理上的拼接"(不丢行)时用UNION ALL。

实操要点:两侧的列结构与可排序性

使用UNION/UNION ALL时,两侧查询必须满足两个前提:

  1. 列数相同:两侧的select列表列数必须一致;
  2. 类型兼容:对应位置的列类型需要能隐式统一(PostgreSQL 会按需做类型调整,必要时可显式cast)。

例如把UNION与仓库中 sets-with-the-values-command.md 介绍的VALUES命令结合,可以非常紧凑地构造测试集合:

values (1), (2), (3) union all values (2), (3), (4); column1 --------- 1 2 3 2 3 4 (6 rows)

VALUES本身就能生成一张临时表结构,可以直接与UNION组合,作为子查询、CTE 或insert ... select的一部分,是构造并集/拼接场景测试数据的常用手段。

搭配 ORDER BY 序号排序

示例中使用了order by 1 asc,这是 PostgreSQL 支持的输出列序号排序写法:select列表中的每个表达式都有一个从 1 开始的索引,可以直接在order by(乃至group by)中引用。仓库中的 use-argument-indexes.md 专门记录了这一技巧,例如select id, updated_at from posts order by 2等价于按updated_at排序。在UNION场景下,由于合并结果没有表别名,用序号排序是最简洁可靠的方式,可以避免为两侧子查询重复书写列名。

延伸:UNION 在 CTE 中的应用

UNION不仅用于普通查询合并,也是递归 CTE(common table expression)的语法基石。仓库中的 fizzbuzz-with-common-table-expressions.md 展示了典型的with recursive写法,其递归分支正是用UNION连接"初始行"与"递归生成的新行":

with recursive fizzbuzz (num,val) as ( select 0, '' union select (num + 1), case when (num + 1) % 15 = 0 then 'fizzbuzz' when (num + 1) % 5 = 0 then 'buzz' when (num + 1) % 3 = 0 then 'fizz' else (num + 1)::text end from fizzbuzz where num < 100 ) select val from fizzbuzz where num > 0;

递归 CTE 要求递归项与终止项用UNION(或UNION ALL)连接,其中UNION负责保证"已经展开过的行不会被重复加入递归过程"。理解UNION的去重语义,有助于理解递归 CTE 为何能收敛;反过来,这也说明了UNION与UNION ALL的选择会直接影响最终结果的形状与行数。

延伸:用 generate_series() 快速验证合并行为

本文示例大量使用generate_series(1,4)这类集合返回函数。仓库中的 insert-a-bunch-of-records-with-generate-series.md 说明了它的原理:generate_series()会为参数区间内的每个值生成一行(如1、2、3直到上界),是造数据、写示例、验证集合语义时的利器。当你需要测试UNION/UNION ALL对大型结果集的去重行为时,用它快速生成两段有重叠区间的序列即可复现本文全部示例。

总结

  • UNION合并结果集时自动去除重复行,得到的是去重后的并集;UNION ALL不做去重,原样保留两侧全部行。
  • 是否需要去重,直接决定了结果的形状(示例中 6 行 vs 8 行)与执行开销(UNION需额外去重计算)。
  • 合并两侧要求列数一致、类型兼容;排序可借助输出列序号(如order by 1)简化书写。
  • UNION是递归 CTE 的基础语法,与VALUES、generate_series()配合可以快速构造各类集合测试场景。

这篇笔记收录于 til 仓库的 postgres 目录 下,并在 README.md 中建有索引;相关的VALUES、generate_series()、参数序号与递归 CTE 技巧,可分别参考 sets-with-the-values-command.md、insert-a-bunch-of-records-with-generate-series.md、use-argument-indexes.md 与 fizzbuzz-with-common-table-expressions.md。

  • 文档
  • 教程
  • 知识库

【免费下载链接】til

:memo: Today I Learned

项目地址:https://gitcode.com/gh_mirrors/ti/til
点击查看免费下载

相关推荐

上一篇:告别僵硬交互!Godot Engine实现VR手势抓取与投掷的5个关键步骤
下一篇:Docbox多语言代码示例功能详解:支持7种编程语言的API文档

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询