用 GROUP BY + HAVING 可查出重复组合:SELECT col1, col2, COUNT() AS cnt FROM t GROUP BY col1, col2 HAVING COUNT() > 1;注意联合索引、NULL 处理及避免 JOIN 导致的假重复。

用 GROUP BY + HAVING 查重复组合
想找出哪些 (col1, col2) 组合出现了不止一次,必须用 GROUP BY 配合 HAVING COUNT(*) > 1。DISTINCT 只能去重,不能告诉你“哪些重复了”。
常见错误是写成:SELECT DISTINCT col1, col2 FROM t WHERE COUNT(*) > 1 —— 这语法直接报错,因为 WHERE 不能用聚合函数。
- 正确写法:先按组合分组,再筛出数量大于 1 的组
SELECT col1, col2, COUNT(*) AS cnt FROM t GROUP BY col1, col2 HAVING COUNT(*) > 1- 如果表很大,确保
(col1, col2)上有联合索引,否则Using temporary; Using filesort会拖慢查询 - 注意 NULL:MySQL 中
(NULL, 'a')和(NULL, 'a')被视为相同组合;但(NULL, 'a')和('a', NULL)是不同组合
用窗口函数定位重复组里的具体行
光知道哪几组重复还不够,业务常要求“把重复的全列出来”或“只留最新一条”。这时 ROW_NUMBER() 是最可控的选择。
别用 GROUP BY 硬套明细字段(比如 SELECT *, COUNT(*) FROM t GROUP BY col1, col2),MySQL 允许但其他数据库会报错,且语义模糊。
- 标准写法:用 CTE 或子查询加
ROW_NUMBER() OVER (PARTITION BY col1, col2 ORDER BY updated_at DESC) - 要查所有重复行:
WHERE rn > 1 OR cnt > 1(需先算出每组总行数) - 要保留每组最新一条:
WHERE rn = 1,但得确认updated_at字段非空、有索引 - ORDER BY 里别用无序字段(如
id),否则结果不稳定;优先选带业务含义的时间戳或状态字段
用 EXISTS 或自连接识别“假重复”来源
很多所谓“重复”,其实是 JOIN 导致的基数膨胀,不是真实数据重复。比如订单主表和订单项表关联后,一个订单三条明细就变三行——这不该删数据,该改查询逻辑。
误判这类问题,轻则白忙活,重则删错生产数据。
- 先验证:单独查主表
SELECT id FROM orders WHERE id = ?,看是否真有多条 - 再查关联:用
EXISTS替代JOIN判断是否存在明细,避免结果膨胀 - 或者用自连接找“同一业务键下 ID 不同但其他字段几乎一致”的记录:
SELECT a.* FROM t a JOIN t b ON a.col1 = b.col1 AND a.col2 = b.col2 AND a.id - 这种写法对大表很慢,务必在
(col1, col2)上建索引,且限制a.id 避免重复配对
跨数据库兼容的多列去重统计怎么写
如果目标是“统计有多少种不同的 (job, level) 组合”,而不是列出它们,COUNT(DISTINCT job, level) 最简洁——但只在 MySQL 8.0.22+、SQL Server 2017+、PostgreSQL 支持。
一写就报错?八成是版本不够或语法写错。
- MySQL 旧版或需要兼容:用子查询
SELECT COUNT(*) FROM (SELECT DISTINCT job, level FROM emp) AS _,AS _别名不能省 - PostgreSQL 必须写成
COUNT(DISTINCT (job, level)),外层括号是语法强制要求,漏了就syntax error - SQL Server 2016 及更早:同样只能走子查询,
COUNT(DISTINCT job, level)直接报Incorrect syntax near ',' - 真正容易被忽略的是性能:组合列基数高(比如
user_id, event_time)时,去重过程大概率落盘,这时候索引和前置WHERE过滤比优化 SQL 写法更管用










