秋招季总有人跑来问我:游戏公司的数据仓库开发工程师,笔试到底考什么?最近一个学弟把当年参加搜狐畅游2020校招笔试的回忆版题目发给我,让我帮他捋一捋复习方向。我把整套题过了一遍,最大的感受是:这套笔试题考的不是你会背多少概念,而是你有没有真正理解数仓从业务需求到数据落地的完整链路。尤其是最近“用户订单分析数据仓库”“维度表和事实表”这类热词频繁出现,说明行业对校招生的要求越来越明确——不是招一个只会写SQL的取数机器,而是招一个能理解数据模型、会做数仓设计的准工程师。
接下来我会从题型构成、维度建模、SQL实战、备考经验四个角度做一次完整复盘。别看这套题标着2020年,这类校招笔试的底层逻辑到现在依然适用,甚至可以说,把热点里的“核心维度表和事实表”吃透,比盲目刷一百道面试题都有用。
1. 拆解搜狐畅游笔试:数据仓库开发工程师到底考什么
1.1 题型构成与考察逻辑
校招笔试的编制思路通常不是“一套题定生死”,而是用综合题过滤一批人,再用专业题筛选出真正有数仓感觉的候选人。搜狐畅游这套数据仓库开发工程师的卷子,按网上回忆版和同类游戏公司笔试题的常见结构来看,大体可以分成这么几块:
| 题型 | 考察点 | 常见形式 |
|---|---|---|
| 数仓基础概念 | 维度建模、事实表与维度表区别、数仓分层、ETL流程 | 选择、填空、简答 |
| SQL手写 | 窗口函数、留存率、分组TopN、连续登录、聚合统计 | 手写SQL,给表结构和数据 |
| 数仓建模设计 | 给定业务场景,设计事实表、维度表、分区策略 | 设计题,需要画出表结构或写建表语句 |
| 业务分析 | 指标口径、数据质量、埋点日志和订单数据关联 | 主观题,给出分析思路 |
这套结构看起来四平八稳,但真正的筛选点在后面两类题。基础概念题大家复习一下都能答出个大概,SQL题很多人也能写出来,但写出来的SQL是不是能处理重复数据、是否考虑了分区裁剪、遇到数据倾斜怎么办,这些才是区分“背过题”和“真做过”的地方。
说白了,校招不可能指望你有一两年大厂数仓实战经验,只能通过这些问题判断你有没有形成“从业务到表结构再到指标”的完整数仓思维。如果你能在设计题里答出“为什么这样设计”,而不是只扔出一堆字段,那在阅卷人眼里就已经高一档了。
1.2 题目背后的岗位画像:游戏数仓开发在做什么
笔试考什么,往往是由岗位日常做什么决定的。游戏公司的数仓开发,日常工作可以概括成:把散落各处的游戏埋点日志、订单流水、渠道数据、登录日志全部统一接入、清洗、建模、分层,最终变成运营、策划、发行看得懂、查得快的报表和分析数据集。
这就决定了笔试绝对不会考很难的算法,也不怎么考JVM调优,重点就是数据仓库建模和SQL能力。比如游戏运营每天要看的新增用户数、DAU、次日留存、付费率、ARPU、LTV,这些指标背后全都依赖一张张事实表和维度表的合理设计。笔试里出现“新增用户次日留存偏低,怎么从数据上定位原因”这类题,本质上就是看你能不能把业务问题翻译成数据问题。
再往深处说,游戏公司比一般互联网公司更看重渠道数据和买量归因。一款游戏上线,广告投放花出去几百万,运营需要知道每个渠道带来的用户质量怎么样、付费能力怎么样。这背后就是用户维度、渠道维度、订单事实表、活跃事实表之间的交叉分析。所以笔试题经常拿“用户订单”做背景,因为订单是游戏公司最核心的变现环节,没有之一。
2. 把用户订单拆成维度和事实:游戏数仓建模的实战推演
2.1 先定业务过程和粒度
很多人一上手做设计题就急着列字段,这是最大的误区。建模第一步,是先定义“你究竟要分析哪条业务过程”。用户订单分析这个场景,业务过程就是“用户在游戏内完成一次支付”。有了业务过程,接下来就要定粒度,也就是事实表里每一行到底代表什么。
订单事实表的粒度,通常是一行代表一笔支付订单。举个例子:某玩家同一天早上通过iOS渠道充值了一个6元首充档,晚上又通过官网活动充值了一笔98元档,那订单事实表里就应该产生两行记录,都关联同一个用户维度键,但订单号不同、渠道维度不同、支付时间不同。如果你把粒度定成“一个用户一天一行”,那这两笔钱就会被揉在一起,渠道分析、档位分析、活动效果分析全都会乱掉。
笔试题里,粒度写没写清楚,是最容易被扣分的地方。我见过很多候选人设计表的时候字段写得密密麻麻,但问他一行的粒度是什么,支支吾吾说不出来。实际上粒度写不明白,后面所有统计逻辑都会飘。答题时第一行就写清楚“粒度:每笔支付订单一行”,这相当于给阅卷人一个定心丸。
2.2 事实表:度量指标的设计与选择
事实表是数据分析的“主体”,里面装的是业务过程产生的度量值。订单事实表的核心字段,大致长这样:
| 字段类型 | 字段示例 | 说明 |
|---|---|---|
| 维度外键 | user_id, channel_id, product_id, date_id | 关联用户、渠道、道具商品、时间等维度表 |
| 退化维度 | order_id, order_no | 订单号直接放事实表,方便查明细和去重 |
| 度量字段 | order_amount, pay_amount, discount_amount, item_count | 订单金额、实付金额、优惠金额、道具数量 |
| 状态字段 | pay_status, order_status, refund_status | 成功、失败、退款等状态,用于口径过滤 |
注意度量字段的“可加性”问题。订单金额、实付金额、道具数量都是可加性度量,任意维度组合都能直接sum,没问题。但比率类指标,比如付费率、转化率,就不能直接sum,必须先算分子分母再相除。这种题笔试里经常挖坑,比如让你统计各渠道总收入,你直接sum一个“付费率”字段,那就明显露怯了。
还有一个隐蔽细节:退单怎么处理。是支付成功后进入事实表,退款时把状态置为已退款,还是直接又插入一条负金额的抵消记录?这两种方案在实际项目里都有人用,但口径必须统一。如果笔试题给了状态字段,答案里最好主动说明“只统计订单状态为支付成功的记录,退款单用状态字段排除或者以负值冲减”,这样能体现出你对数据质量的敏感度。
2.3 维度表:常用维度与缓慢变化维处理
维度表是事实表的“通讯录”,用来描述事实数据的角度。订单分析常用的维度表有这几种:
- 用户维度:用户ID、注册时间、注册渠道、首次登录时间、区服、设备型号、新手引导完成状态。
- 渠道维度:渠道ID、渠道名称、渠道类型(官方包/广告投放/应用商店/线下活动)、推广活动归属。
- 商品或道具维度:道具ID、道具名称、档位、原价、现价、道具类型,以及所属的礼包活动。
- 时间维度:日期、周几、是否节假日、是否活动日、所属自然周/月。
维度表最难的点是缓慢变化维,也就是SCD问题。举个游戏场景下的例子:一个玩家一开始是通过广告渠道注册的,后面被归因到另一个渠道,渠道维度要怎么改?如果直接覆盖原名,那历史订单的渠道归属就全变了;如果保留原值不改,那后续分析又拿不到最新信息。实际中最常用的方案是拉链表,通过增加生效时间、失效时间和当前标志位,让维度表既能还原历史,也能支撑最新状态的查询。
笔试里如果出现“用户VIP等级持续变化,订单数据要按当时VIP等级分析”这种题,能想到用拉链表设计用户VIP维度,基本就是加分项。另外,订单号、支付流水号这类属性,不需要单独建维度表,直接退化放到事实表里,既能减少join又能方便排查问题,这也是维度建模里的经典设计。
2.4 数仓分层设计在订单场景中的落地
数仓分层几乎是必考题,但在订单场景里怎么落地,很多人只会背“ODS、DWD、DWS、ADS”这串字母,不知道每层到底放什么。我用订单分析场景串一遍:
- ODS层:原样接入订单流水、支付回调日志、埋点事件日志,尽量保持和源系统一致,不做过多的数据清洗。
- DWD层:订单明细表,对ODS数据做清洗、去重、格式统一、枚举值规范,补全用户维、渠道维等外键。这一层是事实表的真正落地位置,粒度还是每一笔订单一行。
- DWS层:按用户维度汇总的每日付费表、按渠道维度汇总的每日收入表、按道具维度汇总的销售统计表。这里是公共汇总层,存储的是经过合理粒度汇总的指标。
- ADS层:面向具体报表需求生成的数据,比如运营看板要的“活动期间各渠道ARPU”,或者是“大额付费用户榜单”。
这里想特别强调DWS层的作用。如果每个报表需求都直接从DWD层拉数,每一次都扫全量订单明细,又慢又浪费资源,而且不同报表算出来的口径还可能对不上。DWS层把高频指标提前算好,业务方查询的时候直接按维度过滤聚合,效率和口径一致性都会好很多。笔试设计题能写出这一层的设计思想,说明你不是光会建表,而是真的理解数仓分层的价值。
3. SQL题拉开的分差:数据质量与业务口径
3.1 留存率计算:一道典型的Hive SQL笔试改编题
留存率是游戏数仓最经典的面试题,笔试出现的频率也极高。题目通常给两张表:一张用户注册表,记录每个用户的注册日期;一张活跃表,记录用户每天的登录行为。要求计算某种渠道或全量新增用户的次日留存率。
这类题本身不难,但坑很多。先看一个标准写法,Hive/Spark SQL语法都能跑:
-- 用户注册表:user_reg(reg_date, user_id, channel_id) -- 用户活跃表:user_active(active_date, user_id) with new_users as ( select user_id from user_reg where reg_date = '2020-08-01' ), active_next as ( select distinct user_id from user_active where active_date = '2020-08-02' ) select count(a.user_id) as new_user_cnt, count(b.user_id) as retained_user_cnt, round(count(b.user_id) / count(a.user_id), 4) as retention_rate from new_users a left join active_next b on a.user_id = b.user_id;先说说为什么这么写。第一步先把“2020-08-01注册的用户”取出来,得到新增用户集合;第二步取“2020-08-02活跃的用户”并去重,因为一个用户一天可能登录很多次,不去重就会把留存人数算高;第三步用left join把新增用户和次日活跃用户关联起来,再统计人数和比率。
这种题的加分点在于,你能主动说明口径。比如“新增用户”的定义是注册成功就算,还是需要完成创角才纳入统计;“次日”是自然日还是按用户首次登录时间往后推24小时;是否要剔除测试账号和内部账号。你在答案里写一句“按自然日统计且剔除测试账号”,阅卷人立刻知道你在真实项目里待过,而不是只会套模板。
3.2 窗口函数三板斧:连续登录、分组TopN、滑动计算
窗口函数是数据仓库笔试的高频区,连续登录、分组TopN、滑动计算这三类题,基本是“必刷三件套”。
先看连续登录。经典问题是“找出每个用户连续登录天数最长的区间”。核心思路是去重后,用row_number()给每个用户的登录日期编号,然后用登录日期减去编号得到一个“标记日期”。同一段连续登录的记录,它们的标记日期是一样的。最后按用户和标记日期分组,统计天数即可。这个巧妙点在于把“连续性”转化成了“差值相等”,在笔试现场能写出这个思路的,说明是真正练过窗口函数。
再看分组TopN。比如“统计每个渠道收入排名前三的用户”,只需要用row_number()或rank()按渠道分区、按收入降序排列,然后取rn <= 3。注意row_number和rank的区别:如果两个人收入一样,row_number会随机分1和2,rank会都给1然后跳过2。题目如果要求并列,就要选rank。
滑动计算比如“近7天每个用户的累计付费金额”,用sum(amount) over(partition by user_id order by pay_date rows between 6 preceding and current row)就能实现。这道题考的不只是函数语法,还考察你是否理解窗口的范围定义。总体来说,窗口函数题没有太多捷径,把执行顺序搞明白——先where过滤、再分组、再开窗——比背一百道SQL题都管用。
3.3 笔试里最容易失分的脏数据与口径处理
校招笔试很多时候不会给你一份“干干净净”的数据,而是故意埋一些脏数据。比如订单表里有重复订单、有支付失败的记录、有测试账号的充值、有退款单。这种题想看你有没有真实的数据处理经验,而不是理想化的“select sum(amount) from order”。
我实际工作中就遇到过,线上订单表里居然有超过10%的重复流水,原因是支付回调的重试机制导致同一笔订单被写入多次。在这种数据状态下,直接按订单号去重、保留每个订单最新状态的那条记录,是必须有的操作。笔试里如果遇到类似表结构,答题时第一句话就应该写明“先按订单号去重,按支付时间取最新一条,并过滤支付失败和退款记录,再统计收入”。
还有一个很多人忽略的点:业务口径。比如“付费收入”到底是毛收入还是净收入?是否剔除退款?一个订单分了三期支付算一单还是三单?这些口径不定义清楚,SQL写得再漂亮也没用,因为算出来的数字不一样。笔试阅卷人往往更看重你有没有主动定义口径的意识,这比SQL语法对不对更能说明你的数仓素养。
3.4 SQL之外的隐形加分项:分区、压缩、数据倾斜
有些笔试设计题不要求你真跑一个SQL,但你可以在方案里顺手写出工程化的细节,这是隐形加分项。
比如订单表规模巨大,该怎么设计分区?比较常见的是按天分区,数据量特别大时再考虑按渠道或业务线做二级分区。存储格式上,生产环境一般用ORC或Parquet,配snappy或zstd压缩,能大幅度减少扫描量。笔试答题时加一句“采用ORC格式按天分区存储”,会显得你懂真正的生产环境,而不是只会写demo。
另一个高频考点是数据倾斜。游戏数仓里“官方包”这种渠道用户量极大,join的时候很容易把某个reduce任务压垮。常见的解法包括:大key加随机前缀打散,然后再做二次聚合;或者利用map join让小表直接加载到内存,避免shuffle;再或者先用过滤条件把大部分数据剔除再join。能把倾斜问题讲出两三招的人,实际工作里大概率已经处理过真实数据了。
4. 从笔试到入职:数据仓库开发岗的备考路线与实战心得
4.1 知识框架搭建与资料选择
如果你现在正准备校招,想体系化地复习数据仓库方向,我建议按四个阶段来安排时间。
第一阶段是数仓基础,核心就是数仓分层、维度建模理论、事实表和维度表的设计方法。这阶段推荐《数据仓库工具箱》也就是Kimball那本经典书,重点看维度建模的部分,不用从头啃到尾,把维度和事实的设计准则看完,配合例子动手画一遍表结构,比只看书强很多。
第二阶段是Hive和Spark SQL,重点练窗口函数、复杂查询、UDF/UDAF的基本写法。这个没有捷径,就是刷题,但刷完每道题最好都记一下这题的“考点关键词”,比如“distinct去重”“row_number用于连续问题”“left join算留存”等,方便考前快速过。
第三阶段是真实场景的建模练习,可以找一个订单数据自己设计一套从ODS到DWS的表结构,再把几个核心指标的SQL写出来。这是笔试设计题的关键对应能力,也是面试时能拿出来讲的项目素材。
第四阶段是周边知识,包括调度工具Airflow/DolphinScheduler的基本原理,元数据和数据血缘的概念,数据质量监控的常用手段。这部分笔试不一定考,面试却大概率会聊到。
4.2 做一次“从零到一”的建模练习
我给所有准备数仓岗笔试的同学都提过同一个建议:找一个业务场景,把整个数仓设计流程完整走一遍。最简单也最贴近笔试热点的场景,就是“用户订单分析”。
具体做法大概是这样的:先明确需求,比如运营要看每天不同渠道的付费金额、订单数、付费用户数和客单价;接着按业务过程确定粒度,设计DWD层的订单事实表,字段包括订单号、用户维度外键、渠道维度外键、时间维度外键、订单金额、实付金额、支付状态;再设计用户维度表、渠道维度表和时间维度表,把每个字段的类型、含义、主键都列清楚;最后写出两三条统计SQL,分别把“各渠道日收入”“各渠道付费用户数”“按用户维度汇总的月付费金额”算出来,并说明这些SQL应该挂在DWS层的哪张汇总表上。
这套练习做完,你会发现笔试设计题基本就是这套流程的简化版。更重要的是,你能理解为什么事实表要存度量值、维度表要处理缓慢变化维、分层要建在什么位置,这些是背概念背不出来的。
4.3 我复盘笔试时发现的三个典型错误
带过不少简历和面试,也批过大大小小的笔试记录,我发现准备数仓岗的人容易踩三个坑。
第一个坑是只写表结构不写粒度。很多人设计订单事实表,字段写得满满当当,但一问他这一行代表什么,支支吾吾说不清。笔试答题时,开头就把“每行代表一笔支付订单”写清楚,是体现建模基本功最简单的一步。
第二个坑是把业务属性一股脑塞进事实表。比如把优惠券ID、活动ID当作字段直接放事实表,看起来能查,但这其实是维度属性,应该通过维度表外键关联,而不是在事实表里存一堆业务代码。这样设计会导致后续维度更新时事实表跟着遭殃,维护成本极高。
第三个坑是只看重“写SQL”不看重“翻译需求”。实际上,入职后你每天要接到的需求大多是运营的一句话,比如“帮我看下最近活动哪个渠道拉新效果最好”。这句话翻译过来,是要先定义活动期、再定义拉新、再按渠道分组、再对转化和留存做对比。笔试里的业务分析题就是在模拟这种场景。前期把这种翻译能力练好,入职适应期会短很多,也更对面试官胃口。
4.4 真想进游戏公司数仓,还要补什么
如果目标就是搜狐畅游这类游戏公司,除了通用的数仓知识,我建议再重点补几个游戏行业特有的话题。
首先是游戏埋点。游戏客户端会上报海量事件,比如注册事件、创角事件、登录事件、关卡事件、新手引导完成事件、支付事件。这些事件数据怎么组织成事件表和用户行为明细表,是游戏数仓绕不开的设计点。你会看到很多游戏公司的事实表,不只有订单一种,还有大量行为事实,这是和电商数仓很不一样的地方。
其次是LTV和ROI。游戏公司对买量投放效果极其敏感,所以数仓要为渠道归因、LTV预估、ROI分析提供基础数据。了解用户生命周期价值这个指标的计算逻辑,以及渠道归因的基本方案,面试聊起业务场景会非常有优势。
最后是离线和实时的边界。校招笔试一般只考离线数仓,但游戏公司对实时指标的需求越来越强烈,比如实时DAU、实时流水、活动实时战报。提前了解Flink、Kafka的基本概念,以及实时数仓和离线数仓的架构差异,哪怕只是能说出分层思路,也会在面试中成为亮点。
最后说一点个人体会。当年我准备笔试的时候,也走过一段刷题刷到麻木的路,后来真正入职做数仓才发现,最好的复习方式不是把题库背完,而是把“一份订单数据从产生到变成运营报表”的整个过程亲手做一遍。你越理解业务方怎么用数据,就越知道维度表和事实表该怎么设计,越知道分层该怎么搭。希望这份复盘能帮你把复习重点从“背概念”转到“建模型”上,在笔试里写出让阅卷人眼前一亮的答案。