考察点
这道题考察的是面试方法论本身。面试官在 SQL 环节不只看结果对不对,还看你拿到模糊需求时的反应——直接闷头写还是先对齐口径。这里把常见题型分类整理,并给一套能复用的答题流程。
参考答案
四大高频题型
1. TopN / 组内排序类
代表题:每个部门工资前三、每个用户最近一次下单、每个品类销量冠军。模板固定:row_number() over (partition by ... order by ...) 编号 + 外层过滤。变化点只在并列语义(rank/dense_rank/row_number 三选一)和排序键是否确定。
2. 连续行为类
代表题:连续登录 N 天、连续三个月消费、最长连续活跃。模板固定:去重 → 窗口编号 → 日期减排名构造分组键 → 分组统计。变体是允许断档时用 lag 标记断点再累计求和分组。
3. 留存 / 转化 / 漏斗类
代表题:次日留存率、注册到下单转化率、各环节漏斗。模板:cohort 表(首日/首行为)自连接算间隔,case when 行转列出报表。漏斗类用「步骤时间先后约束」串联:max(case when step='A' then ts end) < max(case when step='B' then ts end) 判断顺序。
4. 行列转换 / 复杂聚合类
代表题:一行多标签聚合、交叉报表、占比分析。工具箱:collect_set/collect_list + concat_ws,lateral view explode,case when 透视,窗口聚合算占比(amount / sum(amount) over (partition by ...))。
答题框架:四步走
第一步:确认口径(最重要,多数人不做的)
拿到题先问三个问题,既厘清需求又展示专业度:
- 粒度是什么?「用户」是 user_id 还是设备 id?「天」是自然日还是账期?
- 边界怎么算?并列的 TopN 算不算?连续登录断一天行不行?留存是「第 N 天」还是「N 日内」?
- 输入表长什么样?确认字段名、类型(日期是 string 还是 timestamp)、有没有重复行、有没有 null。
面试官故意把题出得模糊,就是在等你问。一句话不问直接开写,写出来的东西口径错了,比写得慢更扣分。
第二步:定中间结果再动笔
复杂题不要试图一条 SQL 写完,先把步骤拆出来:「第一步按用户日期去重,第二步窗口编号,第三步分组键,第四步统计」。说清楚再写,写的时候用 CTE(with 子句)分层,每层一个语义。CTE 分层的好处:逻辑可读、面试官能跟上、写错了好改。
第三步:套模板写主干
四大题型都有成熟模板,先把主干写出来保证正确性,别在这时候炫技。写的时候注意三个基本功:
- distinct 和窗口函数的位置:去重永远放在最内层。
- 日期函数写对:date_sub、datediff、date_trunc,参数顺序别反。
- join 条件完整:多键 join 别漏条件,多对多 join 先确认会不会数据放大。
第四步:补边界和校验
写完主动做三件事:
- 说边界:「如果 update_time 有并列,我加了 id 做 tiebreaker」「如果用户一天都没登录,留存分母不受影响因为 cohort 是首日表」。
- 口头验证:拿一个小数据集(2 个用户、5 行数据)心算一遍中间结果,确认分组键、编号符合预期。
- 提性能:「大表上这个 partition key 如果倾斜可以加盐,生产上我会先看 top key 分布」。一句性能意识能把答案从「会写」拉到「会干活」。
常见扣分点
- 不问口径直接写,写完发现「Top3」的并列语义错了。
- 一条 SQL 嵌套五层不写 CTE,自己都绕晕。
- 日期字段不转 date 直接参与运算,datetime 导致按天聚合全错。
- 写完就交,不主动说边界情况,等面试官挑错。
- 只背模板不理解原理,追问「日期减排名为什么能分组」答不上来。
复习优先级
TopN 和连续行为是必考题,必须脱手写;留存转化类理解 cohort 自连接的思路就能推出来;行列转换记牢 collect 系列和 lateral view 的语法骨架。窗口函数四类(排序/取值/聚合/分布)是所有题型的地基,先把地基打牢再刷题。
可能的追问
- 面试官要求不用窗口函数写 TopN 怎么办? 用 group by max 回 join 的写法:先按组取极值,再 join 回原表拿整行。要主动说明并列极值会返回多条这个缺陷,以及现代引擎里窗口函数是更优解。
- 写完发现面试官说需求理解错了怎么办? 不推翻重写,先确认差异点,然后说「那只需要改第二层 CTE 的分组键」,展示结构化写法的可维护性——这正是拆步骤的红利。
- 线上环境 SQL 方言和 Hive 不一样怎么应对? 提前问目标公司用什么引擎(Hive/Spark/Presto/ClickHouse),函数名有差异(比如 Presto 没有 collect_set 用 array_agg,ClickHouse 是 groupArray),核心逻辑是通用的,说明白思路再调整方言即可。