基于SQL的学生出勤按日分段排名问题求助
修正学生出勤连续时段排名的SQL问题
需求说明
按日对学生出勤情况进行排名,规则如下:
- 每个学生单独计算排名
- 缺勤(N)当日排名为0
- 出勤(Y)的连续时段从1开始递增排名,被缺勤打断后,新的连续出勤时段需重新从1开始计数
原始数据
| 学生ID | 开始日期 | 结束日期 | 出勤情况(是/否) |
|---|---|---|---|
| 3524 | 01-Jan-2020 | 03-Jan-2020 | N |
| 3524 | 04-Jan-2020 | 06-Jan-2020 | Y |
| 3524 | 07-Jan-2020 | 08-Jan-2020 | N |
| 3524 | 09-Jan-2020 | 12-Jan-2020 | Y |
| 5347 | 04-Oct-2020 | 05-Oct-2020 | Y |
| 5347 | 06-Oct-2020 | 08-Oct-2020 | N |
| 5347 | 09-Oct-2020 | 11-Oct-2020 | Y |
预期输出
| 学生ID | 日期 | 出勤情况(是/否) | 排名 |
|---|---|---|---|
| 3524 | 01-Jan-2020 | N | 0 |
| 3524 | 02-Jan-2020 | N | 0 |
| 3524 | 03-Jan-2020 | N | 0 |
| 3524 | 04-Jan-2020 | Y | 1 |
| 3524 | 05-Jan-2020 | Y | 2 |
| 3524 | 06-Jan-2020 | Y | 3 |
| 3524 | 07-Jan-2020 | N | 0 |
| 3524 | 08-Jan-2020 | N | 0 |
| 3524 | 09-Jan-2020 | Y | 1 |
| 3524 | 10-Jan-2020 | Y | 2 |
| 3524 | 11-Jan-2020 | Y | 3 |
| 3524 | 12-Jan-2020 | Y | 4 |
| 5347 | 04-Oct-2020 | Y | 1 |
| 5347 | 05-Oct-2020 | Y | 2 |
| 5347 | 06-Oct-2020 | N | 0 |
| 5347 | 07-Oct-2020 | N | 0 |
| 5347 | 08-Oct-2020 | N | 0 |
| 5347 | 09-Oct-2020 | Y | 1 |
| 5347 | 10-Oct-2020 | Y | 2 |
| 5347 | 11-Oct-2020 | Y | 3 |
当前错误输出
| 学生ID | 日期 | 出勤情况(是/否) | 排名 |
|---|---|---|---|
| 3524 | 01-Jan-2020 | N | 0 |
| 3524 | 02-Jan-2020 | N | 0 |
| 3524 | 03-Jan-2020 | N | 0 |
| 3524 | 04-Jan-2020 | Y | 1 |
| 3524 | 05-Jan-2020 | Y | 2 |
| 3524 | 06-Jan-2020 | Y | 3 |
| 3524 | 07-Jan-2020 | N | 0 |
| 3524 | 08-Jan-2020 | N | 0 |
| 3524 | 09-Jan-2020 | Y | 4 |
| 3524 | 10-Jan-2020 | Y | 5 |
| 3524 | 11-Jan-2020 | Y | 6 |
| 3524 | 12-Jan-2020 | Y | 7 |
| 5347 | 04-Oct-2020 | Y | 1 |
| 5347 | 05-Oct-2020 | Y | 2 |
| 5347 | 06-Oct-2020 | N | 0 |
| 5347 | 07-Oct-2020 | N | 0 |
| 5347 | 08-Oct-2020 | N | 0 |
| 5347 | 09-Oct-2020 | Y | 4 |
| 5347 | 10-Oct-2020 | Y | 5 |
| 5347 | 11-Oct-2020 | Y | 6 |
问题分析
原代码的核心问题在于:
RANK() OVER (PARTITION BY studentid,attendance ORDER BY [Date])
该写法将同一个学生的所有出勤(Y)记录归为同一分区,导致排名会跨连续时段累加,无法在缺勤打断后重置为1。
修正后的SQL代码
WITH DailyAttendance AS ( -- 将原始区间数据拆分为单日记录 SELECT studentid, DATEADD(day, n - 1, startdate) AS [Date], attendance FROM YourOriginalTable CROSS APPLY ( -- 生成区间内的日期序列 SELECT TOP (DATEDIFF(day, startdate, enddate) + 1) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n FROM master..spt_values ) AS Numbers ), AttendanceGroups AS ( SELECT studentid, [Date], attendance, -- 标记连续出勤的分组ID:每当出勤从N转为Y时,创建新分组 SUM(CASE WHEN attendance = 'Y' AND LAG(attendance, 1, 'N') OVER (PARTITION BY studentid ORDER BY [Date]) = 'N' THEN 1 ELSE 0 END) OVER (PARTITION BY studentid ORDER BY [Date]) AS group_id FROM DailyAttendance ) SELECT studentid, [Date], attendance, CASE WHEN attendance = 'N' THEN 0 -- 每个连续出勤分组内从1开始递增排名 ELSE ROW_NUMBER() OVER (PARTITION BY studentid, group_id ORDER BY [Date]) END AS 排名 FROM AttendanceGroups ORDER BY studentid, [Date];
代码说明
- DailyAttendance CTE:将原始的日期区间数据拆分为单日记录,确保每一天都有独立的出勤记录。
- AttendanceGroups CTE:通过
LAG()函数获取前一天的出勤状态,当当前为Y且前一天为N时,标记新分组;通过累加标记值得到每个连续出勤时段的唯一分组ID。 - 最终查询:缺勤记录直接返回0,出勤记录则在每个学生的分组内用
ROW_NUMBER()从1开始计数,实现连续时段的排名重置。
内容的提问来源于stack exchange,提问作者Beginner
相关产品推荐
相关产品推荐

