LAG/LEAD仅获取排序后物理相邻记录,无法保证数值最接近,需结合COALESCE、ABS、CASE及自连接获取真实“最接近同事”姓名,并处理薪资重复、边界NULL和差值相等等边界情况。

用 LAG 和 LEAD 获取相邻薪资记录
直接对员工按薪资排序后取上下邻,是计算“最接近同事”的高效起点。但要注意:LAG 和 LEAD 只返回排序后物理相邻的行,不保证数值上最接近——比如薪资序列为 [5000, 6000, 12000],6000 的 LAG 是 5000(差1000),LEAD 是 12000(差6000),此时最近的确实是 5000;但若序列为 [5000, 8000, 9000],8000 的 LEAD 差1000,LAG 差3000,也成立。问题在于:当存在多个等差或跨邻更近时,单靠 LAG/LEAD 会漏掉真实最小差值。
实操建议:
- 先用
ORDER BY salary排序,再用LAG(salary) OVER (ORDER BY salary)和LEAD(salary) OVER (ORDER BY salary)分别拿到前一个和后一个薪资值 - 用
COALESCE处理边界(首行无LAG,末行无LEAD),避免NULL导致差值计算失败 - 差值统一用
ABS()计算,否则负数会影响比较
计算左右差值并选出最小的那个
有了左右邻居的薪资,下一步是比大小——但不能简单 MIN(ABS(salary - lag_salary), ABS(lead_salary - salary)),因为这只能得到最小差值,丢失了“是谁”。必须保留左右两组候选者,再做条件判断。
实操建议:
- 用
CASE WHEN分别判断LAG差值是否非空且 ≤LEAD差值(注意LEAD为空时只选LAG) - 为防相等差值(如本人 8000,左7500、右8500),可附加规则:优先取薪资更低者,或加
ROW_NUMBER() OVER (PARTITION BY ... ORDER BY salary)打断并列 - 差值字段建议命名为
diff_to_prev、diff_to_next,避免后续混淆
关联原表获取同事姓名而非仅薪资
LAG/LEAD 只能拉取同窗口内的标量值(如 salary),无法直接带回 name 或 id。硬塞 LAG(name) 会出错——因为 name 没参与排序,窗口函数不知道该取哪一行的 name。正确做法是把排序+差值逻辑封装成 CTE,再自连接原表。
实操建议:
- 在 CTE 中用
ROW_NUMBER() OVER (ORDER BY salary, id)稳定排序(salary相同时用id避免非确定性) - CTE 输出包括
id,name,salary,prev_id,next_id(通过LAG(id)/LEAD(id)获取) - 主查询用
LEFT JOIN分别连两次原表:一次 ont.id = cte.prev_id取左同事,一次 ont.id = cte.next_id取右同事,再用CASE拼出最终匹配的colleague_name
性能与边界情况必须检查
当员工数超万级,或薪资高度重复(如大量 8000 元),LAG/LEAD 的窗口排序开销会上升,且重复值会导致“相邻”失去数值意义——比如 100 人薪资全是 8000,LAG 返回的只是任意前一行,不是真正差值为 0 的同事。
实操建议:
- 给
salary字段建索引,尤其复合索引(salary, id)能加速窗口排序 - 对薪资完全相等的情况,应单独处理:用
COUNT(*) OVER (PARTITION BY salary)判断是否 > 1,若是,直接取同组内id ≠ self.id的任一同事(无需算差) - 测试时务必覆盖单人表、两人表、全同薪资、空薪资等 case,
LAG/LEAD在单行时返回NULL,容易引发ABS(NULL)报错
实际写的时候,最易被忽略的是:差值相等时业务规则未定义,以及薪资重复时“相邻”不等于“最接近”。这两处不处理,结果看似合理,实则随机。

















