正确做法是用count(distinct (col1, col2))行构造器或子查询select count(*) from (select distinct col1, col2 from t) as deduped;mysql 5.7及更早需concat拼接并处理null,postgresql/mysql 8.0+支持行构造器,sqlite必须用子查询,所有引擎均兼容子查询方案。

用 COUNT(DISTINCT ...) 统计多字段组合去重数量
直接写 COUNT(DISTINCT col1, col2) 会报错 —— 大多数 SQL 引擎(如 MySQL 5.7、PostgreSQL、SQL Server)不支持对多个字段直接用 DISTINCT 修饰。正确做法是把多字段“打包”成一个逻辑单元。
最通用且兼容性最好的方式是用 COUNT(DISTINCT CONCAT(col1, '|', col2)),但要注意分隔符必须确保不会出现在实际数据中;更稳妥的是用行构造器(row constructor),比如 PostgreSQL 和 MySQL 8.0+ 支持 COUNT(DISTINCT (col1, col2))(注意括号不能省)。
- MySQL 5.7 及更早版本:只能用
CONCAT或CAST拼接,例如COUNT(DISTINCT CONCAT(col1, '_', col2)) - MySQL 8.0+ / PostgreSQL:支持
COUNT(DISTINCT (col1, col2)),语义清晰且无拼接风险 - SQLite 不支持多字段
DISTINCT,必须用子查询或GROUP BY+COUNT(*) - 如果字段含
NULL,CONCAT会导致整条结果为NULL,建议先用COALESCE(col1, '')处理
用子查询 + COUNT(*) 替代 DISTINCT(兼容所有数据库)
当目标数据库不支持多字段 DISTINCT,或者你担心拼接冲突时,子查询是最安全的兜底方案。本质是先 GROUP BY 去重,再统计行数。
SELECT COUNT(*) FROM ( SELECT DISTINCT col1, col2 FROM my_table ) AS deduped;
这个写法在所有主流 SQL 引擎里都有效,且语义明确:每一行代表一个唯一的 (col1, col2) 组合。
- 性能上,大表可能比原生
COUNT(DISTINCT ...)稍慢,因为要物化中间结果集 - 某些引擎(如 MySQL)会对
SELECT DISTINCT自动利用索引,但子查询外层的COUNT(*)无法复用该优化 - 如果只查数量不关心具体值,别在子查询里加
ORDER BY或多余字段,避免拖慢执行
GROUP BY 后加 HAVING 能否替代?
不能。如果你看到有人写 SELECT COUNT(*) FROM (SELECT col1, col2 FROM t GROUP BY col1, col2),它和 SELECT COUNT(*) FROM (SELECT DISTINCT col1, col2 FROM t) 效果一样 —— 但 GROUP BY 本身不是去重手段,而是聚合操作;只有配合 DISTINCT 或作为子查询的中间步骤才有意义。
常见误解是认为 HAVING COUNT(*) > 1 能统计“有多少种组合”,其实那是统计重复组合的数量,不是组合总数。
-
SELECT COUNT(*) FROM (SELECT col1, col2 FROM t GROUP BY col1, col2)✅ 等价于去重计数 -
SELECT COUNT(*) FROM t GROUP BY col1, col2❌ 返回多行,不是总数 -
SELECT COUNT(*) FROM t HAVING COUNT(*) > 1❌ 语法错误,HAVING必须跟GROUP BY
字段类型差异带来的隐式转换陷阱
数值型和字符串型字段混用时,CONCAT 可能触发意外转换。例如 col1 INT = 123 和 col2 VARCHAR = '0123',CONCAT(col1, col2) 结果都是 '1230123',导致本应不同的组合被合并。
- 避免直接拼接不同语义类型的字段,比如 ID 和 code
- 用显式格式化:MySQL 用
CONCAT_WS('|', CAST(col1 AS CHAR), col2),PostgreSQL 用col1::TEXT || '|' || col2 - 如果字段本身含特殊字符(如
|、_),拼接前先做转义或改用哈希(如MD5(CONCAT(...))),但哈希有极低碰撞概率,仅限非关键场景
真正麻烦的不是语法怎么写,而是字段语义是否允许“组合唯一”——比如时间戳精度丢失、字符串前后空格未 trim、大小写未统一,这些都会让去重结果偏离预期。











