大厂面试手撕 SQL 的真正难点
大厂数据分析岗手撕 SQL 的难点不在语法,而在业务理解。面试官给你的题目通常包含业务背景,你需要:
- 理解指标口径——"活跃用户"是日活还是月活?"转化率"的分母是曝光还是点击?
- 明确时间窗口——"近 7 天"包不包含当天?"月度"是自然月还是滚动 30 天?
- 处理边界情况——新用户没有历史数据、某天销售额为 0、存在重复记录等
面试前一定要先熟悉目标公司的核心业务指标,下面是常见业务指标参照表。
常见业务指标参照表
面试前对照这张表,搞清楚你要面的公司最关心哪些指标:
| 业务场景 | 常见指标 | 口径要点 |
|---|---|---|
| 电商(拼多多、美团) | GMV、客单价、复购率、转化率、退货率 | GMV = 下单金额 vs 支付金额 vs 确认收货金额,口径差异大 |
| 广告(字节跳动) | CTR、CVR、CPC、CPM、ROI、ARPU | CTR = 点击/曝光,注意去重(同一用户多次曝光算几次?) |
| 内容/社交 | DAU/MAU、留存率、人均使用时长、互动率 | DAU 通常按设备去重;留存率的基准日是注册日还是首次活跃日 |
| 金融/支付 | 交易额、支付成功率、坏账率、逾期率 | 注意区分发起交易 vs 完成交易,以及不同支付渠道的统计口径 |
| 通用 | 新增用户、活跃用户、流失用户、回流用户 | "流失"定义因公司而异:连续 7 天/14 天/30 天未登录 |
实操建议
拿到题目后,先花 30 秒跟面试官确认:1)指标的定义和口径;2)时间范围;3)是否需要去重。这不是浪费时间,而是展示你的业务敏感度。
连续登录检测
经典的 Gaps and Islands 问题。核心技巧:连续日期减去连续序号 = 常量。
比如一个用户在 4/1、4/2、4/3 登录,用 ROW_NUMBER 编号为 1、2、3。拿日期减序号:
- 4/1 - 1 = 3/31
- 4/2 - 2 = 3/31
- 4/3 - 3 = 3/31
三个日期减出来的值相同,说明它们是连续的。按这个值分组,COUNT(*) >= N 就能找出连续 N 天登录的用户。
练习
连续登录至少 3 天
高频字节美团给定
logins 表,找出连续登录至少 3 天的用户,输出 user_id、连续登录天数、起止日期。点击「运行」查看查询结果
查看答案
参考答案
WITH numbered AS (
SELECT user_id, login_date,
ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY login_date) AS rn
FROM logins
),
grouped AS (
SELECT user_id, login_date,
DATE_SUB(login_date, INTERVAL rn DAY) AS grp_key
FROM numbered
)
SELECT user_id,
COUNT(*) AS consecutive_days,
MIN(login_date) AS start_date,
MAX(login_date) AS end_date
FROM grouped
GROUP BY user_id, grp_key
HAVING COUNT(*) >= 3; 解析
DATE_SUB(login_date, INTERVAL rn DAY) 用日期减去行号,连续日期会得到相同的值,按此分组即可找出连续段。用户 1 有 4/1-4/3 连续 3 天,用户 2 有 4/3-4/6 连续 4 天。
每组 Top-N 与首事件归因
两个常见的窗口函数套路放在一起讲:
- Top-N:
RANK/DENSE_RANK + PARTITION BY + WHERE rnk <= N - 首事件归因:先锁定目标事件的时间,再从目标事件之前的记录里取最近一条
练习
首次下单前的最后访问来源
高频字节给定
page_visits 和 orders 两张表,找出每个用户首次下单前的最后一次页面访问来源。点击「运行」查看查询结果
查看答案
参考答案
WITH first_ord AS (
SELECT user_id, MIN(order_time) AS first_order
FROM orders GROUP BY user_id
),
visits_ranked AS (
SELECT v.user_id, v.source, v.visit_time,
ROW_NUMBER() OVER(PARTITION BY v.user_id ORDER BY v.visit_time DESC) AS rnk
FROM page_visits v
JOIN first_ord f ON v.user_id = f.user_id AND v.visit_time < f.first_order
)
SELECT user_id, source
FROM visits_ranked
WHERE rnk = 1; 解析
先找每个用户的首单时间,再筛出首单之前的所有访问,按时间倒序取第一条。用户 1 首单前最后访问来源是 wechat(4/4),用户 2 是 google(4/7)。
NULL 处理与增长计算
练习
订单增长额
给定
sales 表,计算每个用户每笔订单相比上一笔的增长额。第一笔订单的增长额显示为 0。点击「运行」查看查询结果
查看答案
参考答案
SELECT user_id, sale_date, amount,
amount - COALESCE(LAG(amount) OVER(PARTITION BY user_id ORDER BY sale_date), amount) AS increase
FROM sales; 解析
LAG 第一行返回 NULL,COALESCE(..., amount) 让第一笔订单的前值等于自己,差值为 0。也可以用 LAG(amount, 1, amount) 的第三个参数达到同样效果。