COUNT(DISTINCT user_id) 是按日期、渠道或设备类型统计不同用户数的标准解法,分组后对每组单独去重计数,避免重复登录导致虚高,且主流数据库均原生支持。

GROUP BY 后 COUNT(DISTINCT user_id) 是最直接解法
想按日期、渠道或设备类型统计「当天/当渠道有多少不同用户」,COUNT(DISTINCT user_id) 就是标准答案。它在分组后对每个组单独去重计数,不依赖子查询或临时表。
常见错误是写成 COUNT(user_id) —— 这会把重复登录的用户反复计数,结果虚高;或者误用 DISTINCT COUNT(user_id)(语法错误,SQL 不支持这种写法)。
- MySQL 5.7+、PostgreSQL、Snowflake、BigQuery 均原生支持
COUNT(DISTINCT ...) - SQLite 支持,但性能较差,大数据量时建议加
user_id索引 - 旧版 Hive(SELECT COUNT(*) FROM (SELECT DISTINCT user_id FROM t GROUP BY group_col) t2
遇到 NULL 用户 ID 会导致计数丢失
COUNT(DISTINCT user_id) 默认忽略 NULL 值——如果原始数据里有未识别用户(user_id IS NULL),它们不会被计入任何分组的活跃数,但你可能需要知道“未知用户量”。
解决方法不是硬补 COALESCE(user_id, 'unknown')(会污染真实 ID 空间),而是显式拆开统计:
SELECT dt, COUNT(DISTINCT user_id) AS known_users, COUNT(*) - COUNT(user_id) AS unknown_users FROM events GROUP BY dt
这里 COUNT(*) 统计所有行,COUNT(user_id) 只统计非 NULL 的 user_id 行,差值就是该分组中 user_id IS NULL 的数量。
大表上 DISTINCT 计数慢?优先加复合索引
当数据量超千万,COUNT(DISTINCT user_id) GROUP BY dt 可能变慢,不是因为函数本身,而是缺少有效索引导致全表扫描 + 内存哈希去重。
- 最优索引:
CREATE INDEX idx_dt_uid ON events (dt, user_id)—— 覆盖分组键和去重字段 - 避免只建
(user_id, dt):顺序反了,无法高效用于GROUP BY dt - PostgreSQL 用户可考虑
APPROX_COUNT_DISTINCT(user_id)换取 3% 误差和 5x 速度提升(如不需要精确值)
跨多天/多渠道合并活跃用户数不能简单 SUM
如果已按天算出每日活跃数:SELECT dt, COUNT(DISTINCT user_id) AS dau FROM t GROUP BY dt,再想算「过去 7 天总活跃用户数」,不能 对 dau 字段求和——那等于把每天的去重结果又加了一遍,严重高估。
必须回到原始明细表,用窗口或子查询重算:
SELECT COUNT(DISTINCT user_id) AS wau FROM events WHERE dt BETWEEN '2024-06-01' AND '2024-06-07'
或者用 CTE 避免重复写条件:
WITH week_events AS ( SELECT user_id FROM events WHERE dt >= CURRENT_DATE - INTERVAL '6 days' ) SELECT COUNT(DISTINCT user_id) FROM week_events
这个逻辑容易被业务方误解为“DAU 加总 = WAU”,实际是完全不同的集合操作——去重永远得在原始粒度上做,不能在聚合结果上叠加工具。










