基于时间序列的驾驶员用车记录自定义分组排名需求
嘿,我完全懂你的问题——你要的不是那种把同一车辆下所有同驾驶员记录都归为一组的排名,而是要追踪连续的驾驶时段:只要同一辆车的相邻记录驾驶员变了,排名就得更新,对吧?
你之前用的DENSE_RANK() OVER (PARTITION BY CAR_ID, DRIVER_ID ORDER BY DT)之所以会出问题,是因为它不管驾驶员是不是连续驾驶的——哪怕中间插了别的驾驶员,之后再回来的同个驾驶员也会和之前的记录归为同一组,这就是那两个标记#的元素被混在一起的原因。
要实现你要的“连续相同驾驶员才同排名”的效果,我们需要用岛屿问题的经典解法,分三步来做:
步骤1:标记前一条记录的驾驶员ID
首先,我们用LAG()函数,按车辆分组、时间排序,拿到当前记录的上一条记录的驾驶员ID,这样就能对比前后是不是同一个人:
LAG(DRIVER_ID) OVER (PARTITION BY CAR_ID ORDER BY DT) AS prev_driver
步骤2:生成分组切换标记
接下来,我们判断当前驾驶员和上一条是不是不同——如果不同(或者是第一条记录,没有上一条),就标记为1,否则标记为0:
CASE WHEN DRIVER_ID != LAG(DRIVER_ID) OVER (PARTITION BY CAR_ID ORDER BY DT) OR LAG(DRIVER_ID) OVER (PARTITION BY CAR_ID ORDER BY DT) IS NULL THEN 1 ELSE 0 END AS group_flag
这个标记的作用是:每一次驾驶员切换(包括第一条记录),就生成一个“新分组开始”的信号。
步骤3:累加标记得到连续排名
最后,我们按车辆分组、时间排序,对这个group_flag做累加——累加的结果就是我们要的连续排名:
SUM(group_flag) OVER (PARTITION BY CAR_ID ORDER BY DT) AS RES
(注:大多数数据库里,ORDER BY在窗口函数里默认的范围就是从分区开头到当前行,所以可以不用写ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,不过写上也没问题)
把这些整合起来,完整的SQL大概是这样:
WITH driver_segments AS ( SELECT DT, CAR_ID, DRIVER_ID, CASE WHEN DRIVER_ID != LAG(DRIVER_ID) OVER (PARTITION BY CAR_ID ORDER BY DT) OR LAG(DRIVER_ID) OVER (PARTITION BY CAR_ID ORDER BY DT) IS NULL THEN 1 ELSE 0 END AS group_flag FROM your_driving_table -- 替换成你的表名 ) SELECT DT, CAR_ID, DRIVER_ID, SUM(group_flag) OVER (PARTITION BY CAR_ID ORDER BY DT) AS RES FROM driver_segments ORDER BY CAR_ID, DT;
这样运行后,同一车辆下连续的同一个驾驶员会得到相同的RES值,一旦驾驶员切换,RES就会自动递增,完全符合你要的类似DENSE_RANK但只针对连续记录的需求。
内容的提问来源于stack exchange,提问作者yet_another_programmer

