Appearance
微软 DAX (Data Analysis Expressions) 函数核心指南
DAX(数据分析表达式)是 Microsoft Power BI、Analysis Services (SSAS) 和 Excel Power Pivot 中用于数据建模和计算的公式语言。DAX 的核心能力在于通过动态计算、高级过滤和时间智能,将原始数据转化为深度业务洞察。
一、 DAX 计算的核心:度量值 vs 计算列
在编写 DAX 之前,必须严格区分两种基本的计算机制。理解两者的区别是避免报表性能崩塌的关键。
| 特性 | 计算列 (Calculated Column) | 度量值 (Measure) |
|---|---|---|
| 计算时机 | 数据刷新/载入时计算(静态) | 用户交互、切片器切换或图表渲染时(动态) |
| 存储消耗 | 占用 RAM 内存和磁盘空间 | 不占用物理存储,仅在运行时占用少量 CPU 内存 |
| 上下文 | 默认在 行上下文 (Row Context) 中运行 | 默认在 筛选上下文 (Filter Context) 中运行 |
| 应用场景 | 需要作为切片器、行标签、列标签或轴的数据 | 用于图表中的值、KPI 汇总指标、动态百分比 |
💡 黄金法则:能用度量值解决的,绝不使用计算列。只有在需要将结果用作“轴”或“切片器分类”时,才考虑使用计算列。
二、 终极奥义:双重执行上下文
DAX 的灵魂在于上下文 (Context)。90% 的 DAX 错误(计算结果不符合预期)都是因为没有搞懂以下这两个上下文:
1. 筛选上下文 (Filter Context)
- 定义:由报表页面上的一切外部条件(如切片器、矩阵的行/列标签、图表交叉筛选、视觉对象级别过滤器)共同构成的过滤环境。
- 特点:它是自动存在且动态的。度量值会根据当前单元格所处的过滤条件,先过滤数据表,再进行聚合计算。
2. 行上下文 (Row Context)
- 定义:数据表在逐行扫描时,当前正在处理的那一个特定行。
- 产生方式:
- 创建计算列时,DAX 会隐式对整张表创建行上下文。
- 在度量值中使用迭代函数(如
SUMX,AVERAGEX)时,会显式开启行上下文。
- 注意:行上下文不会自动转换为筛选上下文。若要在行上下文中触发筛选,必须使用
CALCULATE函数进行“上下文转换 (Context Transition)”。
三、 高频核心 DAX 函数分类详解
1. 引擎之王:控制与修改上下文
CALCULATE
DAX 中最强大、最核心的函数。它是唯一可以修改、覆盖或扩展当前筛选上下文的函数。
dax
-- 语法:CALCULATE([表达式], [筛选器1], [筛选器2], ...)
电子产品销售额 =
CALCULATE(
SUM(Sales[SalesAmount]),
Product[Category] = "Electronics"
)ALL 与 ALLEXCEPT
用于清除筛选上下文,通常用于计算占总体的百分比。
dax
-- 清除所有产品分类的筛选,计算全局总销售额
销售额贡献比 =
DIVIDE(
SUM(Sales[SalesAmount]),
CALCULATE(SUM(Sales[SalesAmount]), ALL(Product))
)2. 迭代函数 (X-Functions):逐行计算与聚合
以 X 结尾的函数会创建一个行上下文,先对表中的每一行运行表达式,最后再将结果进行聚合。
SUMX / AVERAGEX
dax
-- 逐行将【单价 * 数量】算出每笔订单的金额,最后再求和
总销售利润 =
SUMX(
Sales,
Sales[UnitPrice] * Sales[Quantity] - Sales[TotalCost]
)3. 安全除法函数
DIVIDE
在报表中难免遇到分母为 0 的情况,使用传统 / 会报错或显示为 Infinity。DIVIDE 会自动处理被 0 除的错误。
dax
-- 如果分母为 0,默认返回 Blank(空),也可指定第三个参数作为替代值
毛利率 =
DIVIDE(
[总利润],
[总销售额],
0
)4. 终极武器:时间智能函数 (Time Intelligence)
时间智能函数允许你轻松计算同比、环比、年初至今等指标。前提条件:模型中必须包含一张标记为“日期表”的连续 Calendar 表。
年初至今 (YTD) / 季度至今 (QTD) / 月初至今 (MTD)
dax
当年至今销售额 = TOTALYTD([总销售额], 'Calendar'[Date])期间平移:去年同期 (YoY) 与 上月同期 (MoM)
dax
-- SAMEPERIODLASTYEAR:自动寻找去年同期的对应日期区间
去年同期销售额 =
CALCULATE(
[总销售额],
SAMEPERIODLASTYEAR('Calendar'[Date])
)
-- DATEADD:更灵活的任意期间平移
上月销售额 =
CALCULATE(
[总销售额],
DATEADD('Calendar'[Date], -1, MONTH)
)增长率计算复合示例
dax
销售额同比增长率 =
VAR CurrentSales = [总销售额]
VAR LastYearSales = [去年同期销售额]
RETURN
DIVIDE(CurrentSales - LastYearSales, LastYearSales, 0)5. 关系与导航函数
RELATED
在“多对一”关系中,从“多”的一方(如事实表)提取“一”的一方(如维度表)的维度属性。只能在行上下文(如计算列)中使用。
dax
-- 在销售事实表中创建一个计算列,直接引用产品表的成本价
产品成本列 = RELATED(Product[StandardCost])USERELATIONSHIP
当两张表之间存在多条动态关系时(例如:销售表中有“下单日期”和“发货日期”,都关联了日期表),激活特定的不活动关系 (Inactive Relationship)。
dax
基于发货日期的销售额 =
CALCULATE(
[总销售额],
USERELATIONSHIP(Sales[ShipDate], 'Calendar'[Date])
)四、 DAX 编写最佳实践与性能优化
- 规范引用对象名称:
- 引用列时,务必带上表名:
Table[Column] - 引用度量值时,严禁带上表名:
[Measure] - 示例:
SUM(Sales[Amount]) + [TotalTax](一眼就能分清谁是列,谁是度量值)。
- 引用列时,务必带上表名:
- 善用
VAR(变量):- 使用
VAR存储中间计算结果,不仅可以大幅提升可读性,还能避免多次重复计算同一指标,从而显著提升查询性能。
- 使用
- 避免在计算列中使用复杂逻辑:
- 过多的计算列会迅速撑爆 RAM 内存,导致报表刷新变慢、PBIX 文件体积激增。
- 不要使用
FILTER(Table, ...)过滤整张大表:- 如果只需过滤某个列,请直接在
CALCULATE内部书写条件(如CALCULATE([M], Table[Col] = \"A\")),这样存储引擎会自动优化,而无需用FILTER逐行扫描整张表。
- 如果只需过滤某个列,请直接在