题目重点:合并昨日累计与今日观看,保留只在一侧出现的用户,并处理快照重跑。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练今日day_videos=当天distinct video数,total_videos=这些日计数的累计和;这不是lifetime distinct,同视频跨天可重复计。若累计要求终身不同视频数,需要额外user-video集合,不能直接加。
- snapshot唯一(day,user),读previous_day快照;输出整日替换值,不把相同daily重复UPDATE加到当天结果。原表DDL/累计精确口径需现场核对。
解题路线
- 先今日按user COUNT(DISTINCT video_id),昨日读取user total。FULL OUTER JOIN同时保留旧用户今日不活跃与新用户昨日不存在。
- COALESCE两边user_id取得主键,COALESCE(today,0)给旧用户今日0,COALESCE(yesterday,0)+today得到累计。只用INNER JOIN会同时丢两类单侧用户。
- 指定previous_day必须与当天之前的完整累计状态衔接;若中间缺一天,不是简单选择任意旧快照后跳过,需补回缺日或重算受影响范围。
- 结果写入以(day,user)replace/upsert绝对值,重跑读取固定昨日而非当前日;late event修正今日也要更新后续快照或改成可求和日事实。
- lifetime distinct不能加日distinct:同视频昨天今天各1,日计数和2但终身distinct1。这个统计定义差异要在写SQL前问清,不能只因题提outer join就默认所有累积可加。
手动走一遍
昨日u1=10、u2=5;今日u2观看2个、u3观看1个。结果u1(day0,total10)、u2(2,7)、u3(1,1)。同一批再跑仍同结果,不把u2累加成9。
SQL 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
-- Training: daily distinct video count; cumulative SUM of those daily counts.
-- This is NOT lifetime distinct video count (repeat video across days counts twice).
-- snapshot(day,user_id,day_videos,total_videos), unique(day,user_id).
-- Query produces replacement rows for :day, never incremental UPDATE on retries.
WITH yesterday AS (
SELECT user_id,total_videos FROM snapshot WHERE day=:previous_day
), today AS (
SELECT user_id,COUNT(DISTINCT video_id) AS day_videos
FROM watch WHERE watch_date=:day GROUP BY user_id
)
SELECT COALESCE(y.user_id,t.user_id) AS user_id,
COALESCE(t.day_videos,0) AS day_videos,
COALESCE(y.total_videos,0)+COALESCE(t.day_videos,0) AS total_videos
FROM yesterday y FULL OUTER JOIN today t ON t.user_id=y.user_id
ORDER BY user_id;
复杂度
今日事实扫描O(Wday),去重按user-video集合,双侧join约O(Uy+Ut)hash空间与时间;具体排序计划另计。日快照按U×D规模,晚到后链式重算可能长,需要保留增量事实与版本策略。
易错点与追问
容易失分的边界
- INNER/单侧LEFT JOIN漏单侧用户。
- 累计终身distinct被误实现为每日distinct之和。
- 重跑读当天累计再加,会重复计算。
继续深挖
- 需要累计watch_seconds时哪些指标可直接相加,哪些平均值需保留sum/count?
- 引擎没有FULL OUTER JOIN,可用全体user keys UNION后分别LEFT JOIN模拟。
