news 2026/6/23 7:43:04

PostgreSQL ROW_NUMBER() 窗口函数完全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PostgreSQL ROW_NUMBER() 窗口函数完全解析

一、什么是"窗口函数"?(大白话版)

“窗口"不是窗户,而是"视野范围”!

  • 普通聚合函数(SUM、COUNT):把多行压缩成一行,你看不到原始数据了
  • 窗口函数(ROW_NUMBER):在每行旁边附加计算结果,原始数据还在

比喻:

普通聚合:把全班成绩汇总成平均分 → 你看不出每个人的分数 窗口函数:在每个人旁边标注"班级第几名" → 既看到分数,又看到排名

为什么叫"窗口"?
因为你可以定义一个"滑动窗口"(比如"当前行 + 前2行"),在这个范围内计算。


二、ROW_NUMBER() 一句话解释

给每组数据编个号:1、2、3、4…,从 1 开始连续递增。


三、9 个最实用场景

场景 1:去重(保留最新/最早的一条)

需求:用户可能有多条订单,只保留每个用户的最新订单

SELECT*FROM(SELECTuser_id,order_no,created_at,ROW_NUMBER()OVER(PARTITIONBYuser_idORDERBYcreated_atDESC)ASrnFROMorders)tWHERErn=1;-- 只取每个用户的第一条(最新的)

原理:

  • PARTITION BY user_id:按用户分组
  • ORDER BY created_at DESC:每组内按时间倒序
  • ROW_NUMBER():编号 1、2、3…
  • WHERE rn = 1:只要第一条

场景 2:分页查询(高效分页)

需求:查询第 11-20 条记录

SELECT*FROM(SELECTid,name,created_at,ROW_NUMBER()OVER(ORDERBYcreated_atDESC)ASrnFROMusers)tWHERErnBETWEEN11AND20;

优势:LIMIT/OFFSET在大数据量时更快(尤其是深分页)


场景 3:找出每组的前 N 名

需求:每个部门工资最高的 3 个人

SELECT*FROM(SELECTdept_name,emp_name,salary,ROW_NUMBER()OVER(PARTITIONBYdept_nameORDERBYsalaryDESC)ASrnFROMemployees)tWHERErn<=3;-- 每个部门前 3 名

场景 4:删除重复数据

需求:清理重复的用户记录,只保留 ID 最小的

DELETEFROMusersWHEREidIN(SELECTidFROM(SELECTid,email,ROW_NUMBER()OVER(PARTITIONBYemailORDERBYidASC)ASrnFROMusers)tWHERErn>1-- 保留 rn=1 的,删除其他的);

场景 5:对比当前行和上一行

需求:计算每日销售额环比增长

SELECTsale_date,daily_amount,LAG(daily_amount)OVER(ORDERBYsale_date)ASprev_day_amount,ROUND((daily_amount-LAG(daily_amount)OVER(ORDERBYsale_date))/LAG(daily_amount)OVER(ORDERBYsale_date)*100,2)ASgrowth_rateFROMdaily_sales;

注意:这里用LAG()更适合,但ROW_NUMBER()也可以实现类似效果。


场景 6:标记首次/最后一次行为

需求:标记用户的首次登录和最后登录

SELECTuser_id,login_time,CASEWHENROW_NUMBER()OVER(PARTITIONBYuser_idORDERBYlogin_timeASC)=1THEN'首次登录'WHENROW_NUMBER()OVER(PARTITIONBYuser_idORDERBYlogin_timeDESC)=1THEN'最后登录'ELSE'普通登录'ENDASlogin_typeFROMuser_logins;

场景 7:分组后取中间值

需求:去掉最高分和最低分,取中间的平均分

SELECTstudent_id,AVG(score)ASavg_scoreFROM(SELECTstudent_id,score,ROW_NUMBER()OVER(PARTITIONBYstudent_idORDERBYscoreASC)ASrn_asc,ROW_NUMBER()OVER(PARTITIONBYstudent_idORDERBYscoreDESC)ASrn_desc,COUNT(*)OVER(PARTITIONBYstudent_id)AStotal_countFROMexam_scores)tWHERErn_asc>1ANDrn_desc>1;-- 去掉最低和最高

场景 8:检测数据连续性

需求:找出用户连续登录的天数

SELECTuser_id,login_date,login_date-(ROW_NUMBER()OVER(PARTITIONBYuser_idORDERBYlogin_date)||' days')::INTERVALASgrpFROMuser_loginsGROUPBYuser_id,login_date;-- 相同的 grp 表示连续登录SELECTuser_id,MIN(login_date)ASstart_date,MAX(login_date)ASend_date,COUNT(*)ASconsecutive_daysFROM(-- 上面的子查询)tGROUPBYuser_id,grpHAVINGCOUNT(*)>=3;-- 连续登录 3 天以上

场景 9:排行榜(带并列处理)

需求:生成销售排行榜,相同业绩排名相同

-- ROW_NUMBER():即使分数相同,排名也不同(1、2、3、4)SELECTemp_name,sales_amount,ROW_NUMBER()OVER(ORDERBYsales_amountDESC)ASrankFROMsales_performance;-- 如果需要并列排名,用 RANK() 或 DENSE_RANK()-- RANK(): 1, 2, 2, 4 (跳过 3)-- DENSE_RANK(): 1, 2, 2, 3 (不跳过)

四、核心语法拆解

ROW_NUMBER()OVER(PARTITIONBYcolumn1,column2-- 可选:分组依据ORDERBYcolumn3DESC-- 必填:排序规则)

三个关键点:

  1. OVER():声明这是窗口函数
  2. PARTITION BY:可选,类似GROUP BY,但不压缩行数
  3. ORDER BY:必填,决定编号顺序

五、ROW_NUMBER vs RANK vs DENSE_RANK

函数相同值处理示例适用场景
ROW_NUMBER()强制不同1, 2, 3, 4去重、分页
RANK()并列,跳号1, 2, 2, 4排行榜(允许空缺)
DENSE_RANK()并列,不跳号1, 2, 2, 3排行榜(紧凑排名)

示例对比:

SELECTname,score,ROW_NUMBER()OVER(ORDERBYscoreDESC)ASrow_num,RANK()OVER(ORDERBYscoreDESC)ASrank,DENSE_RANK()OVER(ORDERBYscoreDESC)ASdense_rankFROMstudents;-- 结果:-- name | score | row_num | rank | dense_rank-- ------+-------+---------+------+------------- 张三 | 100 | 1 | 1 | 1-- 李四 | 100 | 2 | 1 | 1 ← 并列第一-- 王五 | 95 | 3 | 3 | 2 ← RANK 跳过 2,DENSE 不跳-- 赵六 | 90 | 4 | 4 | 3

六、性能优化建议

1. 避免全表扫描

-- ❌ 慢:全表编号后再过滤SELECT*FROM(SELECT*,ROW_NUMBER()OVER(ORDERBYcreated_at)ASrnFROMorders)tWHERErn<=10;-- ✅ 快:先过滤再编号SELECT*,ROW_NUMBER()OVER(ORDERBYcreated_at)ASrnFROMordersWHEREcreated_at>='2026-01-01'ORDERBYcreated_atLIMIT10;

2. 合理使用索引

-- 为 PARTITION BY 和 ORDER BY 字段创建索引CREATEINDEXidx_orders_user_createdONorders(user_id,created_atDESC);-- 这样查询会很快SELECT*FROM(SELECT*,ROW_NUMBER()OVER(PARTITIONBYuser_idORDERBYcreated_atDESC)ASrnFROMorders)tWHERErn=1;

3. 避免不必要的 PARTITION BY

-- ❌ 如果不需要分组,不要加 PARTITION BYROW_NUMBER()OVER(PARTITIONBY1ORDERBYid)-- 多余!-- ✅ 直接全局编号ROW_NUMBER()OVER(ORDERBYid)

七、常见错误

错误 1:忘记 ORDER BY

-- ❌ 错误:窗口函数必须包含 ORDER BYROW_NUMBER()OVER(PARTITIONBYuser_id)-- ✅ 正确ROW_NUMBER()OVER(PARTITIONBYuser_idORDERBYcreated_at)

错误 2:在 WHERE 中直接使用

-- ❌ 错误:窗口函数不能在 WHERE 中使用SELECT*FROMordersWHEREROW_NUMBER()OVER(ORDERBYid)=1;-- ✅ 正确:用子查询SELECT*FROM(SELECT*,ROW_NUMBER()OVER(ORDERBYid)ASrnFROMorders)tWHERErn=1;

错误 3:误解 PARTITION BY

-- ❌ 错误理解:以为 PARTITION BY 会分组返回SELECTuser_id,COUNT(*)FROMordersGROUPBYuser_id;-- 这才是分组-- ✅ 正确理解:PARTITION BY 不减少行数SELECTuser_id,ROW_NUMBER()OVER(PARTITIONBYuser_idORDERBYid)ASrnFROMorders;-- 行数不变,只是加了编号

八、记忆口诀

ROW_NUMBER 编序号,分组排序不能少 去重分页最常用,子查询里套一层 PARTITION 是分堆,ORDER 决定谁在前 WHERE 不能直接调,外层过滤才正确

九、总结

核心要点

  1. 窗口函数= 在不压缩行的前提下,附加计算结果
  2. ROW_NUMBER()= 给每组数据编连续序号(1、2、3…)
  3. 最常用场景= 去重、分页、取前 N 名
  4. 必须配合=OVER()+ORDER BY
  5. 使用时机= 需要"组内排名"或"唯一标识"时

快速参考

-- 基本模板SELECT*FROM(SELECT字段列表,ROW_NUMBER()OVER(PARTITIONBY分组字段-- 可选ORDERBY排序字段DESC-- 必填)ASrnFROM表名)tWHERErn=1;-- 或其他条件

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/23 7:42:06

Akagi:麻雀AI智能助手的完整使用指南与深度解析

Akagi&#xff1a;麻雀AI智能助手的完整使用指南与深度解析 【免费下载链接】Akagi 支持雀魂、天鳳、麻雀一番街、天月麻將&#xff0c;能夠使用自定義的AI模型實時分析對局並給出建議&#xff0c;內建Mortal AI作為示例。 Supports Majsoul, Tenhou, Riichi City, Amatsuki, w…

作者头像 李华
网站建设 2026/6/23 7:37:03

OpenClaw与Bedrock AgentCore协同架构解析

1. 这不是“又一个Agent框架”&#xff1a;OpenClaw Bedrock AgentCore 的真实定位与分工逻辑很多人第一次看到“OpenClaw Agent Bedrock AgentCore SDK”这个组合&#xff0c;第一反应是&#xff1a;“哦&#xff0c;又一个AI Agent开发套件”&#xff0c;然后顺手点开GitHu…

作者头像 李华
网站建设 2026/6/23 7:35:22

XYBotV2插件推荐:10个必备插件提升机器人体验

XYBotV2插件推荐&#xff1a;10个必备插件提升机器人体验 【免费下载链接】XYBotV2 &#x1f916; 功能丰富的微信机器人框架 | AI对话、对接Dify、积分系统、游戏互动、每日新闻、天气查询 | 非Hook非Web实现 | 支持 Windows✅ Linux✅ MacOS✅ | 全新架构解决XYBot第一代痛点…

作者头像 李华
网站建设 2026/6/23 7:20:05

Dify连接数据库实战指南:从网络通达到SQL可信执行

1. 为什么Dify平台连接数据库这件事&#xff0c;90%的人卡在“以为连上了”这一步 Dify平台连接数据库&#xff0c;不是点几下鼠标、填几个字段就能宣告成功的功能模块。它本质上是一场跨协议、跨权限、跨环境的协同作战——前端界面要能解析SQL意图&#xff0c;后端服务要能安…

作者头像 李华
网站建设 2026/6/23 7:11:05

多模态诊断框架:如何应对数据缺失与提升模型可解释性

1. 项目缘起&#xff1a;当诊断遇上“不完整”的数据在医疗影像诊断、工业质检、自动驾驶感知这些领域&#xff0c;我们越来越依赖多模态数据来做决策。比如&#xff0c;医生想判断一个脑部病变&#xff0c;理想状态下&#xff0c;他希望能同时看到病人的CT、MRI&#xff08;T1…

作者头像 李华
网站建设 2026/6/23 7:05:04

iOS逆向工程实战:某信营业厅App算法分析与签名破解

1. 项目概述与逆向工程的价值 最近在移动安全研究圈里&#xff0c;一个关于“某信营业厅”App的算法分析项目引起了我的注意。这个标题本身就充满了信息量&#xff1a;“iOS 逆向”指明了平台和技术手段&#xff0c;“某信营业厅”锁定了目标应用&#xff0c;而“算法分析”则是…

作者头像 李华