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

面试必问的SQL窗口函数:row_number、rank、dense_rank实战避坑指南

面试必问的SQL窗口函数:row_number、rank、dense_rank实战避坑指南

在技术面试中,SQL窗口函数几乎是必考内容,尤其是row_numberrankdense_rank这三个排序函数。很多求职者虽然能背出它们的区别,但在实际应用中却经常踩坑。本文将结合LeetCode高频题和真实业务场景,带你深入理解这三个函数的使用技巧和常见陷阱。

1. 核心概念解析:三个函数的本质区别

1.1 基础定义与行为差异

这三个函数都用于为结果集中的行分配排名,但处理相同值的方式截然不同:

  • row_number():为每一行分配唯一的序号,即使值相同也会获得不同排名
  • rank():相同值获得相同排名,但会留下"空缺"(如两个第一名后直接是第三名)
  • dense_rank():相同值获得相同排名,且排名连续不跳号(如两个第一名后是第二名)
-- 示例数据 CREATE TABLE sales ( salesperson VARCHAR(50), region VARCHAR(50), amount DECIMAL(10,2) ); INSERT INTO sales VALUES ('张三', '华东', 5000), ('李四', '华东', 5000), ('王五', '华东', 4000), ('赵六', '华南', 6000), ('钱七', '华南', 5500); -- 三种排名方式对比 SELECT salesperson, region, amount, ROW_NUMBER() OVER(ORDER BY amount DESC) AS row_num, RANK() OVER(ORDER BY amount DESC) AS rank_val, DENSE_RANK() OVER(ORDER BY amount DESC) AS dense_rank_val FROM sales;

执行结果对比:

salespersonregionamountrow_numrank_valdense_rank_val
赵六华南6000111
钱七华南5500222
张三华东5000333
李四华东5000433
王五华东4000554

1.2 分区排序的实际应用

窗口函数的真正威力在于PARTITION BY子句,它允许我们在不同分组内独立计算排名:

-- 按区域分组后的排名 SELECT salesperson, region, amount, ROW_NUMBER() OVER(PARTITION BY region ORDER BY amount DESC) AS region_rank FROM sales;

提示:PARTITION BY可以指定多个列,如PARTITION BY region, year,这在处理多维数据时特别有用。

2. 高频面试题实战解析

2.1 分组取Top N问题

这是面试中最常见的问题类型之一。假设我们需要找出每个区域销售额前两名的销售:

-- 正确解法:使用ROW_NUMBER WITH ranked_sales AS ( SELECT salesperson, region, amount, ROW_NUMBER() OVER(PARTITION BY region ORDER BY amount DESC) AS rank_val FROM sales ) SELECT * FROM ranked_sales WHERE rank_val <= 2;

常见错误:

  1. 使用RANK可能导致返回多于N条记录(如果有并列)
  2. 忘记在子查询或CTE中先计算排名,直接尝试在WHERE中过滤

2.2 连续登录天数问题

另一个经典问题是计算用户的连续登录天数。假设有登录记录表user_logins

-- 找出连续登录超过7天的用户 WITH login_dates AS ( SELECT user_id, login_date, ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY login_date) AS row_num FROM user_logins ), date_groups AS ( SELECT user_id, login_date, DATE_ADD(login_date, INTERVAL -row_num DAY) AS group_date FROM login_dates ) SELECT user_id, MIN(login_date) AS start_date, MAX(login_date) AS end_date, COUNT(*) AS consecutive_days FROM date_groups GROUP BY user_id, group_date HAVING COUNT(*) >= 7;

这个解决方案巧妙地利用了ROW_NUMBER来识别连续的日期序列。

3. 真实业务场景中的陷阱

3.1 销售排行榜的坑

假设你要生成月度销售排行榜,前10名获得奖励。如果简单地使用:

SELECT salesperson, amount, RANK() OVER(ORDER BY amount DESC) AS ranking FROM sales WHERE ranking <= 10; -- 错误!WHERE不能引用窗口函数结果

正确的做法是使用子查询或CTE:

WITH sales_ranking AS ( SELECT salesperson, amount, DENSE_RANK() OVER(ORDER BY amount DESC) AS ranking FROM sales ) SELECT * FROM sales_ranking WHERE ranking <= 10;

注意:这里使用DENSE_RANK而非RANK,可以避免因并列导致的奖励名额不足问题。

3.2 分页查询的性能问题

在实现分页时,很多人会这样写:

-- 低效的分页写法 SELECT * FROM ( SELECT *, ROW_NUMBER() OVER(ORDER BY create_time DESC) AS rn FROM large_table ) t WHERE rn BETWEEN 10001 AND 10100;

对于大数据量表,这种写法性能很差。更好的方式是:

-- 高效分页:使用keyset分页 SELECT * FROM large_table WHERE create_time < :last_seen_time ORDER BY create_time DESC LIMIT 100;

4. 高级技巧与最佳实践

4.1 多维度复合排序

在实际业务中,经常需要按多个字段排序:

SELECT product_id, category, sales_volume, profit_margin, ROW_NUMBER() OVER( PARTITION BY category ORDER BY sales_volume DESC, profit_margin DESC ) AS rank_in_category FROM products;

4.2 窗口帧的灵活应用

窗口函数还支持定义帧范围,这在计算移动平均等场景非常有用:

-- 计算每个销售最近3个月的平均销售额 SELECT salesperson, month, amount, AVG(amount) OVER( PARTITION BY salesperson ORDER BY month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ) AS moving_avg FROM monthly_sales;

4.3 性能优化建议

  1. 索引策略:为PARTITION BYORDER BY中的列创建合适索引
  2. 减少数据量:先过滤再排序,避免对大结果集计算排名
  3. 替代方案:对于简单Top N查询,有时LIMIT可能比窗口函数更高效
-- 优化示例:先过滤再排序 WITH filtered_data AS ( SELECT * FROM large_table WHERE department = 'IT' ) SELECT *, ROW_NUMBER() OVER(ORDER BY salary DESC) AS rank_val FROM filtered_data;

在实际项目中,我发现窗口函数最常见的错误是混淆它们的排名行为。特别是在处理奖励、资格筛选等场景时,选择错误的函数可能导致业务逻辑错误。例如,在一次促销活动中,我们错误地使用了RANK导致实际获奖人数超过了预算名额,就是因为没有考虑到并列情况。

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

相关文章:

  • TouchGal终极指南:一站式Galgame社区平台快速入门教程
  • Godot PCK文件解包工具深度解析:基于Python的内存映射技术实现
  • 彻底掌控Spotify更新节奏:BlockTheSpot版本锁定完全指南
  • FreeMoCap企业级分布式动作捕捉架构设计:从多相机三维重建到骨骼动画生成深度解析
  • 佛山石材翻新护理公司怎么选,靠谱的有哪些 - 工业品网
  • 探寻2026年工业烘箱品牌,百利豪环保机械好用且价格划算 - myqiye
  • 逆向新手也能懂:用Python脚本5分钟搞定BUUCTF的XOR逆向题
  • 高效突破Windows安装限制:MediaCreationTool.bat智能部署解决方案
  • NGA论坛增强脚本:打造你的专属论坛浏览体验终极指南
  • 说说广东佛山口碑不错的清洁企业,广东华瑞环境靠谱吗? - 工业品网
  • 在 IDEA 里,新建一个 Java 程序 + 写第一个能运行的代码
  • 2026年焊枪公司推荐榜,焊机/封闭式管焊机/气体管道焊机/高压油管焊机/不锈钢管道焊机 - 品牌策略师
  • 分析舟山铝合金隔断优质厂家,哪家口碑好 - mypinpai
  • 探讨2026年定制铝合金门头的厂家,宁波舟山哪家更靠谱 - 工业品牌热点
  • 分析2026年广东做石材养护的专业保洁公司,靠谱的有哪些? - mypinpai
  • 一条 INSERT,一条 UPDATE,同时执行会阻塞吗?——MySQL RR 隔离级别锁机制全解析
  • 分析宁波及舟山靠谱的铝合金钢化玻璃雨棚厂家有哪些 - 工业设备
  • EldenRingSaveCopier:艾尔登法环存档迁移的完整指南
  • 终极鼠标灵敏度转换指南:如何在所有游戏中保持一致的瞄准手感?
  • 机器学习超参数调优实战指南
  • 选购铝合金栏杆,宁波哪些生产厂能提供个性化定制服务 - 工业品网
  • 开源硬件改造卡西欧F-91W:传感器扩展与极致能效实践
  • 博客园中的设置
  • 聊聊广东做办公楼日常保洁公司,哪家口碑比较好? - 工业设备
  • League-Toolkit深度解析:LCU API驱动的英雄联盟客户端增强工具实战指南
  • 如何用HSTracker免费提升炉石传说胜率:macOS玩家的终极指南
  • 2026年全铝别墅门个性化定制费用揭秘,宁波厂家收费分析 - 工业推荐榜
  • 终极CentOS-WSL安装指南:三步在Windows上运行完整CentOS环境
  • Ubuntu 20.04上从源码编译Geth 1.10.5:避开Go版本不匹配的坑
  • 微信聊天记录导出完整教程:3步永久保存珍贵对话