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