MySQL 5.7及更早版本需用用户变量实现分组内行号重置,核心是通过@group_key记录上一分组值、@row_num计数,并在分组变化时重置;必须按分组和排序字段排序,且变量初始化须在查询中显式完成。

MySQL中用变量实现分组内行号重置
MySQL 5.7及更早版本不支持 ROW_NUMBER() OVER (PARTITION BY ...),想在每个分组内从1开始编号,得靠用户变量。核心思路是:用 @group_key 记录上一行的分组值,@row_num 计数;当分组变化时重置计数器。
常见错误是直接写 @row_num := @row_num + 1 不加判断,导致全表连续编号而非分组内重置。正确做法必须显式比对当前分组字段与上一行是否一致:
- 先按分组字段(如
category)和排序字段(如created_at)排序,否则变量逻辑失效 - 初始化变量要放在子查询或
SELECT开头,不能只在SET里设一次就完事(尤其在多语句会话中) - MySQL 8.0+ 建议直接用窗口函数,变量方案在高并发或复杂JOIN下行为不可靠
SELECT id, category, name, @row_num := IF(@group_key = category, @row_num + 1, 1) AS row_num, @group_key := category FROM items CROSS JOIN (SELECT @row_num := 0, @group_key := '') AS _init ORDER BY category, created_at;
PostgreSQL里用 ROW_NUMBER() 替代变量逻辑
PostgreSQL原生支持标准窗口函数,ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at) 就是直接解。不需要变量、不依赖执行顺序、结果稳定可预期。
容易忽略的是 ORDER BY 在窗口函数里的必要性:如果省略,分组内行号顺序无保证,可能每次查询都不同。另外注意 NULL 值处理——默认 NULLS FIRST,若业务要求 NULL 排末尾,需显式写 ORDER BY category NULLS LAST。
-
PARTITION BY字段允许多个,例如PARTITION BY category, status - 若只要最新一条记录,可用
QUALIFY ROW_NUMBER() OVER (...) = 1(BigQuery/Trino支持),但 PostgreSQL 需外层嵌套WHERE row_num = 1 - 性能上,带
PARTITION BY的窗口函数通常比自连接或相关子查询快得多
SQL Server中 ROW_NUMBER() 的兼容写法与陷阱
SQL Server 2005起就支持 ROW_NUMBER(),但老项目常混用 TOP N + 子查询模拟分组TOP,效率差且难维护。直接用窗口函数是最简路径。
典型坑是把 ORDER BY 写在外部查询里,以为能影响窗口函数排序——实际无效。窗口函数的 ORDER BY 必须写在 OVER 子句内部:
- 错:
SELECT *, ROW_NUMBER() OVER (PARTITION BY category) FROM t ORDER BY created_at→ 分组内序号仍是随机的 - 对:
ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at) - 若分组字段含中文或特殊字符,确保数据库排序规则(collation)一致,否则
PARTITION BY可能因隐式转换失效
变量方案在跨数据库迁移时为何容易崩
MySQL变量依赖执行顺序,而SQL标准不保证 SELECT 表达式求值顺序。PostgreSQL、SQL Server、Oracle 都不支持类似MySQL的变量赋值语法,强行移植必然报错或返回垃圾数据。
真正可迁移的做法只有两条路:一是统一用窗口函数(需确认目标库版本支持),二是改用聚合+自连接(性能差但兼容性高)。例如模拟分组内最大值对应行,别写变量,改用:
SELECT t1.* FROM items t1 WHERE NOT EXISTS ( SELECT 1 FROM items t2 WHERE t2.category = t1.category AND t2.created_at > t1.created_at );
变量是权宜之计,窗口函数才是分组计数的正解。但如果你正在维护一个跑在MySQL 5.6上的老系统,那变量初始化那一行 CROSS JOIN (SELECT @row_num := 0, @group_key := '') AS _init 必须保留——少一个等号,整列就全变成NULL。










