Skip to content

微软 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)

  • 定义:数据表在逐行扫描时,当前正在处理的那一个特定行。
  • 产生方式
    1. 创建计算列时,DAX 会隐式对整张表创建行上下文。
    2. 在度量值中使用迭代函数(如 SUMX, AVERAGEX)时,会显式开启行上下文。
  • 注意:行上下文不会自动转换为筛选上下文。若要在行上下文中触发筛选,必须使用 CALCULATE 函数进行“上下文转换 (Context Transition)”。

三、 高频核心 DAX 函数分类详解

1. 引擎之王:控制与修改上下文

CALCULATE

DAX 中最强大、最核心的函数。它是唯一可以修改、覆盖或扩展当前筛选上下文的函数。

dax
-- 语法:CALCULATE([表达式], [筛选器1], [筛选器2], ...)
电子产品销售额 = 
CALCULATE(
    SUM(Sales[SalesAmount]), 
    Product[Category] = "Electronics"
)

ALLALLEXCEPT

用于清除筛选上下文,通常用于计算占总体的百分比。

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 的情况,使用传统 / 会报错或显示为 InfinityDIVIDE 会自动处理被 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. 关系与导航函数

在“多对一”关系中,从“多”的一方(如事实表)提取“一”的一方(如维度表)的维度属性。只能在行上下文(如计算列)中使用。

dax
-- 在销售事实表中创建一个计算列,直接引用产品表的成本价
产品成本列 = RELATED(Product[StandardCost])

USERELATIONSHIP

当两张表之间存在多条动态关系时(例如:销售表中有“下单日期”和“发货日期”,都关联了日期表),激活特定的不活动关系 (Inactive Relationship)。

dax
基于发货日期的销售额 = 
CALCULATE(
    [总销售额], 
    USERELATIONSHIP(Sales[ShipDate], 'Calendar'[Date])
)

四、 DAX 编写最佳实践与性能优化

  1. 规范引用对象名称
    • 引用时,务必带上表名:Table[Column]
    • 引用度量值时,严禁带上表名:[Measure]
    • 示例:SUM(Sales[Amount]) + [TotalTax](一眼就能分清谁是列,谁是度量值)。
  2. 善用 VAR (变量)
    • 使用 VAR 存储中间计算结果,不仅可以大幅提升可读性,还能避免多次重复计算同一指标,从而显著提升查询性能
  3. 避免在计算列中使用复杂逻辑
    • 过多的计算列会迅速撑爆 RAM 内存,导致报表刷新变慢、PBIX 文件体积激增。
  4. 不要使用 FILTER(Table, ...) 过滤整张大表
    • 如果只需过滤某个列,请直接在 CALCULATE 内部书写条件(如 CALCULATE([M], Table[Col] = \"A\")),这样存储引擎会自动优化,而无需用 FILTER 逐行扫描整张表。