HAVING COUNT(*) > N 必须配合 GROUP BY,因 WHERE 在聚合前执行故不能用聚合函数;多列重复需 GROUP BY 所有判定字段;查原始重复行宜用窗口函数;动态阈值需 CTE 或变量。

直接用 HAVING COUNT(*) > N 即可,但必须配合 GROUP BY,且不能写在 WHERE 里——这是最常踩的语法坑。
为什么 WHERE COUNT(*) > 2 会报错?
WHERE 是行级过滤,在分组和聚合发生前就执行,此时 COUNT(*) 还没算出来,数据库根本不知道“这一组有多少行”。强行写会触发类似 ERROR: aggregate functions are not allowed in WHERE 的错误。
正确做法是:先 GROUP BY 分组,再用 HAVING 对聚合结果筛选。
-
HAVING必须紧跟GROUP BY,顺序不能颠倒 -
HAVING可以引用SELECT中的别名(部分数据库如 PostgreSQL 支持,MySQL 8.0+ 也支持),但稳妥起见建议重复写表达式 - 如果只关心哪些值重复超限,
SELECT里可以只写分组字段;若需计数,加上COUNT(*)
示例:查登录次数 ≥ 3 的用户
SELECT user_id, COUNT(*) AS cnt FROM login_log GROUP BY user_id HAVING COUNT(*) >= 3;
多列组合重复时,GROUP BY 要包含所有判定字段
业务中“重复”往往不是单字段决定的,比如“同一用户在同一天多次下单”才算异常。这时重复判定依据是 (user_id, order_date) 组合,GROUP BY 必须同时列出:
- 漏掉任一列,分组逻辑就错了,可能把不同日期的记录误合并
- 如果表有主键或唯一索引,注意它不影响逻辑分组——
GROUP BY只看显式写的字段 - 字段顺序不影响结果,但为可读性建议按业务语义排列(如先
user_id后order_date)
示例:查每个用户每天下单 ≥ 5 次的记录
SELECT user_id, order_date, COUNT(*) AS daily_order_cnt FROM orders GROUP BY user_id, order_date HAVING COUNT(*) >= 5;
要返回所有原始重复行(不止分组摘要),得用窗口函数
上面两个方法只返回分组键和计数,拿不到每条具体记录。如果需要导出全部重复数据做人工核查或下游处理,COUNT(*) OVER (PARTITION BY ...) 更直接:
- 窗口函数不改变行数,每行都带一个“当前分组内总行数”
- 配合
WHERE就能筛出所有属于高频分组的原始行 - 比子查询或自连接更简洁,性能通常更好(尤其大数据量)
- 注意:MySQL 5.7 不支持窗口函数,需 8.0+;PostgreSQL、SQL Server、Oracle 均支持
示例:取出所有 email 出现 ≥ 2 次的完整用户记录
SELECT * FROM ( SELECT *, COUNT(*) OVER (PARTITION BY email) AS email_cnt FROM users ) t WHERE email_cnt >= 2;
真正容易被忽略的是:重复阈值 N 往往不是固定数字,而是动态计算出来的(比如“高于平均值的 2 倍”或“Top 10% 分位数”)。这时候就得嵌套 CTE 或用变量,不能再靠单层 HAVING 解决。

















