
只要是想走数据仓库方向的校招同学应该都绕不开“美丽联合”这套2018校招基础平台-数据仓库开发工程师笔试试卷。虽然现在回头看去这套题放在今天的大数据面试里难度不算天花板但它考察的点非常典型维度建模、事实表与维度表设计、数据分层、ETL流程、离线数仓工具链几乎覆盖了基础平台数仓岗日常工作的核心内容。我当年准备这套题的时候最大的感受是——它不是靠刷题能过的而是真的需要你把“用户订单分析”这个场景从业务到表结构完整地想一遍。这篇博文就是围绕这套试卷的考点方向把我自己复盘过程中整理的思路和实操经验完整写出来适合正在准备数据仓库校招的同学也适合刚入门数仓、想搞懂维度建模和分层设计的人。1. 校招数仓笔试题的命题逻辑基础平台岗到底想要什么人先说一个很多人容易误解的地方校招笔试不是招“熟练工”而是通过题目筛选出“有数据思维、懂建模套路、能落地工程化”的潜力股。尤其是基础平台这个方向它对应的不是某个具体业务线的BI报表组而是集团公共数据能力的建设者——比如公共维度层、公共明细层、数据治理规范、ETL调度框架这些东西都要求你具备“让别人能基于你的模型来做分析”的抽象能力。所以这套试卷的考点分布其实很有规律我自己归纳下来大概是这几个方向考察方向典型出题形式校招要求维度建模选择/简答判断星型模型与雪花模型理解事实表、维度表、粒度的概念用户订单分析建模大题设计核心维度表和事实表能独立画出表结构并说明主键与外键数仓分层简答ODS/DWD/DWS/ADS职责能说清分层目的和每层作用Hive/ETL实操题/调优题掌握Hive常用语法和常见数据倾斜场景数据质量简答或场景题了解校验、对账、血缘的基本思路再看“基础平台”这四个字它提示了一个题眼笔试不会让你设计某个具体App独有的埋点表而是让你设计“全公司都能复用”的公共模型。最典型的就是用户订单分析——这是电商数仓里最标准、最通用的场景用来考察维度建模几乎题题必中。因此这套试卷考察的核心能力可以浓缩成三句话第一能不能把业务过程拆成维度、事实和度量第二能不能用标准建模方法把这些要素组成可扩展的表结构第三能不能在工程层面分层、ETL、调优把它真正跑起来。接下来所有章节都是围绕这三句话展开的。2. 核心题型第一关维度表与事实表的区分必须形成本能笔试里最基础也最容易失分的就是对维度表和事实表的判断。给出一堆字段问你“哪些应该放在维度表哪些应该放在事实表”这种题看起来简单但每年都有人栽在“商品价格、优惠金额”这种字段上。判断标准其实很清晰事实表记录的是“业务过程产生的数值度量”通常可以加总比如订单金额、商品数量、优惠分摊金额。维度表记录的是“描述业务过程的上下文属性”通常是文本或枚举类型比如用户所在城市、商品类目、下单渠道。如果某个字段既像是描述又像是度量那就看它是否随订单快照变化。举例来说“订单原价”会随着商品价格变化而变但它是订单产生那一瞬间的度量结果应该进事实表“商品当前所属类目”则应该进商品维度表。针对用户订单分析这个场景最基础的划分方式是这样的维度表用户维度user用户唯一标识、注册时间、注册渠道、城市、年龄段、性别。商品维度product商品唯一标识、商品名称、所属SPU、品牌、一级类目、二级类目。店铺维度shop店铺唯一标识、店铺名称、店铺等级、主营类目。日期维度date日期、周几、是否节假日、所属月份、所属季度、所属年份。渠道维度channel渠道标识、渠道名称、渠道类型例如App、小程序、H5、线下。事实表订单事实表fact_order日期维度外键、用户维度外键、商品维度外键、店铺维度外键、渠道维度外键、订单号退化维度、商品数量、下单金额、优惠金额、实付金额、订单状态。笔试里如果给出的是选择题通常就问“以下哪个应放在事实表”或者“以下哪张表属于维度表”。只要把握住“能加总的度量→事实表描述上下文→维度表”基本不会错。如果是简答题还要补充一句话维度表提供“分组、筛选、透视”事实表提供“计算、汇总、度量”两者通过外键关联形成星型模型。从今天的招聘要求看这个知识点已经算是数仓岗的“常识题”了。但常识题不是送分题因为很多同学会把“维度退化”搞混。比如订单号它本质上是一张订单维度的主键但为了查询方便会在事实表里直接保留订单号字段这就是退化维度。笔试中如果问“订单号应该放在哪”回答“事实表中的退化维度”比单纯回答“事实表”显得更专业。3. 用户订单分析建模大题设计核心维度表与事实表的完整思路接下来进入整套试卷里最核心的题目“用户订单分析设计核心维度表和事实表”。这类题没有标准答案但阅卷人会明显倾向那些“先定粒度、再定表结构、最后说指标口径”的答案。我把自己当时的作答思路完整梳理一遍这套方法到现在我都还在用。3.1 第一步明确业务过程与事实粒度拿到这个题先不要急着写CREATE TABLE而是写清楚三行字业务过程用户下单支付完成事实粒度订单商品行粒度即每个订单中的每一件商品作为一行度量字段商品数量、下单金额、优惠金额、实付金额。为什么一定要先说粒度因为粒度决定事实表的行数含义和后续所有指标的计算口径。同样的订单表如果一行代表“一个订单”那要分析“一个订单里包含多少个商品”时就必须解析订单明细非常别扭如果一行代表“一个订单中的一件商品”分析订单数时需要做DISTINCT但分析商品销量时非常直接。校招笔试通常会让你自由设计所以从开始就把粒度定为“订单商品行粒度”既能兼顾订单级指标也能兼顾商品级指标是最稳妥的选择。3.2 第二步核心表结构的DDL设计以Hive环境为例我当时写出来的核心设计大概是下面这套维度表用户维度表CREATE TABLE dim_user ( user_key BIGINT COMMENT 用户代理键自增或Hash生成, user_id STRING COMMENT 业务用户ID, gender STRING COMMENT 性别, age_band STRING COMMENT 年龄段, city STRING COMMENT 城市, register_date STRING COMMENT 注册日期, register_channel STRING COMMENT 注册渠道, create_time TIMESTAMP COMMENT 记录创建时间, update_time TIMESTAMP COMMENT 记录更新时间, is_valid TINYINT COMMENT 是否当前有效版本 ) COMMENT 用户维度表 PARTITIONED BY (dt STRING) STORED AS ORC;维度表商品维度表CREATE TABLE dim_product ( product_key BIGINT COMMENT 商品代理键, product_id STRING COMMENT 商品业务ID, spu_id STRING COMMENT SPU ID, brand_name STRING COMMENT 品牌名称, category_1 STRING COMMENT 一级类目, category_2 STRING COMMENT 二级类目, category_3 STRING COMMENT 三级类目, shelf_status STRING COMMENT 上下架状态, create_time TIMESTAMP, update_time TIMESTAMP ) COMMENT 商品维度表 PARTITIONED BY (dt STRING) STORED AS ORC;事实表订单事实表CREATE TABLE fact_order ( order_id STRING COMMENT 订单ID退化维度, order_line_id STRING COMMENT 订单行ID在订单商品行粒度下作为唯一标识, user_key BIGINT COMMENT 用户维度外键, product_key BIGINT COMMENT 商品维度外键, shop_key BIGINT COMMENT 店铺维度外键, date_key BIGINT COMMENT 日期维度外键格式如20250220, channel_key BIGINT COMMENT 渠道维度外键, quantity BIGINT COMMENT 商品数量, order_amount DECIMAL(18,2) COMMENT 下单原价金额, discount_amount DECIMAL(18,2) COMMENT 优惠金额, pay_amount DECIMAL(18,2) COMMENT 实付金额, order_status STRING COMMENT 订单状态: PAID/REFUNDED/COMPLETED ) COMMENT 订单事实表 PARTITIONED BY (dt STRING) STORED AS ORC;这里有一个校招笔试时容易被忽略的点事实表的user_key、product_key、shop_key、date_key写的是“外键”而不是直接写user_id、product_id。表面上看起来两张表字段差不多但含义完全不同。维度表里的user_id是业务ID事实表里的user_key是维度代理键两者通过维度表里的映射关系关联。这样设计的好处在于即便用户ID因为业务系统迁移发生变化也不会影响历史事实。日期维度的date_key我用的是“20250220”这种整数格式没有直接用时间戳。原因很简单数仓日常分析最常见的就是按天、按月、按年聚合整数型日期键既能比较也能排序性能还比字符串好。校招阶段能写出这个细节会是一个加分项。3.3 第三步指标口径与常见SQL写法DDL写完之后试卷如果还有“统计每日GMV”这种分析题一定要把指标口径解释清楚。比如GMV到底是“下单金额之和”还是“支付金额之和”一般来说电商场景里GMV是下单金额口径包含未支付订单可按业务需求过滤而“实付金额”是支付成功口径。笔试卷如果没明确可以在答案里写“假设GMV口径为下单成功且未取消的订单金额总和”。对应的SQL可以这样写SELECT dt, COUNT(DISTINCT order_id) AS order_cnt, SUM(order_amount) AS gmv, SUM(pay_amount) AS real_pay_amt FROM fact_order WHERE order_status NOT IN (CANCELLED) GROUP BY dt;如果要统计“用户复购率”这类进阶指标就涉及到用户维度和订单维度的再次关联SELECT dt, SUM(CASE WHEN buy_cnt 2 THEN 1 ELSE 0 END) / COUNT(DISTINCT user_key) AS repurchase_rate FROM ( SELECT date_key AS dt, user_key, COUNT(DISTINCT order_id) AS buy_cnt FROM fact_order GROUP BY date_key, user_key ) t GROUP BY dt;这些SQL在笔试里的作用是证明“你的表设计是能落地分析的”。因为模型设计得再好如果写不出查询或者查询性能极差阅卷人会觉得你只有理论没有实践。4. 缓慢变化维与代理键为什么你的维度表设计容易被扣分用户订单分析设计题里十个人有九个人会写出用户维度表但真正能拿到高分的人寥寥无几。原因在于大部分人的维度表只是把“字段罗列”出来没有考虑维度自身的“变化机制”。笔试阅卷人最喜欢在这类细节上拉开差距。4.1 三种缓慢变化维策略缓慢变化维SCDSlowly Changing Dimension指的是维度属性随时间发生变化的情况。最经典的例子用户修改了收货地址、商品从三级类目调整到另一个类目、店铺等级升级了。在用户订单分析场景里最常见的问法是这样的“用户在2024年1月从上海搬家到北京那么2024年1月之前的历史订单在按城市统计时应该算上海还是北京”这个问题本质上就是在考SCD策略SCD类型1覆盖写直接更新旧值不保留历史。历史订单与用户当前状态绑定之前的订单也会被归到新城市。SCD类型2拉链表/新增行保留旧历史新增一条版本记录通过生效时间和失效时间控制版本区间。历史订单关联到当时的用户版本。SCD类型3新增列在维度表中增加“前值”字段只保留最近一次变化。适合只需了解上一次状态的场景。针对订单分析按城市统计历史GMV正确的回答是“用SCD类型2”因为用户的实际行为发生时点决定了这笔订单应该归属当时所在的城市。如果采用SCD类型1历史统计就会被篡改如果采用SCD类型3只能回溯最近一次变化前值无法恢复更早的状态。很多校招同学会担心SCD2太复杂笔试不敢写。其实笔试阶段不需要你把拉链表SQL写全只要能明确说出“用户维度用SCD2、商品类目调整用SCD2或SCD3、价格变动不放进维度表”这种策略组合就已经能拿到大部分分数了。4.2 代理键的价值代理键Surrogate Key是无业务含义的整数主键在维度表里推荐使用。为什么不用业务ID直接当主键因为业务ID可能重复使用、可能被业务系统修改、可能是带有含义的字符串。比如用户ID如果业务系统规定“重新注册的账号沿用原ID”那么历史事实关联到的用户就会错乱。代理键在事实表和维度表之间增加了一层缓冲业务系统再乱也影响不到数仓模型。我当时在笔试答案里写了这样一句话后来复盘时觉得非常关键“维度表采用代理键事实表只存代理键业务主键仅作为自然键存于维度表”。这句话清楚地体现了建模规范意识比堆一堆字段更让阅卷人认可。4.3 日期维度必须单独建表日期维度看起来可有可无但在笔试中单独建一张dim_date表很加分。因为订单分析里“周同比”“月环比”“是否节假日”这些分析场景都依赖日期属性。如果日期属性既不在订单事实表里、也没有日期维度表后续的统计就无法统一口径。日期维表设计不用太复杂至少包含以下字段字段名示例说明date_key20250220整数日期主键date_str2025-02-20标准日期字符串year2025年份month2月份day20日week8周序号is_weekend1是否周末is_holiday0是否节假日日期维表可以通过SQL或脚本一次性预生成5到10年的数据笔试只要能画出字段表就足够了。5. 数据分层与ETL设计笔试简答题千万别只背概念数仓分层是每套数仓笔试试卷里必定出现的简答题主题美丽联合这套也不例外。常考的问题包括“ODS、DWD、DWS、ADS各层的作用”“为什么要做数据分层”“ETL过程中如何保证数据质量”。这类题的陷阱在于如果只写出各层的缩写含义分数一定不高。阅卷人想看到的是“为什么”。5.1 为什么要分层数仓分层的核心目的有三个。第一清晰的数据流向。从数据接入到数据应用每一层只做特定的事情出现问题能快速定位到某一层而不是在一堆无序SQL里排查。第二公共逻辑下沉避免重复计算。同一个指标如果写十遍很容易出现口径不一致例如A同事的GMV包含取消订单B同事的不包含。通过公共DWS层把口径固定下来下游所有应用都引用同一份结果。第三权限与安全控制。ADS层可以面向业务人员展示脱敏数据ODS层只有少数开发人员能访问。分层天然提供了一种数据治理边界。5.2 各层职责划分ODS层Operational Data Store操作数据存储层负责从业务库同步原始数据结构与业务库保持一致不做清洗。这一层典型操作是每日全量或增量抽取。DWD层Data Warehouse Detail明细数据层负责清洗、标准化、维度退化、脱敏。例如把业务库里的杂乱编码统一成标准字典把多张业务表join成明细宽表。DWS层Data Warehouse Summary汇总数据层按主题进行轻度汇总。例如按“用户天”汇总订单数、GMV、客单价。ADS层Application Data Store应用数据层面向具体报表和业务分析一般为高度汇总的表。在校招笔试里最容易混的是DWD和DWS。我的理解口诀是DWD保持明细行数跟业务明细一致DWS做汇总行数会大幅减少。如果一个统计口径可以在DWS计算就不要在DWD阶段先汇总否则会丢失明细灵活性。5.3 ETL设计与校验机制ETL相关题目通常不会只问流程而是结合“数据质量”来问。比如“每天凌晨同步订单表你怎么保证同步的数据没有丢失或重复”完整回答要分三个环节来说抽取Extract根据业务表更新时间字段做增量抽取同步任务记录每次抽取的最大时间戳作为下次抽取的起点。转换Transform清洗空值、统一单位、格式化时间字段、过滤掉明显异常数据例如数量为负、金额超过阈值。加载Load写入目标分区表注意分区覆盖策略避免重复加载同一个分区数据。校验机制才是加分点。同步完成后至少要做几个校验行数校验源表当日行数与目标表该分区行数对比。主键校验通过对关键ID做count distinct检查是否有重复。金额对账ODS层原始订单金额总和与DWD层明细金额总和对比。空值校验非空字段的空值数量是否为0。这些校验逻辑不一定会在试卷中让你全部写出但能在简答题里提到两个以上会明显体现出工程意识是能从“学生思维”切换到“工程思维”的信号。6. Hive性能优化与数据倾斜整套试卷真正拉开差距的实操题如果维度建模是数仓笔试的“基础分”那Hive调优和数据倾斜就是“拔高分”。美丽联合这套题里即使没有直接出大题的Hive优化面试环节也一定会追问。所以准备笔试时这部分必须一块儿准备。数据倾斜的表现非常典型MapReduce任务卡在99%某个reduce task运行时间远超其他task。它的本质是“输入数据分布不均”少数key对应了海量数据导致这种key所在reduce端负载过高。在校招笔试里常考三种场景6.1 大表Join小表时的空值倾斜订单表大表的user_id有空值或异常值时按user_id关联用户维度表空值会全部集中到一个reduce task。解决方案是给空值或异常值加随机前缀打散到多个reduceSELECT COALESCE(u.user_key, -1) AS user_key, COUNT(1) AS cnt FROM ( SELECT order_id, CASE WHEN user_id IS NULL OR user_id THEN CONCAT(unknown_, FLOOR(RAND() * 1000)) ELSE user_id END AS user_id FROM fact_order ) o LEFT JOIN dim_user u ON o.user_id u.user_id GROUP BY COALESCE(u.user_key, -1);这里有一个细节加随机前缀后空值user_id被拆分成1000个不同key但最后聚合时用COALESCE(u.user_key, -1)把空值映射回统一的-1保证统计结果不会分散。6.2 热点key倾斜某些商品在活动期间订单量极高例如一款爆品占了当天订单的20%按商品维度聚合时该商品的reduce task会明显比其他慢。常见的处理策略是“先两阶段聚合”-- 第一阶段加随机前缀打散 SELECT product_id, sum(cnt) AS sales_cnt FROM ( SELECT product_id, CONCAT(product_id, _, FLOOR(RAND() * 10)) AS shuffled_key, COUNT(1) AS cnt FROM fact_order GROUP BY product_id, CONCAT(product_id, _, FLOOR(RAND() * 10)) ) t GROUP BY product_id;这种方法在高考点key时效果非常明显。对于SQL比较复杂的情况也可以直接使用Hive 3.x的skew join hint在支持版本里设置SET hive.optimize.skewjointrue; SET hive.skewjoin.key100000;6.3 Count Distinct导致的数据倾斜统计用户数时有人直接写COUNT(DISTINCT user_id)。当数据量极大时这个操作在reduce阶段会非常吃力因为它实际上会触发全局去重。优化思路是先用子查询去重再进行统计SELECT COUNT(1) AS user_cnt FROM ( SELECT user_id FROM fact_order GROUP BY user_id ) t;用GROUP BY替代COUNT DISTINCT是校招里性价比最高的优化回答。6.4 小文件问题与常用参数除了数据倾斜Hive的另一类笔试高频题是小文件问题。输入数据由大量小文件组成时NameNode元数据压力大、任务启动和调度开销高查询性能极差。常规解决方案是设置任务级合并SET hive.merge.mapfilestrue; SET hive.merge.mapredfilestrue; SET hive.merge.size.per.task256000000; SET hive.merge.smallfiles.avgsize16000000;同时建议控制动态分区数量避免“一个分区几十个小文件”的局面。这些小技巧在笔试中出现代表你真的“跑过数仓任务”不只是看过文档。7. 笔试答题策略复盘我见过最可惜的几种失分方式写完核心知识点之后再聊一下应试层面的经验。这套试卷难度不算离谱但每年通过率并不高主要原因不全是知识储备问题而是踩了一些答题策略上的坑。7.1 一定要先写“粒度”再写“表结构”在校招笔试里见过太多人直接甩出一张fact_order表字段写得规规矩矩但没有一句话说明“每一行代表什么”。这个缺失非常致命因为事实表的粒度决定了所有后续字段是否合理。阅卷人一旦无法判断你的表粒度就会怀疑你的表设计是否逻辑自洽。所以无论是大题还是小题只要出现“设计表结构”四个字第一步永远是写清楚“每行记录描述到什么层级”。常见写法是订单事实表一行代表一个订单中的一个商品行用户维度表一行代表一个用户的一个版本商品维度表一行代表一个SPU的一个SKU。7.2 答概念题时先“定义用途例子”不要只写一句话比如“什么是星型模型”很多同学只写“由一张事实表和若干维度表组成”。这句话在笔试里只能得一半分。更完整的回答是星型模型是以事实表为中心通过外键关联多张维度表的建模方式查询时从事实表出发通过维度表进行筛选和分组。它的优点是结构简单、理解成本低适合OLAP分析。用户订单分析场景中fact_order关联dim_user、dim_product、dim_shop就是一个典型的星型模型。核心技巧是即使题目只问“是什么”也要补一句“为什么要用它”。7.3 开放性“设计题”不要追求唯一正确答案用户订单分析这类设计题没有标准JSON式答案阅卷人看的是你的分析框架。我推荐按“业务过程 → 粒度 → 维度 → 度量 → 表结构 → 查询示例”这个顺序来写。就算中间某张表字段写得不够完善只要框架完整整体评价就不会差。7.4 尽量体现“数据质量意识”很多笔试简答题的最后一小问会问“如何保证数据质量”即使没问也可以在表设计阶段加上“每天同步后需要做源表行数与目标表分区行数比对”这类型的话。这个细节不占篇幅却能从一堆只写SELECT语句的答案里跳出来。8. 准备这套试卷时我更推荐你用项目验证而不是纯刷题如果你现在还在准备阶段我比较推荐的做法是别只停留在“看题—背答案”。找一份真实的订单样例数据自己写脚本生成一套用户、商品、订单数据然后按照上面这套建模思路从ODS到DWD再到DWS完整跑一遍。我在实际准备过程中就是这么做的。只用了几万条模拟订单数据但当我真正把dim_user、dim_product、fact_order这几张表建出来、再把每日GMV统计跑通时维度建模里那些“为什么要用代理键”“为什么要把粒度定成订单商品行”“为什么SCD2更合理”的问题一下就通了。笔试考完之后这套表结构直接被我迁移到了后面的小项目里后面做用户复购率分析、商品销量排行、渠道转化漏斗时基本不需要改表结构。这说明一个建得好的核心模型具备很强的扩展性而这种扩展性正是基础平台数仓岗最看重的东西。如果你能把这一步亲自跑完再回头做这套试卷我相信你的答题重心会完全不一样——不是在回忆概念而是在用自己踩过的坑组织答案。