老王开了一家小饭馆,生意红火,每天要记上百张订单。他原先只用一本流水账,把菜名、数量、价格、服务员、日期通通混在一起写。可每当他想知道“上个月哪道菜卖得最好”“工作日和周末的客单价差多少”时,翻账本翻得头晕眼花。后来他换了一套记账法:柜台只放一本“交易流水”,每行记一件事;旁边立起几本固定的参考书——菜品档案、员工名册、日历页、门店信息。要查什么,先看流水里的编号,再去参考书里找答案。这套“一本流水 + 几本参考书”的记账体系,正是数据仓库中星型 Schema 的雏形。本文就用老王的饭馆贯穿全程,带你从零理解星型 Schema 的每一块砖。

岗位关联

星型 Schema 是数据仓库面试的必考考点,也是数据工程师建模的第一张图纸。理解它,你就能看懂企业数仓中 70% 以上的核心模型,并在技术选型时做出有依据的决策。

目录
  1. 可视化演示
  2. 一、星型结构
  3. 二、事实表设计
  4. 三、维度表设计
  5. 四、星型对雪花
  6. 五、维度退化
  7. 六、缓慢变化维
  8. 七、聚合导航层
  9. 八、建模决策
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

\u5c55\u793a\u661f\u578bSchema\u7684\u4e2d\u592e\u4e8b\u5b9e\u8868\u4e0e\u5468\u8fb9\u7ef4\u5ea6\u8868\u7ed3\u6784\u3001\u4e8b\u5b9e\u8868\u7c92\u5ea6\u4e0e\u5ea6\u91cf\u5c42\u6b21\u3001\u4ee5\u53ca\u661f\u578b\u4e0e\u96ea\u82b1\u578b\u7ed3\u6784\u5bf9\u6bd4\u3002

本节配有交互动效(进入视口后自动加载,约 3KB)。

\u661f\u578bSchema\u7ed3\u6784 \u4e2d\u592e\u4e8b\u5b9e\u8868+\u5468\u8fb9\u7ef4\u5ea6\u8868 4\u4e2a\u7ef4\u5ea6: \u65f6\u95f4/\u4ea7\u54c1/\u5ba2\u6237/\u5730\u7406 Fact = FK + Measures \u4e8b\u5b9e\u8868\u901a\u8fc7\u5916\u952e\u8fde\u63a5\u7ef4\u5ea6\u8868\u5f62\u6210\u661f\u578b\u7ed3\u6784

一、星型结构

老王重新设计了记账系统。柜台只放一本“交易流水”,每一行记一条订单明细:日期编号、菜品编号、数量、金额。旁边立起几本固定的参考书——菜品档案本、日历本、员工名册、门店信息卡。要查什么,先看流水里的编号,再去参考书里翻答案。这套布局从上往下看,仿若一颗星星:流水账是星心,参考书是星芒。

在数据仓库里,“交易流水”就是事实表(Fact Table),它记录“发生了什么”,每一行是一个业务事件,以数值度量为中心;“参考书”就是维度表(Dimension Table),描述“事件发生在什么场景”,是筛选与分组的依据。事实表通过外键(Foreign Key)指向维度表主键,形成一张以事实为中心向外辐射的关联网络。

星型 Schema 的典型特征就是“一中心、多辐射”:一张大而增长快的事实表,加上若干小而稳定的维度表。OLAP 查询往往从事实表出发,按需 JOIN 一张或几张维度表做过滤与聚合。因为路径短、结构固定,它对“按维度切分统计”这类分析极其友好。

老王把新账本挂在墙上让伙计照填,几天后发现:任何“这周哪道菜最赚”“周末哪家店最忙”的问题,翻几本册子就能回答。一个稳定的星,正是混沌业务中那点可复用的秩序。

Fact  ⋈  DimTime  ⋈  DimDish  ⋈  DimStore\displaystyle \text{Fact} \;\bowtie\; \text{Dim}_{\text{Time}} \;\bowtie\; \text{Dim}_{\text{Dish}} \;\bowtie\; \text{Dim}_{\text{Store}}
这个式子描述“事实表依次与时间、菜品、门店维度表做等值连接”,是星型查询的标准动作。
  1. Fact(事实表)代表老王的交易流水本,是全查询的起点。
    Fact\text{Fact}
  2. $\bowtie$(自然连接)外键与主键对齐时产生的关联运算。
    Fact⋈DimTime\text{Fact} \bowtie \text{Dim}_{\text{Time}}
  3. Dim(维度表)墙角里的参考书,角标指明具体是哪一本。
    DimDish\text{Dim}_{\text{Dish}}
  4. 链式连接事实表与多张维度表依次连接,形成放射状的星形结构。
    Fact⋈Dim1⋈⋯⋈Dimn\text{Fact} \bowtie \text{Dim}_1 \bowtie \cdots \bowtie \text{Dim}_n

二、事实表设计

流水的每一行写的是“某日、某桌、某道菜、某服务员、数量、金额”。这些数字是老王经营决策的尺子:今天卖了多少?本月流水涨了没?流水账的核心,就是这一串串可累加的数字。

在星型模型中,事实表就是“事件 + 数字”的集合。它包含两类列:外键列(指向各维度)和度量列(数值指标)。度量按可加性分为三类:可加指标在所有维度下都能求和;半可加指标只能在部分维度求和,例如余额跨时间不可累加;不可加指标如单价、比率,不能直接求和。

设计事实表的第一步是定粒度(Grain):表里一行到底代表什么?一行是一道菜、一桌账单、还是一天汇总?粒度越细,能回答的问题越多,但行数越大、ETL 越重。粒度是事实表的天花板,任何指标必须先明确它挂在哪个粒度上才有意义。

老王发现,只要记到“一道菜一行”,他就能回答任何角度的汇总;若只记“一桌一行”,就再也问不出“宫保鸡丁卖了多少”。事实表设计的本质,就是先确定一行是什么。

Sales=∑i=1N(qi×pi)\displaystyle \text{Sales} = \sum_{i=1}^{N} \left( q_i \times p_i \right)
这是营业额的计算式:把流水账每一行的数量与单价相乘,再逐行累加。可加指标的特质使它能在任意维度下被安全求和。
  1. Sales(总销售额)全部明细行加总后的结果,是报表上的“总计”。
    Sales\text{Sales}
  2. $\sum_{i=1}^{N}$(循环累加)对流水账中的 N 行逐行做加法。
    ∑i=1Nxi\sum_{i=1}^{N} x_i
  3. $q_i \times p_i$(第 i 行金额)第 i 道菜的数量乘单价,得到这一行的贡献。
    qi×piq_i \times p_i

三、维度表设计

菜品档案本不只记菜名,还记分类、辣度、食材、定价;日历本记日期、星期、是否节日、是否促销。这些描述属性就像参考书里的目录和批注,让流水账里的编号变得可读、可筛、可分组。

维度表承载分析查询的“上下文”。每条维度记录包括:代理键(Surrogate Key)——与业务无关的自增主键;业务键(Business Key)——源系统里的原始编码;以及若干描述属性(Attribute)。代理键让维度表与业务系统解耦,源系统键的复用与改名都不会破坏历史关联。

维度表还会形成层次结构,如“菜品 → 类别 → 菜系”,支撑上卷(Roll-up)与下钻(Drill-down)分析。维度粒度一般指一层一条记录,如一种菜品一行;若同一维度存在多种粒度,则应拆成多张维度表,避免一张表里混两种层次。

参考书要勤维护,伙计查菜名才不翻错页。维度表虽小,却决定了分析的视野——它回答“从哪个角度去看那笔交易”。

Dim=(SK,  BizKey,  Attr1,  Attr2,  … )\displaystyle \text{Dim} = \left( \text{SK},\; \text{BizKey},\; \text{Attr}_1,\; \text{Attr}_2,\; \dots \right)
维度表的结构是一个代理键打头、业务键兜底、一组描述属性跟随的宽行记录。
  1. SK(代理键)系统自动生成的自增整数,与业务无关。
    SK=1001\text{SK} = 1001
  2. BizKey(业务键)源系统遗留的原始编号,可能出现复用或变更。
    BizKey≠SK\text{BizKey} \neq \text{SK}
  3. Attr(描述属性)供筛选和分组的文本列。
    Attr=(菜名,分类,辣度)\text{Attr} = (\text{菜名}, \text{分类}, \text{辣度})

四、星型对雪花

有人建议老王把菜品册拆成两本:分类册只记“川菜、家常菜”,菜品册只记“菜名、辣度、价格”,两本用编号关联。这样“川菜”两个字只写一遍,省纸,但每次查一道菜得多翻一本册子。老王犹豫了。

这对应数据建模里的规范化和反规范化。星型模型把维度表做成宽表、不做拆分,查询时只需一次 JOIN,路径短、语义直观;雪花模型把维度表按第三范式拆分,消除了冗余,维度层级变深,查询需要多层 JOIN,模型复杂度上升。

雪花型更适用于存储昂贵或维护极为严格的环境;而对以查询为主的 OLAP 系统,星型凭借更少的 JOIN、更稳的查询计划成为绝对主流。数据仓库领域有条经验:能用星型就不用雪花,除非有明确且充分的理由。

老王最终没有拆册子:纸很便宜,翻两本书的时间却贵。星型用一点点冗余,换来了“一翻即得”的速度——这正是它在数据仓库里胜出的原因。

R  ⟶  R1+R2,R=R1⋈R2\displaystyle R \;\longrightarrow\; R_1 + R_2, \qquad R = R_1 \bowtie R_2
雪花型把一个宽维度表拆成两张窄表,想还原完整信息必须再做一次连接,这就是它的“额外成本”。
  1. R(原宽维度表)一张表全包全部描述属性的初始形态。
    R=(name,category,spicy,price)R = (\text{name}, \text{category}, \text{spicy}, \text{price})
  2. 拆分($R_1 + R_2$)把类别属性抽成独立小表,消除冗余。
    R1=(name,spicy,price),R2=(category,name)R_1 = (\text{name}, \text{spicy}, \text{price}), \quad R_2 = (\text{category}, \text{name})
  3. 无损还原只有通过连接才能恢复完整信息,这就是雪花的代价。
    R=R1⋈R2R = R_1 \bowtie R_2

五、维度退化

每张订单都有一个订单号,印在票根上。如果为订单号单独开一本“订单册”,它每天得新增几百页,而每一页只有孤零零一个编号,几乎没有描述信息。老王觉得这纯属浪费。

这类维度只有一个标识属性、没有其他描述内容,单独建模反而制造大量“瘦维度表”。业界将其称为退化维度(Degenerate Dimension):它不建立独立的维度表,而是作为一列直接留在事实表中。

典型场景是订单号、发票号、交易流水号等凭证编号。它们常用于分组(如按订单汇总)、关联外部系统或去重,但不需要维度表的描述属性。退化维度保留了业务可追溯性,同时避免了无意义的维度表膨胀,是星型设计中的常见优化。

老王把订单号留在流水账的票根上,省下了一本越来越厚的空册子。退化维度的智慧,就是识别“哪个属性配不上一张表”。

Fact=(ord_no,  SKdish,  SKtime,  qty,  amt)\displaystyle \text{Fact} = \left( \text{ord\_no},\; \text{SK}_{\text{dish}},\; \text{SK}_{\text{time}},\; \text{qty},\; \text{amt} \right)
订单号以普通列的身份出现在事实表中,不指向任何维度表,这就是“退化”的含义。
  1. ord_no(订单号)退化维度列,直接留在事实表里当凭证。
    ord_no=’A001’\text{ord\_no} = \text{'A001'}
  2. 外键列其余维度通过代理键与维度表关联。
    SKdish⇒DimDish\text{SK}_{\text{dish}} \Rightarrow \text{Dim}_{\text{Dish}}
  3. 度量列数量与金额是流水账的核心数字。
    amt=qty×price\text{amt} = \text{qty} \times \text{price}

六、缓慢变化维

六月,宫保鸡丁从 32 元涨到 38 元。老王面临一道难题:历史订单按什么价算?直接改档案本,三月的账就失真了;保留旧页,新订单又该看哪一页?这正是缓慢变化维(Slowly Changing Dimension)问题。

SCD 有几种经典策略。类型 1:直接覆盖旧值,简单但不留历史;类型 2:新增一行记录新值,旧行保留并标记有效起止时间,能完整还原“当时的价格”;类型 3:在旧行上增加一列保存“上一版本值”,只能追溯一步。

数据仓库中类型 2 使用最广:每次属性变化就插入新行,并维护 valid_from、valid_to 时间区间和当前标记。查询历史事实时,用业务时间落在哪个区间去关联对应维度行,就能得到当时真实的状态。类型 1 适合无需追溯的修正性属性,类型 3 适合“只关心上一版”的场景。

老王选了“保留旧页、插入新页”,并在页眉标注生效日期。几个月后回头看三月的账,每一笔都能对上当时的价签——这就是缓慢变化维的全部意义。

(SK=1001,  price=32,  [t0,t1))  ⇒  (SK=1002,  price=38,  [t1,MAX))\displaystyle \left( \text{SK}=1001,\; \text{price}=32,\; [t_0, t_1) \right) \;\Rightarrow\; \left( \text{SK}=1002,\; \text{price}=38,\; [t_1, \text{MAX}) \right)
类型 2 的核心理念:旧价留旧行,新价开新行,各守一个半开区间,互不干扰。
  1. 旧行(SK=1001)记录 32 元,有效期从 \(t_0\) 到 \(t_1\)(不含)。
    [t0,t1)[t_0, t_1)
  2. 新行(SK=1002)记录 38 元,自 \(t_1\) 起一直有效。
    [t1,MAX)[t_1, \text{MAX})
  3. 按时间取价查询某日期的价格,看该日期落在哪个区间即可。
    price(t)={32t∈[t0,t1)38t∈[t1,MAX)\text{price}(t) = \begin{cases} 32 & t \in [t_0, t_1) \\ 38 & t \in [t_1, \text{MAX}) \end{cases}

七、聚合导航层

每周一,老王都要汇报“上月各菜销量、各店流水”。每次从几万张流水单里逐行相加,等他算完,周会都要开始了。于是他想:能不能平时就把汇总先算好?

这就是聚合导航层(Aggregation Navigation)的朴素来源:在明细事实表之上构建预聚合表,如按天、按月、按菜品汇总。当用户查询的粒度恰好命中或能由更高层聚合推出时,OLAP 引擎会自动导航到最合适的聚合表,避免扫描全部明细。

聚合导航的代价是存储与构建成本:层数越多、覆盖率越高,占用空间与维护时间越大。常见做法是只对高频查询模式做一到两层预聚合,并设置自动重写规则;无法命中的查询再回到明细。聚合层与明细表是“缓存与源头”的关系,不是替代关系。

老王那本周报小本,就是数据仓库里的聚合导航层。它让“又快又全”不再矛盾——日常看周报,想刨根问底时再翻回流水账。

agg=Fact  ↦  GROUP BY (month,category)  SUM(amt)\displaystyle \text{agg} = \text{Fact} \;\mapsto\; \text{GROUP BY} \, (\text{month}, \text{category}) \; \text{SUM}(\text{amt})
这表示预聚合表是由明细事实表按“月份 + 类别”分组求和得到的。
  1. GROUP BY(分组)把流水按“月份 + 类别”分堆。
    GROUP BY (month,category)\text{GROUP BY} \, (\text{month}, \text{category})
  2. SUM(组内求和)对每一堆的金额求和,得到一行汇总。
    SUM(amt)\text{SUM}(\text{amt})
  3. agg(聚合结果)预聚合表里的一行,查询直接命中它就不用扫明细。
    agg=(month,category,∑amt)\text{agg} = (\text{month}, \text{category}, \textstyle\sum \text{amt})

八、建模决策

一个季度下来,老王总结了自己的建模口诀:先想清楚流水账的一行是什么,再定记哪些数字、配哪些参考书、要不要准备周报本。他说,账本设计错了,改起来比换厨师还麻烦。

这也正是星型建模的决策顺序:第一步定粒度(Grain),第二步定度量(Measure)并确认可加性,第三步定维度(Dimension)与分析层次,第四步再按查询热度设计聚合层。粒度像定海神针,它决定了行数规模、度量口径和维度集合。

所有选择都围绕“业务问题”展开:粒度要细到能回答最细节的问题,又不至于细到成本失控;维度要覆盖常见筛选与分组;聚合层只服务于高频查询。在模型清晰与查询性能之间,每一次取舍都是对业务价值的重新排序。

老王的饭馆账本虽简单,其决策过程与企业级数仓同构。掌握星型 Schema,就握住了“从业务问题到数据模型”的翻译器——这是数据建模最核心的基本功。

grain  ⟶  measures  ⟶  dims  ⟶  aggs\displaystyle \text{grain} \;\longrightarrow\; \text{measures} \;\longrightarrow\; \text{dims} \;\longrightarrow\; \text{aggs}
建模链路是单向的:先定粒度,才能定度量、选维度、建聚合,顺序不能颠倒。
  1. grain(粒度)事实表每行代表的最细事件,是后续一切的地基。
    grain=event per row\text{grain} = \text{event per row}
  2. measures(度量)粒度确定后,才能判断哪些指标可加、该存什么。
    measures⊥grain\text{measures} \perp \text{grain}
  3. dims(维度)决定分析上下文,回答“按什么角度看问题”。
    dims=context of grain\text{dims} = \text{context of grain}
  4. aggs(聚合)最后用聚合层为高频查询加速。
    aggs=f(group by,sum)\text{aggs} = f(\text{group by}, \text{sum})

AI 应用场景

电商订单分析大屏

阶段:数据准备与分析特征构建

解决的问题:传统做法是在几十张业务表间临时关联,SQL 冗长、口径混乱。星型建模后,订单事实表统一度量口径,商品、用户、时间维度表提供筛选上下文,分析团队取数从小时级降到分钟级。

具体案例:某电商平台搭建“订单主题”星型模型,BI 团队基于它产出 GMV、客单价、品类渗透率等指标,大屏与周报共用同一套口径,指标不再互相打架。

用户增长漏斗的特征底座

阶段:特征工程与特征平台

解决的问题:漏斗分析需要按渠道、设备、城市等维度切分事件序列。行为事实表 + 用户、渠道维度表构成星型,使“拉新 → 激活 → 留存 → 转化”每一步的用户数都能以统一粒度抽样与聚合。

具体案例:某增长团队把埋点行为放进事件事实表,用户属性放进维度表,自动生成漏斗特征供机器学习模型训练,特征更新延迟从 T+1 降到小时级。

实时风控的聚合特征

阶段:在线推理阶段的特征获取

解决的问题:风控模型需要“近 1 小时该商户交易笔数、金额”等统计特征,直接查明细太慢。星型模型下的预聚合层按商户 + 小时预计算,推理时一次命中聚合表,响应时间大幅缩短。

具体案例:支付风控系统的特征服务基于订单事实表构建小时级聚合表,查询 P99 从数百毫秒降到十毫秒以内,模型决策几乎实时完成。

Python 演示

python42 行
import pandas as pd # dimension table: dish catalog (reference book)dish_dim = pd.DataFrame({    'dish_sk': [1, 2, 3, 4],    'dish_name': ['宫保鸡丁', '麻婆豆腐', '水煮鱼', '酸辣土豆丝'],    'category': ['川菜', '川菜', '川菜', '家常菜'],    'price': [32, 18, 58, 12]}) # dimension table: date calendar (calendar book)date_dim = pd.DataFrame({    'date_sk': [1, 2, 3],    'date_val': ['2025-01-01', '2025-01-02', '2025-01-03'],    'month': ['2025-01', '2025-01', '2025-01']}) # fact table: order line items (transaction ledger)fact = pd.DataFrame({    'order_no': ['A001', 'A001', 'A002', 'A003', 'A003', 'A003'],    'dish_sk': [1, 3, 2, 1, 2, 4],    'date_sk': [1, 1, 1, 2, 2, 3],    'qty': [2, 1, 1, 3, 2, 2],    'amount': [64, 58, 18, 96, 36, 24]}) # star join: fact joins dimension tables via foreign keysstar = fact.merge(dish_dim, on='dish_sk').merge(date_dim, on='date_sk')print(star[['order_no', 'dish_name', 'category', 'qty', 'amount', 'month']]) # aggregation navigation: pre-aggregate by date and categoryreport = star.groupby(['date_val', 'category'])['amount'].sum().reset_index()print(report) # additive check: sum over any dimension equals grand totalassert report['amount'].sum() == fact['amount'].sum()print('additivity check passed') # degenerate dimension: order_no stays directly in fact tabledistinct_orders = fact['order_no'].nunique()print('rows:', len(fact), 'distinct orders:', distinct_orders)assert distinct_orders < len(fact)
目的:用 pandas 搭建一个迷你星型模型,演示事实表与维度表的外键连接、预聚合查询以及可加性验证。
  1. fact.merge(dish_dim, on='dish_sk')事实表按外键连接菜品维度表,是星型关联的核心动作
  2. .merge(date_dim, on='date_sk')链式连接第二张维度表,体现“一中心、多辐射”
  3. star.groupby(['date_val', 'category'])['amount'].sum()按日期 + 类别聚合,模拟聚合导航层的预聚合
  4. assert report['amount'].sum() == fact['amount'].sum()可加性验证:任意维度求和都等于总额
  5. fact['order_no'].nunique() < len(fact)订单号有重复,证明它作为退化维度留在事实表更合理

常见误区

误区“事实表不就是放数字的吗?订单号、备注这些文本不该出现,应该全部放进维度表里。”

正解事实表以度量为主,但可以保留少量维度属性。订单号在事实表中一行一值、几乎没有重复,单独建维度表纯属浪费——这正是退化维度的场景。只要这些属性不参与“多对一”建模,留在事实表反而高效。

为何常见:初学者从“度量 / 维度”的二分法出发,容易把规则绝对化。

误区“数据库范式要求消除冗余,雪花型把维度拆得干干净净,不是更好的设计吗?”

正解数据仓库面向查询而非事务。星型用反规范化减少 JOIN 层级,OLAP 查询更快;雪花型省了存储却多了 JOIN,通常只在存储极度紧张或维护约束强时才用。建模目标是“查询快、易理解”,不是“范式高”。

为何常见:把 OLTP 的三范式经验错误迁移到 OLAP 场景。

误区“把事实表记到'一行一个事件'总是对的,越细越灵活,成本高点无所谓。”

正解粒度必须匹配业务问题。粒度过细意味着海量行数与高昂的 ETL 成本,而很多分析只需要日粒度甚至月粒度。先问“要回答什么问题”,再定粒度,避免为用不上的灵活性买单。

为何常见:“灵活性第一”听起来正确,却忽略了存储与查询的代价。

误区“价格变化时往维度表里插一条新行,历史数据不就有了吗?”

正解类型 2 需要完整机制:有效起止时间列、当前标记(或 MAX 时间戳),以及查询时按时间点选行的逻辑。如果只插行、不标有效期,聚合时无法判断该用哪一行,反而引入脏数据。

为何常见:只看到“多一行”的表层,忽略了时间旅程的语义。

误区“预聚合表回答大多数问题都很快,把明细事实表删掉能省一大半存储。”

正解聚合表只能回答已定义好的维度组合与指标。一旦新问题的粒度未被预聚合,就必须回明细表重算。明细事实表是原子真相,聚合层只是可丢弃的加速缓存,二者互补而非替代。

为何常见:把“加速缓存”误当成“数据副本”,不理解导航是动态选择的过程。

面试题

Q1. 星型 Schema 中,位于中心位置的是?

单选题 · easy

  • A.事实表← 正确答案
  • B.维度表
  • C.聚合表
  • D.字典表

答案:A。星型模型以事实表为圆心、维度表为辐射点,所有维度都通过外键指向事实表,因此事实表必须居中,选项 A 正确。

Q2. 销售额在任意维度下都能直接求和,属于哪类指标?

单选题 · easy

  • A.可加指标← 正确答案
  • B.半可加指标
  • C.不可加指标
  • D.衍生指标

答案:A。可加指标在所有维度上都能安全求和,销售额就是典型;半可加指标如余额跨时间不可累加,不可加指标如单价不能直接相加。

Q3. 订单号直接留在事实表、不单独建维度表,这种设计称为?

单选题 · easy

  • A.维度退化← 正确答案
  • B.度量冗余
  • C.外键下沉
  • D.缓慢变化

答案:A。订单号只有一个标识属性、没有其他描述内容,单独建表只会制造瘦维度表,因此作为退化维度留在事实表中,既保留可追溯性又避免膨胀。

Q4. 星型模型与雪花模型最本质的区别是?

单选题 · mid

  • A.维度表规范化程度不同← 正确答案
  • B.事实表行数规模不同
  • C.主键类型使用不同
  • D.底层存储介质不同

答案:A。星型维度表反规范化、宽而扁平;雪花型把维度拆到第三范式。区别核心是规范化程度,而非表行数、主键或存储介质。

Q5. 需要完整保留菜品价格的历史,最合适的 SCD 方式是?

单选题 · mid

  • A.类型 1 直接覆盖旧值
  • B.类型 2 新增行留历史← 正确答案
  • C.类型 3 增加旧值一列
  • D.类型 0 完全不做处理

答案:B。类型 1 覆盖旧值会丢失历史;类型 3 只能回溯一步;类型 2 通过新增行 + 有效起止时间记录完整变化史,是追溯历史价格的首选。

Q6. 设计事实表时,最先要确定的是?

单选题 · mid

  • A.事实表粒度← 正确答案
  • B.字段数据类型
  • C.表的压缩格式
  • D.索引的数量

答案:A。粒度决定一行代表什么,进而决定度量口径与维度集合,是建模的定海神针;字段类型、压缩、索引都是粒度确定之后的执行细节。

Q7. 下列指标中属于半可加指标的有?

多选题 · mid

  • A.账户实时余额← 正确答案
  • B.商品库存数量← 正确答案
  • C.订单实付金额
  • D.用户注册年龄

答案:AB。余额与库存跨时间不可累加,但跨账户、跨门店可求和,属于半可加;订单金额是典型可加指标;年龄是描述属性,不参与直接求和。

Q8. 关于维度表代理键,说法正确的有?

多选题 · hard

  • A.与业务系统完全解耦← 正确答案
  • B.防止业务键变更影响历史← 正确答案
  • C.必须沿用源系统主键
  • D.通常为自增整数← 正确答案

答案:ABD。代理键是系统自增的整数主键,与业务解耦;即使源系统业务键被复用或修改,代理键不变,历史关联依然稳定。沿用源系统主键恰恰违背了代理键的初衷。

Q9. 关于 SCD 类型 2,正确的特征包括?

多选题 · hard

  • A.新增一行保留新值← 正确答案
  • B.覆盖原行不留历史
  • C.记录有效起止时间← 正确答案
  • D.可追溯完整价格历程← 正确答案

答案:ACD。类型 2 在属性变化时新增一行并维护有效起止区间,历史查询按时间落点取对应行,从而完整还原当时状态。覆盖原行是类型 1 的做法。

Q10. 月度聚合表已存在,SQL 按“年-类别-门店”查询时,OLAP 引擎最合理的导航路径是?

单选题 · extreme

  • A.直接扫描原始事实表
  • B.命中月度聚合并上卷← 正确答案
  • C.用维度表替代事实表
  • D.终止查询返回空结果

答案:B。查询粒度是年,月度聚合可按年上卷推出结果,引擎应导航到最近聚合层而非扫描全部明细;维度表只提供上下文,替代不了事实表的度量。