GROUP BY 配合 COUNT() 可判断字段值唯一性:COUNT()=1 表示唯一,>1 表示重复;需用 HAVING 筛选,不可用 WHERE;含 NULL 时多数数据库将其归为一组;多列组合唯一性需在 GROUP BY 中列出所有列;窗口函数 COUNT() OVER (PARTITION BY ...) 更高效且保留原行。

GROUP BY 配合 COUNT() 判断唯一性
直接用 GROUP BY 本身不会告诉你某值是否唯一,必须搭配聚合函数。最常用的是 COUNT(*):结果为 1 表示该组值唯一,大于 1 表示重复。
例如查表 users 中哪些 email 是唯一的、哪些不是:
SELECT email, COUNT(*) AS cnt FROM users GROUP BY email HAVING COUNT(*) = 1;
想看非唯一值,把 = 1 换成 > 1 即可。
-
HAVING必须写在GROUP BY之后,WHERE对聚合前的行生效,不能用COUNT(*) - 如果字段含
NULL,多数数据库(如 PostgreSQL、SQL Server)会把所有NULL归为同一组;MySQL 8.0+ 默认也如此,但旧版可能行为不一致 - 别在
SELECT里选非分组列(如id),否则会报错或返回不确定值(除非用ANY_VALUE()或启用了ONLY_FULL_GROUP_BY以外的 SQL 模式)
用窗口函数避免二次扫描(MySQL 8.0+/PostgreSQL)
如果既要原记录又要标记是否唯一,用 COUNT(*) OVER (PARTITION BY ...) 更高效——不用 GROUP BY 就能给每行打上频次标签。
比如标记每条用户记录的邮箱是否唯一:
SELECT id, email,
COUNT(*) OVER (PARTITION BY email) AS email_cnt
FROM users;
后续可在应用层或外层 WHERE 过滤:WHERE email_cnt = 1 取唯一,WHERE email_cnt > 1 取重复。
- 窗口函数不改变行数,适合保留原始上下文(如想导出所有重复邮箱的完整记录)
- 注意:SQLite 目前(v3.45)支持窗口函数,但某些旧版本不支持;MySQL 必须 ≥ 8.0
- 性能上,单次扫描比先
GROUP BY再JOIN回原表通常更快,尤其数据量大时
处理多列组合的唯一性判断
唯一性常涉及多个字段联合,比如「姓名 + 出生日期」是否重复。这时 GROUP BY 要列出所有目标列,COUNT(*) 统计组合出现次数。
例如检查 orders 表中是否有相同 user_id 和 product_id 的重复下单:
SELECT user_id, product_id, COUNT(*) AS dup_cnt FROM orders GROUP BY user_id, product_id HAVING COUNT(*) > 1;
- 多列
GROUP BY顺序不影响结果,但影响索引能否命中;如有复合索引(user_id, product_id),这个顺序就更利于加速 - 若其中任一列为
NULL,不同数据库对(a, NULL)和(a, NULL)是否算同一组的处理一致(标准 SQL 要求 NULLs 归为一组),但个别方言可能有差异,建议实测 - 别漏掉
HAVING—— 没它就只是分组统计,无法筛选出你要的“非唯一”情况
为什么 DISTINCT 不够用?
DISTINCT 只能去重,不能告诉你某个值到底出现了几次,更无法区分「唯一」和「重复」两类。它输出的是去重后的集合,丢弃了频次信息。
比如 SELECT DISTINCT email FROM users,你根本不知道哪个邮箱只有一条,哪个有几十条。
- 如果目标是「找出所有重复项的原始记录」,
DISTINCT完全无能为力 - 有人误用
GROUP BY但不加HAVING或聚合,结果只是分组列表,看不出重复与否 - 真正关键的分水岭在于:你要的是「存在性判断」(有没有重复)还是「频次信息」(重复几次、哪些行)——前者用
HAVING,后者推荐窗口函数
NULL 值的聚合行为和 HAVING 与 WHERE 的分工边界。写完记得用小数据集验证下 NULL 字段是否按预期归组。

















