SQL 怎么计算每日累计UV?为什么不能把每日UV累加?
面试怎么回答
固定起点的累计UV,可以先在统计范围内按用户取最早活跃日期,再统计每日首次人数并累计求和。每天UV相加会重复计算跨日用户。要明确月内累计还是全历史累计,缺失日期用日历补齐但不把采集缺失当零。迟到事件会把首次日向前移动,需要修正受影响区间;这套方法不能直接替代滚动七日UV。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
周一来了A、B,周二来了B、C。每日UV都是2,但周二的累计UV只能是3。去重发生在哪个范围,比SQL里有没有DISTINCT更重要。
固定统计范围内,每位用户只在首次出现日贡献一次,再累计每日首次人数;滚动窗口和范围外历史需要另行定义。
日去重和区间去重是两件事
每天的UV回答当天有多少不同用户;累计UV回答从固定起点到当天,至少出现过一次的不同用户数。用户跨日复访,会被多个日UV重复包含。
这里讨论九月内累计活跃用户,不是历史累计注册用户。八月来过、九月第一次活跃的用户,仍应该在九月累计活跃中出现;不能直接使用全历史首次访问日筛九月新客。
算法成立的关键是把每个用户的贡献变成唯一的一次:在当前统计范围内,第一次出现时加一,后续再出现不再增加。

先得到首次日,再累计每日首次人数
假设activity_day已经按统一业务时区产生日期,用户标识有效;示例是 PostgreSQL 17 风格SQL,统计到九月三十日。
WITH first_seen AS (
SELECT user_id, MIN(biz_date) AS first_date
FROM activity_day
WHERE biz_date >= DATE '2026-09-01'
AND biz_date < DATE '2026-10-01'
AND user_id IS NOT NULL
GROUP BY user_id
), daily_new AS (
SELECT first_date, COUNT(*) AS new_uv
FROM first_seen
GROUP BY first_date
)
SELECT c.biz_date,
SUM(COALESCE(n.new_uv, 0)) OVER (
ORDER BY c.biz_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_uv
FROM calendar_day c
LEFT JOIN daily_new n ON n.first_date = c.biz_date
WHERE c.biz_date >= DATE '2026-09-01'
AND c.biz_date < DATE '2026-10-01'
ORDER BY c.biz_date;
日历表必须每天唯一一行。这里补零的是“当天没有首次用户”,前提是当天采集完整;如果源分区尚未就绪,应显示数据未完成,而不是先输出一条看似平稳的曲线。
要按渠道分别累计,则首次判定粒度改成渠道与用户。各渠道累计UV仍不能相加成全站累计,因为同一用户可能进入多个渠道。
这个方法不是滚动窗口的通用替身
固定起点累计只有加入,没有因为时间前移而自然移出。滚动七日UV则要求用户在最近七日仍存在,首次访问发生很久以前的用户也可能再次进入窗口。
因此不能拿历史首次人数做七日窗口SUM来冒充七日活跃。后者需要维护窗口内用户存在性,或者合并可表达用户集合的结构,具体选型取决于精度和成本要求。
固定累计也不意味着结果永远只增不减。用户合并、无效事件清理或统计口径变更,可能修正历史结果;单调性检查必须建立在输入版本不变的前提下。
一个典型场景:月底累计比月活还大
假设报表的月底累计UV大于同月明细去重人数,最初怀疑月底漏采。实际上日UV每一天都正确,只是报表把它们直接相加。
原来的验数只对日维度,没有检查月底累计是否等于同范围月活。改为首次日法后,还需要加一条跨粒度不变量:同一数据版本下,最后一天累计等于整段范围的去重人数。
再假设一条迟到记录把某用户首次日从三日移到一日,不能只给新到达那天加一。应调整首次记录和相应每日首次人数,再修正一日至二日的累计结果,三日起人数不变。

把增量维护的边界写进设计
维护首次表时要有稳定用户键,允许更早日期更新,并保留数据版本或修正批次。只做不存在时插入,会漏掉迟到事件带来的首次日变化。
删除用户唯一一条早期活动记录时,原先的MIN也可能失效,需要从剩余事件重新求首次日。规模较大时可以只回溯受影响用户,但不能假设首次表是永远不变的静态维表。
面试回答这题,先给出简单精确算法,再说明累计范围和修正边界,比一上来堆叠去重技术名词更有说服力。