必须用having而非where过滤count()>1,因为where在分组前执行,无法访问聚合结果;正确顺序是group by→count()→having count(*)>1,否则报错或逻辑错误。

GROUP BY 后 COUNT(*) > 1 为什么查不出重复值?
因为没写 HAVING,写了 WHERE。这是最常踩的坑:聚合结果(比如 COUNT(*))只能在分组后用 HAVING 过滤,WHERE 在分组前执行,根本看不到计数结果。
常见错误写法:SELECT email FROM users WHERE COUNT(*) > 1 GROUP BY email → 直接报错或逻辑错。
- 正确顺序是:
GROUP BY→COUNT(*)→HAVING COUNT(*) > 1 - 如果目标字段可能为
NULL,IN子查询会漏掉这些行,得额外加OR email IS NULL并在子查询里补上HAVING COUNT(*) > 1 OR COUNT(email) != COUNT(*) -
HAVING里不能引用未出现在GROUP BY中的非聚合字段,比如GROUP BY email后写HAVING name = 'Alice'会报错
多字段组合重复时 GROUP BY 要怎么写?
必须把所有参与判断重复的字段全写进 GROUP BY,缺一个就等于放宽了重复定义。
例如查「同一用户同一天对同一商品的重复下单」,GROUP BY user_id, product_id, order_date 是对的;只写 GROUP BY user_id, product_id 就会把不同日期的订单也合并,误判为重复。
- 字段顺序无关紧要,但语义上建议按业务主次排列(如先
user_id,再product_id,最后order_date) - 任一字段含
NULL,多数数据库会把整组视为相同值,若业务中NULL有明确含义(如“未知日期”),需提前用COALESCE(order_date, '1970-01-01')统一处理 - 大表上没索引时,
GROUP BY多字段性能极差,建议建联合索引:CREATE INDEX idx_user_prod_date ON orders(user_id, product_id, order_date)
想看到重复数据的完整行,该用 IN 还是窗口函数?
小表、简单场景用 IN 子查询够用;大表或需要区分“首次/后续重复”时,ROW_NUMBER() 更稳、更灵活。
IN 写法示例:
SELECT * FROM users WHERE email IN ( SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1 );
但要注意:IN 对空值不敏感,且大表易触发全表扫描;EXISTS 替代可提升性能,但语法稍冗长。
-
ROW_NUMBER()示例:SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn FROM users,再筛rn > 1就是纯重复行 - 若要保留每组最新一条(如按
created_at DESC),直接改ORDER BY即可,不用改结构 - PostgreSQL / SQL Server / MySQL 8.0+ 都支持
ROW_NUMBER(),但QUALIFY(如QUALIFY rn = 1)仅 BigQuery、Snowflake 等支持,别在 MySQL 里硬套
COUNT(DISTINCT) 在 GROUP BY 里为什么总返回 1?
大概率是 GROUP BY 里混进了高粒度字段,比如 id、created_at(带毫秒)、order_no 这类几乎唯一的列,导致每组只有一两行,自然 COUNT(DISTINCT xxx) 没机会体现去重效果。
- 检查
GROUP BY列表,只保留真正代表业务维度的字段(如user_id、date、region) - 用
COUNT(*)和COUNT(DISTINCT target_col)对比:如果两者接近,说明分组太细;如果COUNT(*)远大于COUNT(DISTINCT),才说明该维度确实存在重复 - MySQL 5.7+ 默认开启
ONLY_FULL_GROUP_BY,关掉它不是解法——会返回不可靠的随机值,应严格按语义写全GROUP BY
实际跑的时候,先确认你要的是「哪些值重复了」还是「哪些行重复了」,前者用 GROUP BY + HAVING,后者优先上 ROW_NUMBER();中间那个模糊地带——比如既要统计次数又要捞出原始记录——就得嵌套或连表,绕不开。










