对比两种解法:自连接 + DISTINCT(你之前问的)和 窗口函数(LAG/LEAD)

0 阅读2分钟

image.png

解法 1:自连接 + DISTINCT(基础版)

这是最容易理解的入门写法,核心是 “把表和自己连 3 次,要求 3 行的 Num 相同、Id 连续”。

sql

SELECT DISTINCT l1.Num AS ConsecutiveNums
FROM Logs l1, Logs l2, Logs l3
WHERE 
  l1.Id = l2.Id - 1 
  AND l2.Id = l3.Id - 1 
  AND l1.Num = l2.Num 
  AND l2.Num = l3.Num;

关键解释

  • l1.Id = l2.Id - 1:保证 l1 是 l2 的上一行,l2 是 l3 的上一行(Id 连续)。
  • l1.Num = l2.Num = l3.Num:保证 3 行数字相同。
  • DISTINCT:去重(比如数字连续出现 4 次时,会匹配出 2 组结果,去重后只留一个)。

解法 2:窗口函数(LAG/LEAD)(进阶版)

这是更高效、更灵活的写法,核心是 “取当前行的前 1 行 / 后 1 行数据,对比是否相同”。

sql

SELECT DISTINCT Num AS ConsecutiveNums
FROM (
  SELECT 
    Num,
    -- 取当前行的上一行 Num
    LAG(Num, 1) OVER (ORDER BY Id) AS prev_num,
    -- 取当前行的下一行 Num
    LEAD(Num, 1) OVER (ORDER BY Id) AS next_num
  FROM Logs
) AS temp
-- 核心条件:当前行 = 上一行 = 下一行
WHERE Num = prev_num AND Num = next_num;

关键解释

  1. 子查询 temp 的作用:为每行生成 “上一行 Num” 和 “下一行 Num”,结果如下:

    表格

    Numprev_numnext_num
    1NULL1
    111👉 这行满足:1=1=1
    112
    211
    122
    212
    22NULL
  2. 外层查询:筛选出 “当前行 = 上一行 = 下一行” 的行,再用 DISTINCT 去重(避免重复结果)。


两种解法对比

表格

维度自连接 + DISTINCT窗口函数(LAG/LEAD)
理解难度低(新手友好)中(需要理解窗口函数)
性能差(表连 3 次,数据量大时慢)优(只扫描表 1 次)
灵活性差(改 “连续 4 次” 要连 4 次表)优(改条件只需加 LAG/LEAD)
适用场景入门学习、小数据量实际工作、大数据量

拓展:更通用的窗口函数写法(支持任意连续次数)

如果想找 “连续出现 N 次” 的数字,用 ROW_NUMBER() 分组更灵活:

sql

SELECT DISTINCT Num AS ConsecutiveNums
FROM (
  SELECT 
    Num,
    -- 计算每行在同数字分组内的行号
    ROW_NUMBER() OVER (PARTITION BY Num ORDER BY Id) AS rn,
    Id
  FROM Logs
) AS temp
-- 同一数字的 Id - rn 相同则为连续行,统计连续次数
GROUP BY Num, (Id - rn)
HAVING COUNT(*) >= 3; -- 改这里的数字就能调整连续次数(比如>=4)

总结

  1. DISTINCT 的核心作用:去除重复的结果行,不管是自连接还是窗口函数,只要连续次数超过阈值就需要加。
  2. 自连接写法适合新手理解 “连续” 的逻辑,但性能和灵活性差;窗口函数是实际工作中的首选。
  3. 窗口函数中,LAG/LEAD 适合固定连续次数(如 3 次),ROW_NUMBER() 适合任意连续次数(如 N 次)。