Power BI学习笔记第19篇:面试题汇总 · 第二篇:数据建模与 DAX 篇
数据建模和 DAX 是区分"会用 Power BI"和"真正懂 Power BI"的分水岭。面试官问到这两块,眼睛都在放光------因为答不好的人太多了。
第 1 题:什么是星型模型(Star Schema)?为什么 Power BI 推荐使用它?
参考答案:
星型模型是维度建模的基础结构,由一个事实表 (Fact Table)和多个维度表(Dimension Tables)组成,形状像一颗星星:
┌─────────────┐
│ 维度表 │ ← 日期维度
│ DateKey │
└──────┬──────┘
│
┌───────────┼───────────┐
│ │ │
┌───▼───┐ ┌───▼───┐ ┌───▼───┐
│维度表 │ │维度表 │ │维度表 │
│产品 │ │客户 │ │地区 │
└───┬───┘ └───┬───┘ └───┬───┘
│ │ │
└──────────┼──────────┘
│
┌──────▼──────┐
│ 事实表 │
│ SalesAmount │
│ Quantity │
└─────────────┘
为什么推荐星型模型:
| 优点 | 说明 |
|---|---|
| 查询性能 | 维度表通常较小,JOIN 效率高,VertiPaq 压缩效果好 |
| 模型清晰 | 事实表存度量,维度表存描述,职责明确 |
| 易于维护 | 新增维度只需添加新表,不用改事实表结构 |
| DAX 友好 | 单一路径的relationship,DAX 计算更简单可靠 |
反例:雪花模型(Snowflake Schema,维度表继续规范化拆分子维度)会增加 JOIN 复杂度,Power BI 中应避免。
第 2 题:什么是事实表和维度表?各自有哪些类型?
参考答案:
事实表(Fact Table) :存储业务过程的度量值(Measures),是数据分析的核心。
| 类型 | 特点 | 示例 |
|---|---|---|
| 事务事实表 | 每行业为一条业务事件 | 订单明细、交易记录 |
| 周期快照事实表 | 固定周期汇总 | 月末库存、账户余额 |
| 累计快照事实表 | 全流程状态追踪 | 订单履约全链路 |
| 无事实事实表 | 只记录事件发生,无度量 | 考勤打卡、学生上课 |
维度表(Dimension Table) :存储业务实体的描述信息,提供分析角度。
| 类型 | 特点 | 示例 |
|---|---|---|
| 退化维度(Degenerate) | 存在于事实表中,无独立表 | 订单号、发票号 |
| 缓慢变化维度(SCD) | 属性随时间变化 | 客户地址变更、产品分类调整 |
| 角色扮演维度 | 同一物理表在不同角色下使用 | 日期表同时作为订单日期和发货日期 |
| 雪花维度 | 规范化拆分的维度(不推荐) | 省→市→区的层级表 |
第 3 题:Power BI 中的关系(Relationship)有哪些类型?如何选择?
参考答案:
三种基数(Cardinality)类型:
| 类型 | 说明 | 适用场景 |
|---|---|---|
| 一对多(1:N) | 维度表1行 ←→ 事实表N行 | 星型模型的标准配置 |
| 多对一(N:1) | 与一对多相反,仅视角不同 | 同上 |
| 多对多(M:N) | 双向过滤,无需唯一性 | 多对多业务关系(如学生-课程) |
| 一对一(1:1) | 两表行数相同 | 拆分大表、配置表 |
两个方向(Cross Filter Direction):
| 方向 | 说明 | 风险 |
|---|---|---|
| 单向(Single) | 维度 → 事实单向传播筛选 | ✅ 推荐,逻辑清晰 |
| 双向(Both) | 双向互相影响 | ⚠️ 可能导致歧义和循环依赖,性能差 |
最佳实践 :99% 的场景用一对多 + 单向即可。双向过滤除非你非常清楚自己在做什么,否则别开。
第 4 题:什么是 DAX?它和 Excel 公式有什么区别?
参考答案:
DAX(Data Analysis Expressions)是 Power BI 中的公式语言,用于创建度量值、计算列和角色。
与 Excel 公式的核心区别:
| 维度 | Excel 公式 | DAX |
|---|---|---|
| 上下文 | 基于单元格 | 基于行上下文 + 筛选上下文 |
| 引用范围 | 单表为主 | 全数据模型 |
| 迭代函数 | 无(少数函数支持) | 大量存在(X 系列函数) |
| 时间智能 | 无 | 内置支持(TOTALYTD、SAMEPERIODLASTYEAR 等) |
| 关系使用 | 不支持跨表引用 | 通过 RELATED / RELATEDTABLE 跨表 |
| 用途 | 单元格级计算 | 聚合级度量值 |
DAX 的核心概念:上下文(Context)------你的公式在不同行、不同筛选条件下,结果是不同的。理解不了上下文,就理解不了 DAX。
第 5 题:解释一下 DAX 中的行上下文(Row Context)和筛选上下文(Filter Context)。
参考答案:
这是 DAX 最核心的概念,没有之一。
行上下文(Row Context):
- 作用于当前行,逐行迭代
- 在计算列中自动存在;在度量值中需要用迭代函数(如 SUMX)创建
- 例子:
FullName = [FirstName] & " " & [LastName]--- 对每一行分别执行
筛选上下文(Filter Context):
- 由视觉对象(图表、切片器、筛选器)创建
- 定义哪些行参与当前计算
- 例子:柱状图选了"华东地区",DAX 计算时自动只考虑华东的数据
举例对比:
dax
// 计算列 --- 有行上下文,无筛选上下文
RevenuePerUnit = 'Sales'[Amount] / 'Sales'[Quantity]
// 度量值 --- 由视觉对象提供筛选上下文
Total Revenue = SUM('Sales'[Amount])
// 在华东地区图表中自动筛选,只汇总华东数据
常见陷阱 :在度量值中直接引用列(如 SELECTEDVALUE(Product[Category])),而不是在筛选上下文中使用------这是新手最容易犯的错误。
第 6 题:什么是 CALCULATE?它为什么是 DAX 中最重要的函数?
参考答案:
CALCULATE 是 DAX 中最强大的函数,没有之一。它在修改筛选上下文的同时执行表达式。
语法:
dax
CALCULATE(<expression>, <filter1>, <filter2>, ...)
核心作用 :在已有筛选上下文的基础上,添加、替换或移除筛选器,然后执行表达式。
常用场景示例:
dax
// 例1:计算华东地区总销售额
Revenue_East = CALCULATE(
SUM(Sales[Amount]),
Geography[Region] = "华东"
)
// 例2:计算不含增值税的总销售额
Revenue_ExTax = CALCULATE(
SUM(Sales[Amount]),
REMOVEFILTERS() // 移除所有外部筛选
)
// 例3:计算同比
Revenue YoY =
VAR CurrentYear = SUM(Sales[Amount])
VAR LastYear = CALCULATE(
SUM(Sales[Amount]),
SAMEPERIODLASTYEAR('Date'[Date])
)
RETURN
DIVIDE(CurrentYear - LastYear, LastYear)
为什么重要:Power BI 中几乎所有复杂度量值(同比、环比、排名、动态分段等)都离不开 CALCULATE。不会 CALCULATE,就做不了真正的 BI。
第 7 题:什么是度量值(Measure)和计算列(Calculated Column)?何时使用?
参考答案:
| 度量值(Measure) | 计算列(Calculated Column) | |
|---|---|---|
| 计算时机 | 查询时动态计算 | 数据刷新时一次性计算 |
| 存储位置 | 不占用模型存储空间 | 作为新列存储在表中,占用内存 |
| 上下文 | 受筛选上下文影响 | 受行上下文影响 |
| 性能 | 懒计算(仅在视觉对象需要时) | 预计算(刷新时算好) |
| 适用场景 | 聚合计算(求和、计数、比值) | 需要按行引用的场景(人名拼接、条件分类) |
选择原则:
- 能用度量值解决的事,优先用度量值。因为度量值按需计算,不占内存。
- 计算列适用于:需要作为维度使用(如作为切片器筛选项)、或需要参与 ROW CONTEXT 迭代的场景。
反面教材 :创建 Total Quantity = SUM(Sales[Quantity]) 这样的计算列------它既不需要行上下文参与,又是纯聚合,直接用度量值即可。
第 8 题:什么是时间智能函数?举例说明常用的几种。
参考答案:
时间智能函数是 DAX 专门为日期维度设计的一系列函数,用于处理同比、环比、年初至今等时间分析。
核心函数列表:
dax
// 年初至今(Year-to-Date)
Revenue YTD = TOTALYTD(SUM(Sales[Amount]), 'Date'[Date])
// 季度年初至今
Revenue QTD = TOTALQTD(SUM(Sales[Amount]), 'Date'[Date])
// 月初至今
Revenue MTD = TOTALMTD(SUM(Sales[Amount]), 'Date'[Date])
// 同比(Same Period Last Year)
Revenue LY = CALCULATE(
SUM(Sales[Amount]),
SAMEPERIODLASTYEAR('Date'[Date])
)
// 环比(Previous Period)
Revenue PM = CALCULATE(
SUM(Sales[Amount]),
PARALLELPERIOD('Date'[Date], -1, MONTH)
)
// 移动平均(过去3个月平均)
Revenue MA3 = AVERAGEX(
DATESINPERIOD('Date'[Date], LASTDATE('Date'[Date]), -3, MONTH),
[Revenue] // 引用的是度量值,不是列
)
前提条件 :必须有一个标记为日期表(Mark as Date Table) 的连续日期表,且各行唯一、无空缺。
第 9 题:什么是 KEEPFILTERS?它和直接写筛选条件有什么区别?
参考答案:
dax
// 方式1:不使用 KEEPFILTERS
Revenue_East = CALCULATE(
SUM(Sales[Amount]),
Product[Category] = "电子产品" // ← 会覆盖外部筛选中的产品维度
)
// 方式2:使用 KEEPFILTERS
Revenue_East = CALCULATE(
SUM(Sales[Amount]),
KEEPFILTERS(Product[Category] = "电子产品") // ← 保留外部筛选,交集运算
)
区别:
| 行为 | 裸筛选条件 | KEEPFILTERS |
|---|---|---|
| 外部已有同类筛选 | 覆盖(替换) | 保留(取交集) |
| 结果 | 强制只看电子产品 | 外部筛选了"手机"→结果为0(交集为空) |
| 适用场景 | 想强制特定值时 | 想保持外部筛选一致时 |
使用建议 :默认情况下,当你想保留外部筛选时使用 KEEPFILTERS。如果你在 CALCULATE 的第二个参数里写筛选条件,它会覆盖外部同类筛选------这个行为经常让新手困惑。
第 10 题:什么是 ALLSELECTED?它和 ALL、ALLEXCEPT 的区别是什么?
参考答案:
三者都用于操作筛选上下文,但作用范围不同:
| 函数 | 作用 | 返回值 |
|---|---|---|
ALL() |
移除所有筛选(全局) | 指定表或列的所有唯一值 |
ALLEXCEPT() |
移除除指定列外的所有筛选 | 全局去掉特定列的筛选 |
ALLSELECTED() |
移除视觉对象内的筛选,保留 UI 级别的筛选 | 用户在视觉对象上实际选择的值 |
实战举例:
dax
// 计算销售总额(移除所有产品筛选)
Total Revenue = CALCULATE(
SUM(Sales[Amount]),
ALL(Product[ProductName]) // 不管用户选了哪个产品,都显示总额
)
// ALLSELECTED:只移除视觉对象内切片器的筛选
// 如果用户选了"华东",但没选任何产品 → 计算华东的总销售额
Revenue_East_Total = CALCULATE(
SUM(Sales[Amount]),
Geography[Region] = "华东",
ALLSELECTED(Product) // 移除产品切片器筛选,但保留地区筛选
)
// 百分比计算(分母用 ALLSELECTED 实现占比)
% of Total = DIVIDE(
[Revenue],
CALCULATE([Revenue], ALLSELECTED())
)
一句话总结 :ALL 是全局的,ALLSELECTED 是用户视觉交互层面的。
第 11 题:什么是迭代函数(X 系列函数)?为什么需要它们?
参考答案:
迭代函数对表中的每一行 执行表达式,然后聚合结果------这是 DAX 中创建行上下文的方式。
语法结构 :FunctionX(<table>, <expression>)
常用迭代函数:
dax
// SUMX:逐行求和
Revenue = SUMX(Sales, Sales[Quantity] * Sales[UnitPrice])
// AVERAGEX:逐行求平均
Avg Deal Size = AVERAGEX(Sales, Sales[Amount])
// COUNTX:逐行计数(非空)
Active Deals = COUNTX(Sales, Sales[DealID])
// FILTERX 系列(CALCULATETABLE + FILTER):
// 筛选后返回表(用于嵌套计算)
HighValueCustomers = CALCULATETABLE(
Customer,
FILTER(Customer, Customer[LTV] > 100000)
)
为什么需要 :普通聚合函数(SUM、COUNT)在度量值中没有行上下文,无法逐行计算。迭代函数是连接行上下文 和聚合计算的桥梁。
第 12 题:什么是角色扮演维度(Role-Playing Dimension)?如何在 Power BI 中处理?
参考答案:
角色扮演维度指同一个物理日期表在不同业务场景中扮演不同角色------例如同一张日期表,既是"订单日期"又是"发货日期"。
处理方式:
方式一:DAX 中的 USERELATIONSHIP(推荐)
dax
// 默认关系:订单日期
Revenue = SUM(Sales[Amount])
// 使用发货日期的关系
Revenue_Shipped = CALCULATE(
SUM(Sales[Amount]),
USERELATIONSHIP(Sales[ShipDate], 'Date'[Date])
)
方式二:隐藏重复的日期列
在模型视图中建立多个关系(Power BI 允许一个表与同一张表有多个关系,但只有一个 active),用 DAX 的 USERELATIONSHIP 激活非活动关系。
方式三:创建多个日期表副本(不推荐)
在 Power Query 中复制日期表,改为不同名称------但会导致模型膨胀和维护困难。
最佳实践 :保持单一日期表,用 USERELATIONSHIP 激活不同关系。同一模型中只维护一份日期表,确保所有时间智能函数行为一致。
第二篇 · 数据建模与 DAX 篇 · 共三篇