用row_number()配合partition by编号加模运算是分组轮岗最稳路径,但order by字段必须唯一(如hire_date, employee_id),否则编号不可控;需双层cte先编号再算组内总人数,模运算用(datediff+rn-1)%cnt+1对齐起始日,并截断时间、校验日期有效性。

直接说结论:用 ROW_NUMBER() 配合 PARTITION BY 编号 + 模运算(%)是实现分组轮岗最稳的路径,但必须确保排序字段唯一,否则编号顺序不可控。
为什么 ROW_NUMBER() 必须带 ORDER BY 且字段要唯一
很多人写 ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY name) 后发现排班结果每天“跳号”或重复——问题往往出在 name 不唯一。SQL 标准规定:当 ORDER BY 字段值相同时,ROW_NUMBER() 的分配顺序是**未定义的**(non-deterministic),数据库可能每次执行都换顺序。
- 正确做法:强制用一个确定性字段补全排序,比如
employee_id或hire_date,组合成ORDER BY hire_date, employee_id - 错误示例:
ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY name)—— 若两个员工同名,编号就可能颠倒 - 性能提示:这个排序字段最好有索引,尤其当人员表很大时
模运算(%)怎么算当天该谁值班
轮岗本质是“循环取余”:假设某车间有 5 人,从第 0 天开始值班,那么第 n 天该由编号为 (n % 5) + 1 的人值班(+1 是因为 ROW_NUMBER() 从 1 开始)。但注意:起始日偏移量必须对齐。
- 先算天数差:
DATEDIFF(day, '2026-01-01', GETDATE())得到从基准日到今天的天数 - 再结合人员编号:
(DATEDIFF(...) + rn - 1) % cnt + 1,其中rn是ROW_NUMBER()结果,cnt是该组总人数(用COUNT(*) OVER (PARTITION BY dept_id)算) - 别直接用
rn % cnt:它会让编号为cnt的人永远轮不到第 0 天,逻辑错位
CTE 套两层才能安全拿到“组内最大编号”
你不能在同一个查询里一边生成 ROW_NUMBER(),一边用 MAX(rn) OVER (PARTITION BY ...) 当作总人数——有些数据库(如 SQL Server 2016 以前)会报错,或返回不一致结果。
- 第一层 CTE:只做
ROW_NUMBER()和基础字段投影 - 第二层 CTE:基于第一层,加
COUNT(*) OVER (PARTITION BY dept_id) AS cnt - 然后在外层 WHERE 或计算列中使用
cnt做模运算 - 反模式:
SELECT ..., ROW_NUMBER()..., COUNT(*) OVER (...) FROM ... WHERE (rn % cnt) = ...—— 这种写法在部分引擎里会因优化器重排执行顺序而出错
容易被忽略的边界点:跨月/跨年时日期函数行为
DATEDIFF(day, start_date, target_date) 看似简单,但若 start_date 是 '2026-02-29'(非闰年不存在),或 target_date 含时间部分(如 '2026-05-21 14:30:00'),会导致天数计算偏差。
- 始终用
CAST(GETDATE() AS DATE)截断时间部分 - 起始日必须是有效日期,建议存为配置表字段,而非硬编码字符串
- 如果排班要支持“跳过节假日”,模运算前得先过滤掉这些日期——这意味着不能只靠单次
DATEDIFF,得预生成日历维度表










