解法 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;
关键解释
-
子查询
temp的作用:为每行生成 “上一行 Num” 和 “下一行 Num”,结果如下:表格
Num prev_num next_num 1 NULL 1 1 1 1 👉 这行满足:1=1=1 1 1 2 2 1 1 1 2 2 2 1 2 2 2 NULL -
外层查询:筛选出 “当前行 = 上一行 = 下一行” 的行,再用
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)
总结
DISTINCT的核心作用:去除重复的结果行,不管是自连接还是窗口函数,只要连续次数超过阈值就需要加。- 自连接写法适合新手理解 “连续” 的逻辑,但性能和灵活性差;窗口函数是实际工作中的首选。
- 窗口函数中,
LAG/LEAD适合固定连续次数(如 3 次),ROW_NUMBER()适合任意连续次数(如 N 次)。