用group by+having找重复分组计数需两层聚合:先按字段分组统计每组行数,再对计数值分组筛选出现次数>1的值,最后关联原始分组字段;cte方式最清晰,窗口函数适用于需额外上下文的场景。

用 GROUP BY + HAVING 找出重复的分组计数
想找出「哪些分组出现了相同数量」,本质是先按字段分组、统计每组行数,再把“行数”当新维度去查重复。不能直接在 WHERE 里用 COUNT(*),因为聚合函数必须配合 GROUP BY,且筛选聚合结果得用 HAVING。
典型错误是写成:SELECT col, COUNT(*) FROM t GROUP BY col HAVING COUNT(*) = COUNT(*)——这永远真,没意义。正确思路是两层聚合:外层把内层的计数结果再分组。
- 先用子查询或 CTE 算出每组的计数,比如
SELECT col, COUNT(*) AS cnt FROM t GROUP BY col - 再对这个
cnt字段做分组,查出现次数 > 1 的值:GROUP BY cnt HAVING COUNT(*) > 1 - 最终把原始分组字段和重复的计数值关联起来(通常用 JOIN 或 IN)
CTE 方式最清晰,避免嵌套过深
CTE 把中间结果具名化,逻辑一目了然,也方便调试。注意别漏掉外层 SELECT 要包含原始分组字段,否则只看到重复的计数值,不知道是哪几组撞上了。
WITH group_counts AS ( SELECT col, COUNT(*) AS cnt FROM t GROUP BY col ) SELECT col, cnt FROM group_counts WHERE cnt IN ( SELECT cnt FROM group_counts GROUP BY cnt HAVING COUNT(*) > 1 );
这里 cnt IN (...) 是关键:内层子查询返回所有「被多个分组共享的计数值」,外层取出对应的具体分组。如果用 JOIN 也能实现,但 IN 更直白,且对 NULL 安全(只要原始 col 不为 NULL)。
窗口函数方案:适用于需要额外上下文的场景
如果除了找重复计数,还要知道某组在所有分组里的排名、累计占比等,窗口函数更灵活。但纯找重复时它略重,且 MySQL 8.0+、PostgreSQL、SQL Server 支持较好,SQLite 和旧版 MySQL 不行。
-
COUNT(*) OVER (PARTITION BY col)先给每行打上所属分组的计数 - 再用
COUNT(*) OVER (PARTITION BY cnt)算出这个计数被多少组共用 - 最后过滤
shared_count > 1即可
注意窗口函数不减少行数,结果集会保留原始所有行,需用 DISTINCT 去重,否则同一分组的多行都会输出。
性能和易错点:COUNT(*) vs COUNT(col)、NULL 处理
如果分组字段 col 允许 NULL,GROUP BY col 会把所有 NULL 归为同一组——这是标准行为,但容易被忽略。若业务上 NULL 应视为不同实体,得提前处理,比如用 COALESCE(col, UUID())(PostgreSQL/MySQL)或 ISNULL(col, NEWID())(SQL Server)。
- 用
COUNT(*)统计行数,不受 NULL 影响;用COUNT(col)会跳过col为 NULL 的行,导致计数偏小 - 大数据量时,子查询方案可能触发两次全表扫描,加
col上的索引能加速第一层GROUP BY - 某些方言(如旧版 MySQL)不支持子查询中引用外层字段,此时必须改用 JOIN 写法
真正麻烦的不是语法,而是搞清「你要的重复,是指计数值重复,还是分组键本身重复」——前者是本题,后者直接 GROUP BY col HAVING COUNT(*) > 1 就行。别混了。











