- 文档
- 教程
- 知识库
【免费下载链接】til
:memo: Today I Learned
在 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 行为对比
| 行为 | UNION | UNION ALL |
|---|---|---|
| 重复行处理 | 自动去重,每条记录只出现一次 | 原样保留,重复行全部出现 |
| 结果行数 | 示例中 6 行 | 示例中 8 行 |
| 是否需要去重计算 | 需要(排序/哈希去重) | 不需要 |
| 典型适用场景 | 需要唯一集合(如合并两个查询的结果、求并集) | 保留明细、拼接日志、分区表全量读取、性能敏感的合并 |
两条核心经验:需要"数学意义上的并集"(不重复)时用UNION;需要"物理上的拼接"(不丢行)时用UNION ALL。
实操要点:两侧的列结构与可排序性
使用UNION/UNION ALL时,两侧查询必须满足两个前提:
- 列数相同:两侧的
select列表列数必须一致; - 类型兼容:对应位置的列类型需要能隐式统一(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
相关推荐
如何关掉 SystemInformer 悬停提示弹窗:3 步快速指南
如何关掉 SystemInformer 悬停提示弹窗:3 步快速指南 你正在梳理进程树,鼠标每划过一行就弹出一个提示小窗,正好挡住你要看的数据。这是 Syste
桌面应用调试器应用安全驱动开发深入解析 @turf/union:多边形并集(Union)合并的地理计算指南
深入解析 @turf/union:多边形并集(Union)合并的地理计算指南 本指南以 Turf 项目( turf union 官方文档 https://lin
数据分析给 AI 一个真实已登录的浏览器:BrowserSkill 5 分钟上手指南
给 AI 一个真实已登录的浏览器:BrowserSkill 5 分钟上手指南 BrowserSkill 是一套面向 AI 浏览器自动化 的工具:它由 bsk 命
人工智能AI 应用AI 技能浏览器控制dsh-plugin
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考