Power BI学习笔记第19篇:面试题汇总 · 第二篇:数据建模与 DAX 篇

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 计算时自动只考虑华东的数据

举例对比

// 计算列 — 有行上下文,无筛选上下文
RevenuePerUnit = 'Sales'[Amount] / 'Sales'[Quantity]

// 度量值 — 由视觉对象提供筛选上下文
Total Revenue = SUM('Sales'[Amount])
// 在华东地区图表中自动筛选,只汇总华东数据

常见陷阱:在度量值中直接引用列(如 SELECTEDVALUE(Product[Category])),而不是在筛选上下文中使用——这是新手最容易犯的错误。


第 6 题:什么是 CALCULATE?它为什么是 DAX 中最重要的函数?

参考答案:

CALCULATE 是 DAX 中最强大的函数,没有之一。它在修改筛选上下文的同时执行表达式。

语法

CALCULATE(<expression>, <filter1>, <filter2>, ...)

核心作用:在已有筛选上下文的基础上,添加、替换或移除筛选器,然后执行表达式。

常用场景示例

// 例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 专门为日期维度设计的一系列函数,用于处理同比、环比、年初至今等时间分析。

核心函数列表

// 年初至今(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?它和直接写筛选条件有什么区别?

参考答案:

// 方式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 级别的筛选 用户在视觉对象上实际选择的值

实战举例

// 计算销售总额(移除所有产品筛选)
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>)

常用迭代函数

// 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)
)

为什么需要:普通聚合函数(SUMCOUNT)在度量值中没有行上下文,无法逐行计算。迭代函数是连接行上下文聚合计算的桥梁。


第 12 题:什么是角色扮演维度(Role-Playing Dimension)?如何在 Power BI 中处理?

参考答案:

角色扮演维度指同一个物理日期表在不同业务场景中扮演不同角色——例如同一张日期表,既是"订单日期"又是"发货日期"。

处理方式

方式一:DAX 中的 USERELATIONSHIP(推荐)

// 默认关系:订单日期
Revenue = SUM(Sales[Amount])

// 使用发货日期的关系
Revenue_Shipped = CALCULATE(
    SUM(Sales[Amount]),
    USERELATIONSHIP(Sales[ShipDate], 'Date'[Date])
)

方式二:隐藏重复的日期列

在模型视图中建立多个关系(Power BI 允许一个表与同一张表有多个关系,但只有一个 active),用 DAX 的 USERELATIONSHIP 激活非活动关系。

方式三:创建多个日期表副本(不推荐)

在 Power Query 中复制日期表,改为不同名称——但会导致模型膨胀和维护困难。

最佳实践:保持单一日期表,用 USERELATIONSHIP 激活不同关系。同一模型中只维护一份日期表,确保所有时间智能函数行为一致。


第二篇 · 数据建模与 DAX 篇 · 共三篇

posted on 2026-04-26 09:11  哥本哈士奇(aspnetx)  阅读(5)  评论(0)    收藏  举报

导航