当前位置: 首页 > news >正文

为什么 DISTINCT 加了 ROWNUM 反而数据变少了?揭秘 KES 与 PG 的执行优先级陷阱


目录

引言:看似只是“取前 10 个”,结果却少了好几行

一、先说结论:不是 DISTINCT 出问题了,是它和 ROWNUM 的先后顺序变了

在 KES / Oracle 中

在 PostgreSQL 中

二、升级版流程图:同一句 SQL,数据库到底是怎么执行的

三、用数据把问题讲透:为什么前 10 行不一定能得到 10 个去重结果

KES / Oracle 的结果理解

PostgreSQL 的结果理解

四、再往深一层:ROWNUM 为什么还会影响优化器

看这个例子

五、升级版流程图:ROWNUM 为什么会阻断优化

六、怎么写才稳:三种更安全的写法

方案一:明确你到底要的是“先去重再截取”,还是“先截取再去重”

业务意图 1:我要前 N 个物理行,再去重

业务意图 2:我要 N 个不重复用户

方案二:用窗口函数替代“模糊顺序”

方案三:如果要稳定结果,就显式加 ORDER BY

七、常见误区清单:你可能不是第一次踩坑

误区 1:我只是想“取前 10 个不重复值”

误区 2:PG 里 LIMIT 10 等价于 Oracle 的 ROWNUM <= 10

误区 3:去重和截断可以随便调换

八、几个可直接拿去改造的 SQL 模板

模板 1:先去重,再取前 10 个

模板 2:先按时间排序,再取前 10 个不重复用户

模板 3:PG 风格的结果集截断

九、为什么这个坑在迁移项目里特别常见

十、一个更适合团队排查的流程图

十一、最佳实践总结

结语:SQL 看起来差不多,语义可能差很远


引言:看似只是“取前 10 个”,结果却少了好几行

很多人第一次遇到这个问题时,都会有点懵。

明明写的是:

SELECT DISTINCT user_id FROM access_log WHERE rownum <= 10;

按直觉理解,应该是“取前 10 个不重复的用户”。可实际跑出来,可能只有 7 行、8 行,甚至更少。原文给出的例子就很典型:同一批前 10 条物理记录里,重复值很多,最后DISTINCT一去重,剩下的自然不到 10 行。

这类问题最容易在数据库迁移时暴露出来。因为你在 Oracle 或 KES 里写惯了ROWNUM,换到 PostgreSQL 后,一旦使用了LIMIT,执行顺序和语义就不完全一样了。于是,同样的 SQL,看起来差不多,结果却不一样。


一、先说结论:不是DISTINCT出问题了,是它和ROWNUM的先后顺序变了

这个坑的本质,不是 SQL 写错,也不是数据坏了,而是数据库对“先截取还是先去重”的处理顺序不同

在 KES / Oracle 中

ROWNUM是一个伪列,它在行读取阶段就被赋值,通常早于DISTINCT发生。
所以执行逻辑更接近:

扫描数据 → 先给行号 → 先限制 ROWNUM → 再 DISTINCT 去重 → 返回结果

在 PostgreSQL 中

LIMIT更像是作用于最终结果集。
执行逻辑更接近:

扫描数据 → 先 DISTINCT 去重 → 再 LIMIT 截断 → 返回结果

这就是为什么同样是“要 10 条”,在 KES / Oracle 里可能不足 10 条,而 PG 更容易返回你预期中的 10 条。原文已经把这个差异讲得很清楚。


二、升级版流程图:同一句 SQL,数据库到底是怎么执行的

这张图的关键不在“流程多”,而在于把两个数据库的执行重心分开了。
KES / Oracle 更偏向“先限制,再整理”;PG 更偏向“先整理,再限制”。


三、用数据把问题讲透:为什么前 10 行不一定能得到 10 个去重结果

原文用了一张非常直观的数据表来说明这个问题:前 10 条物理记录里,user_id有大量重复,因此DISTINCT之后只剩 7 行。

我们把这个例子再扩展一下:

rowiduser_id
1A
2A
3B
4C
5A
6D
7E
8B
9F
10G
11H
12C
13I
14J
15K

KES / Oracle 的结果理解

如果写:

SELECT DISTINCT user_id FROM access_log WHERE rownum <= 10;

那数据库先拿前 10 行,再去重,结果是:

  • A
  • B
  • C
  • D
  • E
  • F
  • G

所以最终只有7 行

PostgreSQL 的结果理解

如果写成:

SELECT DISTINCT user_id FROM access_log LIMIT 10;

它更像是先把全表去重,再取前 10 个不重复值。
所以只要全表里不重复值足够多,就能返回10 行


四、再往深一层:ROWNUM为什么还会影响优化器

原文还提到一个非常重要但容易被忽略的点:ROWNUM不只是影响结果,它还会影响执行计划,尤其是子查询的提升和条件下推。

看这个例子

SELECT * FROM ( SELECT DISTINCT user_id FROM access_log WHERE rownum <= 10 ) t WHERE t.user_id = 'A';

直觉上,你可能觉得外层t.user_id = 'A'应该尽量下推到子查询里,减少扫描范围。
ROWNUM的存在,往往会阻断这种优化。

也就是说:

  • 子查询先被截断
  • 外层再过滤
  • 优化器无法自由重写

这会导致两个后果:

  1. 性能损失:本来可以少扫很多数据,最后还是扫了不少。
  2. 语义误判:你以为自己在做“精准查询”,实际却是“先取一小段,再筛”。

五、升级版流程图:ROWNUM为什么会阻断优化

这也是为什么很多迁移项目里,最先要做的不是“改语法”,而是“重新理解语义”。
同样的 SQL 结构,在不同数据库里,优化器未必会按你预期的方式重写。


六、怎么写才稳:三种更安全的写法

方案一:明确你到底要的是“先去重再截取”,还是“先截取再去重”

这是最重要的一步。

业务意图 1:我要前 N 个物理行,再去重

这种语义在 KES / Oracle 里是可以成立的,但你要明确知道它返回的结果不一定有 N 行。

SELECT DISTINCT user_id FROM ( SELECT user_id FROM access_log WHERE rownum <= 10 ) t;

这个写法把“先截取”显式包进子查询里,语义更清楚。

业务意图 2:我要 N 个不重复用户

那其实更接近 PostgreSQL 那边的写法:

SELECT DISTINCT user_id FROM access_log LIMIT 10;

或者在 KES / Oracle 中用更明确的排序和编号方式来实现。


方案二:用窗口函数替代“模糊顺序”

如果你要的是“按某种顺序,取前 10 个不重复值”,那窗口函数更稳。

SELECT user_id FROM ( SELECT user_id, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY user_id) AS rn FROM access_log ) t WHERE t.rn = 1 FETCH FIRST 10 ROWS ONLY;

这个思路的核心是:

  1. 先给每个user_id编号
  2. 每个分组只保留一条
  3. 最后再截取前 10 条

这样比DISTINCT + ROWNUM的语义更明确,也更容易迁移。


方案三:如果要稳定结果,就显式加ORDER BY

ROWNUM最大的问题之一是:它依赖物理扫描顺序
如果没有ORDER BY,不同执行计划、不同索引、不同统计信息下,结果都可能变化。

比如:

SELECT DISTINCT user_id FROM ( SELECT user_id FROM access_log ORDER BY create_time DESC ) t WHERE rownum <= 10;

这样至少能把“前 10 条”定义得更清楚,不再完全依赖物理页顺序。


七、常见误区清单:你可能不是第一次踩坑

原文已经指出,这类问题常常发生在“你以为逻辑很简单”的地方。
下面再把常见误区整理一下:

误区 1:我只是想“取前 10 个不重复值”

如果你直接写:

SELECT DISTINCT user_id FROM access_log WHERE rownum <= 10;

你要先确认:你真的是想“先取 10 行,再去重”?
如果不是,那这个写法本身就是错位的。

误区 2:PG 里LIMIT 10等价于 Oracle 的ROWNUM <= 10

不完全等价。
LIMIT更像结果集截断,而ROWNUM更像读取过程中的行号限制。

误区 3:去重和截断可以随便调换

不能。
一旦调换,结果集数量、内容、排序稳定性都会变。


八、几个可直接拿去改造的 SQL 模板

模板 1:先去重,再取前 10 个

SELECT user_id FROM ( SELECT DISTINCT user_id FROM access_log ) t WHERE rownum <= 10;

这个写法更接近“我要 10 个不重复值”的语义。

模板 2:先按时间排序,再取前 10 个不重复用户

SELECT user_id FROM ( SELECT user_id, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY create_time DESC) AS rn FROM access_log ) t WHERE t.rn = 1 ORDER BY user_id FETCH FIRST 10 ROWS ONLY;

模板 3:PG 风格的结果集截断

SELECT DISTINCT user_id FROM access_log ORDER BY user_id LIMIT 10;

这个写法适合 PostgreSQL,也更符合“先整理,再截取”的思路。


九、为什么这个坑在迁移项目里特别常见

因为 Oracle / KES 和 PostgreSQL 的 SQL 习惯不一样。

原文已经点出了这一点:同一条 SQL,在不同引擎上跑,结果不一定一样。
而迁移项目里,最危险的往往不是明显报错,而是这种**“能跑但不完全对”**的情况。

这类 SQL 的风险通常有三种:

  1. 结果少了
  2. 结果顺序变了
  3. 性能突然变差了

所以,迁移时不要只看语法是否能执行,还要看:

  • 是否保留了原始业务语义
  • 是否引入了执行计划变化
  • 是否影响了结果集完整性

十、一个更适合团队排查的流程图

这套流程适合做成团队内的 SQL 审查 checklist。
只要一看结果不对,就按这个顺序排查,基本不会跑偏。


十一、最佳实践总结

  1. 不要默认DISTINCT + ROWNUM表示“取 N 个不重复值”。它未必是你真正想要的语义。
  2. 先明确顺序:是先去重,还是先截断。两者结果可能完全不同。
  3. 跨库迁移时优先使用子查询或窗口函数,不要依赖引擎私有行为。
  4. 需要稳定结果时必须加ORDER BY,否则物理扫描顺序变化会导致结果波动。
  5. 检查执行计划,因为ROWNUM还可能阻断优化器的下推和重写。

结语:SQL 看起来差不多,语义可能差很远

DISTINCT + ROWNUM这个坑,表面上是语法组合问题,实际上是执行优先级和优化器行为的问题。原文已经把这个核心讲得很清楚:KES / Oracle 和 PostgreSQL 对行号限制与去重的处理顺序不同,因此相同的 SQL 可能得到不同的行数。

真正稳定的做法,不是死记某个数据库的特殊规则,而是把业务意图写清楚:
你到底是想“先取样再去重”,还是“先去重再取样”?
一旦这个问题想明白,SQL 就不会再偷偷变味了。

http://www.jsqmd.com/news/807959/

相关文章:

  • 从热传导到细胞轨迹:一个公式讲明白Diffusion Map的数学直觉
  • 010、多旋翼飞行器结构与受力分析
  • QQ空间历史说说备份指南:GetQzonehistory让数字记忆永久留存
  • TVA重塑智慧城市安防新范式(21)
  • 面试必问:公司用AI 赋能自动化,你是怎么用AI 做自动化测试的呢?(附落地全流程)
  • DownKyi哔哩下载姬:5步快速掌握B站视频下载完整教程
  • 数学_大鹏_9B_板块03_相似三角形
  • Bash 4.0 及以上版本如何实现关联数组配置?
  • 增量式编码器驱动开发实战:从原理到FPGA高速计数
  • 终极指南:用Python快速自动化你的COMSOL多物理场仿真工作流
  • 【Lindy 2.3新特性深度解析】:实时异步任务路由+动态工具发现机制如何将RAG工作流提速3.8倍
  • 别再乱用multicycle约束了!从慢钟到快钟,一个真实案例讲透-start和-end参数怎么选
  • 棋盘格上下文模型:如何为端到端图像压缩解锁并行解码新范式
  • 讯灵AI渠道经理推荐哪家? - myqiye
  • 这家公司四害消杀,2026推荐案例实战榜! - 速递信息
  • 告别AT指令!用nRF52832的BLE NUS服务,5分钟搞定手机与开发板的双向通信
  • 宿舍报修小程序|基于微信小程序的宿舍报修系统的设计与实现(源码+数据库+文档)
  • 项目介绍 MATLAB实现基于遗传算法(GA)进行锂电池剩余寿命(RUL)预测(含模型描述及部分示例代码)专栏近期有大量优惠 还请多多点一下关注 加油 谢谢 你的鼓励是我前行的动力 谢谢支持 加油 谢
  • 基于AWS与Terraform的OpenClaw私有AI助手一键部署实战
  • 选购进口儿童家具的技巧,斯堪维亚口碑怎样? - mypinpai
  • 图形化部署Hermes Agent:零门槛搭建AI智能体与飞书机器人
  • GEO优化公司排行榜:医美机构首选5家专业服务商 - 品牌2025
  • 终极指南:5步掌握碧蓝航线Live2D模型提取技术
  • 2026年常州热缩管源头厂家与汽车线束波纹管定制深度横评指南 - 优质企业观察收录
  • 从数学定义到代码实现:深度解析卷积与互相关的本质差异
  • 别再被数据线坑了!手把手教你用STLINK-V3E给NUCLEO-H7A3ZI-Q开发板下载程序(附驱动安装与灯号解读)
  • 终极指南:如何使用Attu图形化管理工具简化向量数据库操作
  • 人工智能【第22篇】Seq2Seq模型与注意力机制:机器翻译的基石
  • 微信聊天记录永久备份终极指南:3步将珍贵对话从手机安全迁移到电脑
  • 永辉超市购物卡回收攻略,省钱妙招! - 团团收购物卡回收