“连续重复”指按指定字段(如时间、id)排序后相邻行值相同且成段出现,需先用order by明确行序,再通过lag()比对相邻值并累计求和生成组标识,最后按组计数筛选≥3的连续段。

什么是“连续重复”?先明确判断逻辑
SQL 里没有天然的“连续”概念,它只认行序——而行序取决于 ORDER BY。如果你没写 ORDER BY,数据库返回的顺序不保证稳定,所谓“连续”就无从谈起。所以第一步必须明确:按哪个字段排序?通常是时间戳(created_at)、自增 ID(id)或业务序号(step_no)。否则子查询再精巧也查不准。
常见错误是直接对原始表做 GROUP BY value,那查出来的是“总共有几次重复”,不是“哪几行连续重复了三次”。要定位连续段,得把“相邻行是否相同”这个状态算出来。
用窗口函数标记连续组(推荐方案)
核心思路:用 LAG() 拿上一行的值,和当前行比较;再用累计求和生成“连续组ID”。这是目前最清晰、可读性最强、性能也较可控的做法。
假设表叫 logs,字段有 id、status,想找出 status 连续出现 ≥3 次的记录:
SELECT id, status
FROM (
SELECT id, status,
SUM(is_new_group) OVER (ORDER BY id) AS group_id
FROM (
SELECT id, status,
CASE
WHEN LAG(status) OVER (ORDER BY id) = status THEN 0
ELSE 1
END AS is_new_group
FROM logs
) t1
) t2
WHERE group_id IN (
SELECT group_id
FROM (
SELECT group_id, COUNT(*) AS cnt
FROM (
SELECT id, status,
SUM(is_new_group) OVER (ORDER BY id) AS group_id
FROM (
SELECT id, status,
CASE
WHEN LAG(status) OVER (ORDER BY id) = status THEN 0
ELSE 1
END AS is_new_group
FROM logs
) t1
) t2
GROUP BY group_id
HAVING COUNT(*) >= 3
) t3
);
注意点:
-
ORDER BY id必须和外层一致,否则SUM() OVER分组错乱 - 如果
status可能为NULL,LAG(status) = status会返回UNKNOWN,得改成LAG(status) IS NOT DISTINCT FROM status - 某些旧版 MySQL(
MySQL 5.7 或更老版本怎么处理?
只能靠自连接或变量模拟行号。变量法快但不稳定(执行计划可能重排顺序),自连接更可靠但性能差。
用自连接找连续三行(按 id 排序):
SELECT DISTINCT l1.* FROM logs l1 JOIN logs l2 ON l2.id = l1.id + 1 AND l2.status = l1.status JOIN logs l3 ON l3.id = l1.id + 2 AND l3.status = l1.status;
适用场景有限:
- 只适合找固定长度(如连续3次),扩展到 ≥N 次就得加 N−1 个 JOIN,难维护
- 要求
id是严格递增且无空缺,否则l2.id = l1.id + 1会漏掉真实连续但 ID 不连号的情况 - 若排序字段不是主键,得先生成带序号的临时结果,再自连接,复杂度翻倍
为什么别用子查询套子查询硬拼?
有人试图这样写:SELECT <em> FROM logs WHERE status IN (SELECT status FROM logs GROUP BY status HAVING COUNT(</em>) >= 3)——这查的是“全局高频值”,完全忽略顺序。哪怕加了 ORDER BY,子查询里也没法表达“相邻”。
真正需要的不是“哪些值重复多”,而是“哪些位置构成连续段”。只要没引入行间依赖(比如 LAG、自连接、变量),就不可能正确建模连续性。
连续重复的本质是序列模式匹配,SQL 不是正则引擎,得靠窗口函数或连接显式构造上下文。漏掉排序依据、忽略 NULL 处理、或误用聚合子查询,基本等于白跑。










