mysql 中 row_number() 函数详解
row_number()
是 sql 窗口函数中的一种,用于为查询结果集中的每一行分配一个唯一的连续序号。与 rank()
和 dense_rank()
不同,row_number()
不会处理重复值,即使排序字段值相同,也会严格按行顺序递增编号。
一、基础语法
row_number() over ( [partition by 分组字段] order by 排序字段 [asc|desc] )
- partition by:按指定字段分组,每组内重新从1开始编号。
- order by:决定排序逻辑,影响行号的分配顺序。
二、核心特点
特性 | 说明 |
---|---|
唯一性 | 每行序号严格递增,不重复(即使排序字段值相同) |
灵活性 | 可结合分组(partition by )实现复杂场景 |
兼容性 | mysql 8.0+ 原生支持,低版本需用变量模拟 |
性能影响 | 未优化时可能导致全表扫描,需合理使用索引 |
三、典型应用场景
1. 数据分页查询
-- 查询第3页数据(每页10条) with paged_data as ( select id, name, row_number() over (order by id) as row_num from users ) select * from paged_data where row_num between 21 and 30;
2. 删除重复数据
-- 保留最新记录(假设 create_time 为时间戳) delete from orders where (id, product_id) in ( select id, product_id from ( select id, product_id, row_number() over ( partition by product_id order by create_time desc ) as rn from orders ) t where rn > 1 -- 删除重复项,保留最新一条 );
3. 分组取top n记录
-- 获取每个部门薪资前3名 select * from ( select name, department, salary, row_number() over ( partition by department order by salary desc ) as dept_rank from employees ) ranked where dept_rank <= 3;
4. 生成唯一流水号
-- 按日期生成订单流水号(格式:yyyymmdd-0001) select order_id, concat( date_format(create_time, '%y%m%d'), '-', lpad(row_number() over ( partition by date(create_time) order by create_time ), 4, '0') ) as serial_num from orders;
四、与其他排序函数对比
函数 | 重复值处理 | 示例结果(排序字段值相同) |
---|---|---|
row_number() | 强制分配不同序号 | 1, 2, 3, 4 |
rank() | 相同值共享排名,后续跳过序号 | 1, 1, 3, 4 |
dense_rank() | 相同值共享排名,后续连续递增 | 1, 1, 2, 3 |
-- 对比三种函数 select score, row_number() over (order by score desc) as row_num, rank() over (order by score desc) as rank, dense_rank() over (order by score desc) as dense_rank from exam_scores;
五、性能优化技巧
1. 索引设计
为 partition by
和 order by
涉及的字段创建联合索引:
create index idx_dept_salary on employees(department, salary desc);
2. 减少计算范围
-- 仅处理2023年数据 select * from ( select order_id, amount, row_number() over (order by amount desc) as rn from orders where year(order_date) = 2023 -- 先过滤再排序 ) t where rn <= 100;
3. 避免嵌套查询
-- 优化前(性能差) select * from ( select *, row_number() over (...) as rn from large_table ) t where rn <= 100; -- 优化后(直接使用limit,若逻辑允许) select *, row_number() over (...) as rn from large_table order by ... limit 100;
六、mysql低版本兼容方案(5.7及以下)
使用会话变量模拟 row_number()
-- 按部门分组排序 select department, name, salary, @row_num := if( @current_dept = department, @row_num + 1, 1 ) as row_num, @current_dept := department as dummy from employees order by department, salary desc;
七、常见错误与排查
1. 错误:序号不符合预期
- 原因:未正确指定
order by
或partition by
- 解决:检查排序字段是否明确,分组条件是否合理
2. 错误:性能低下
- 原因:未使用索引导致全表扫描
- 解决:使用
explain
分析执行计划,添加必要索引
3. 错误:结果集为空
- 原因:外层查询条件与子查询中的
where
冲突 - 解决:验证过滤条件逻辑
八、最佳实践
- 明确排序规则:始终显式指定
order by
的排序方向(asc/desc) - 慎用全局排序:避免无
partition by
的大数据集操作 - 监控内存使用:窗口函数可能消耗大量临时内存
- 版本验证:生产环境确认 mysql 版本 >= 8.0
- 结合 cte 使用:提高复杂查询的可读性
with ranked_products as ( select product_id, row_number() over (partition by category order by sales desc) as rn from products ) select * from ranked_products where rn = 1;
总结:row_number()
是处理行级序号分配的利器,特别适合需要精确控制行顺序的场景。合理使用可显著简化分页、去重、top n查询等操作,但需注意其对性能的影响,尤其在处理海量数据时需结合索引优化。
到此这篇关于mysql 中 row_number() 函数详解的文章就介绍到这了,更多相关mysql row_number()函数内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论