COUNT(DISTINCT user_id)是唯一正确写法,DISTINCT必须作为COUNT的参数修饰符紧贴括号内;错误写法如COUNT DISTINCT user_id或COUNT(user_id, DISTINCT)均报错;WHERE条件须置于COUNT外,不可嵌入括号内。
很多人写成 COUNT DISTINCT user_id,直接报错。SQL 标准语法要求 DISTINCT 是 COUNT 的参数修饰符,必须紧贴括号内,且只作用于单个表达式。正确写法是 COUNT(DISTINCT user_id)。
常见错误现象:
SELECT COUNT DISTINCT user_id FROM events; → 语法错误(如 PostgreSQL/MySQL 报 syntax error at or near "DISTINCT")SELECT COUNT(user_id, DISTINCT) → 无效函数调用实操建议:
COUNT(DISTINCT column_name),哪怕只统计一列COUNT(DISTINCT user_id, event_type),但 MySQL 不支持,会报错NULL,DISTINCT 自动忽略 NULL 值,不参与计数COUNT(DISTINCT user_id) 只负责去重计数,过滤逻辑必须由 WHERE 或 HAVING 承担。有人误以为可以写成 COUNT(DISTINCT user_id WHERE event_time > '2024-01-01'),这是无效语法(除非用条件聚合,但那是另一回事)。
使用场景:统计「近7天活跃用户」
SELECT COUNT(DISTINCT user_id) FROM events WHERE event_time >= CURRENT_DATE - INTERVAL '7 days';
SELECT COUNT(DISTINCT user_id WHERE event_time >= ...)(仅 SQLite 支持这种写法,且非标准)性能影响:加 WHERE 能大幅减少扫描行数,比先 COUNT(DISTINCT) 再过滤高效得多;索引(如 (event_time, user_id))能明显加速这类查询。
当 user_id 去重后仍有千万级唯一值,COUNT(DISTINCT) 通常需要构建哈希表或排序,容易触发磁盘临时表(MySQL)或内存超限(PostgreSQL 的 work_mem 不足)。
可选应对方式:
APPROX_COUNT_DISTINCT(user_id),ClickHouse 用 uniq(user_id),误差率通常 INSERT INTO daily_active_users SELECT CURRENT_DATE, COUNT(DISTINCT user_id) FROM events WHERE event_date = CURRENT_DATE;,查时直接 SUM(count)
WHERE 尽量缩小范围,再 COUNT(DISTINCT)
注意:不同数据库对 COUNT(DISTINCT) 的底层实现差异很大——MySQL 8.0+ 会自动尝试使用临时哈希表,而旧版只能排序;Spark SQL 默认走 MapReduce 阶段,shuffle 开销高。
比如按渠道统计活跃用户:SELECT channel, COUNT(DISTINCT user_id) FROM events GROUP BY channel;。如果某渠道数据全为 NULL,该渠道不会出现在结果中(因为 GROUP BY NULL 被合并成一组,但很多引擎直接跳过)。
容易踩的坑:
channel 为 NULL 时,整行仍参与分组,但结果里可能看不到 NULL 行(取决于数据库默认行为,如 MySQL 5.7 默认显示,PostgreSQL 显示 <NULL>)LEFT JOIN,不能只靠 GROUP BY
COUNT(DISTINCT user_id, session_id),MySQL 不支持,必须改写为子查询或用 CONCAT 拼接(但有长度和字符集风险)真实业务里,“活跃用户”定义常依赖事件类型(如只算 page_view 或 purchase),漏掉 WHERE event_type IN (...) 是最常被忽略的逻辑点。