员工近三个月累计薪水:ROWS 和 RANGE 的区别
LeetCode 579 · 滑动窗口、RANGE 窗口、排除最近一个月
面试怎么回答
先用 MAX(Month) OVER (PARTITION BY Id) 去掉每个人最近一个月,再按 Id 分区、Month 排序做 SUM 窗口。窗口我会写 RANGE BETWEEN 2 PRECEDING AND CURRENT ROW,按月份数值取范围;写 ROWS 的话,月份不连续时会多算更早的月份。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
题意
员工表记录每个员工每个月的薪水。对每个员工,去掉他最近的一个月,然后对剩下的每个月,计算「当月及前两个月」的薪水合计(不足三个月就按实际月份算)。结果按员工升序、月份降序。
表结构
- Employee:Id、Month、Salary;(Id, Month) 为主键
思路
分两步:先标出每个员工的最大月份并排除;再按员工分区、月份排序做滑动窗口求和。原解法用 ROWS BETWEEN 2 PRECEDING 取前两行,但月份可能不连续(比如只有 1、3、4 月),这时「前两行」不等于「前两个月」。用 RANGE BETWEEN 2 PRECEDING AND CURRENT ROW 按月份数值取范围,才是真正的近三个月。
参考解法
select Id,Month,
sum(Salary) over(partition by Id order by Month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) Salary
from
(
select Id,Month,Salary,
lead(Month,1,0) over(partition by Id order by Month) lm
from Employee
)t1
where lm != 0
order by Id,Month desc
另一种写法:用 RANGE 按月份数值取窗口,月份不连续也正确
SELECT Id, Month,
SUM(Salary) OVER (PARTITION BY Id ORDER BY Month
RANGE BETWEEN 2 PRECEDING AND CURRENT ROW) AS Salary
FROM (
SELECT *, MAX(Month) OVER (PARTITION BY Id) AS max_month FROM Employee
) t
WHERE Month < max_month
ORDER BY Id, Month DESC
易错点
- ROWS 按行数取窗口,月份有缺口时会把更早的月份算进来。
- 排除最近一个月要在算完窗口之前还是之后?窗口只往前看,所以先排除再算结果一样;但如果窗口往后看,顺序就会影响结果。