count(distinct column_name)是最简洁标准的不重复值计数方法,支持主流数据库;需注意不可多加括号、不支持嵌套聚合、null被自动忽略、多列去重语法因库而异。

COUNT(DISTINCT user_id) 是最直接的写法
统计去重后的用户数量,核心就是 COUNT(DISTINCT column_name)。它会先对指定列做去重,再计数,结果是唯一值的个数。别用 COUNT(*) 加 GROUP BY 再套外层查询——既啰嗦又容易漏掉空值处理。
常见错误现象:COUNT(DISTINCT user_id) 返回 0 或比预期少,大概率是 user_id 含 NULL;DISTINCT 会自动忽略 NULL,所以这部分用户完全不计入。
- 如果业务上
NULL表示“未知用户”,且需要算作一个独立类别,得先用COALESCE(user_id, 'unknown')或CASE WHEN user_id IS NULL THEN -1 ELSE user_id END做映射 - 字段类型要一致:比如
user_id有时存为字符串('U123'),有时是数字(123),DISTINCT会当成两个不同值 - MySQL 5.7+ 和 PostgreSQL 支持多列去重,如
COUNT(DISTINCT user_id, event_type),但 SQLite 不支持,会报错near "DISTINCT": syntax error
WHERE 条件必须写在 COUNT 外层
COUNT(DISTINCT user_id) 是聚合函数,不能在 WHERE 里直接过滤去重逻辑。比如想统计“近30天活跃的去重用户数”,不能写 WHERE login_time > DATE_SUB(NOW(), INTERVAL 30 DAY) 然后套 COUNT(DISTINCT user_id) —— 这样没问题;但若误把条件塞进 DISTINCT 里(语法根本不允许),就卡住了。
正确做法是把时间过滤放在 WHERE 子句,再对结果集做 COUNT(DISTINCT user_id):
SELECT COUNT(DISTINCT user_id) FROM user_login_log WHERE login_time >= '2024-05-01';
- 别在
HAVING里加条件:那是分组后筛选,而这里还没分组 - 如果表很大,确保
login_time和user_id上有联合索引,否则全表扫描太慢 - PostgreSQL 中,
DISTINCT在大数据量下可能触发哈希聚合,内存不足时会写磁盘,观察EXPLAIN ANALYZE的HashAgg步骤是否 spill
替代方案:用子查询 DISTINCT + COUNT(*) 更可控
某些场景下,COUNT(DISTINCT ...) 行为不够透明,比如要同时查看去重后的具体用户列表、或后续要和其他指标拼接,用子查询更灵活:
SELECT COUNT(*) FROM ( SELECT DISTINCT user_id FROM user_login_log WHERE login_time >= '2024-05-01' ) AS deduped;
这个写法和 COUNT(DISTINCT user_id) 语义等价,但好处是:你可以随时把 SELECT COUNT(*) 换成 SELECT user_id 查看明细,调试更方便。
- Oracle 早期版本对
COUNT(DISTINCT)优化差,用子查询反而更快 - 如果要去重逻辑复杂(比如按设备+用户组合判重),子查询里写
SELECT DISTINCT device_id, user_id更清晰 - 注意子查询别名(如
AS deduped)在 MySQL 8.0+ 和 PostgreSQL 中是必需的,否则报错Every derived table must have its own alias
别忽略 NULL 和空字符串混用的问题
实际数据里,user_id 字段经常同时存在 NULL、空字符串 ''、空白字符串 ' '。它们在 DISTINCT 中全被视为不同值,但业务上可能都代表“未识别用户”。
- 用
TRIM(user_id) = '' OR user_id IS NULL统一归类,再配合CASE转换 - MySQL 中
''和NULL在ORDER BY中排序位置不同,但在DISTINCT里确实是分开的 - 如果表结构允许,建表时把
user_id设为NOT NULL并设默认值(如'anonymous'),从源头减少歧义
真正麻烦的不是语法怎么写,而是搞清业务里“一个用户”到底指什么:是登录账号?设备 ID?手机号?还是 session token?这些定义不清,再准的 COUNT(DISTINCT) 也算不对。











