文章目录
一、 DAX查询工具
1.1 查询工具简介
- DAX Studio:在sqlbi工具页面,可以找到多种BI工具。其中,DAX Studio是一个在 Power BI Designer、Power Pivot for Excel 以及 Analysis Services Tabular 中编写、执行和分析 DAX 查询的工具。访问 http://daxstudio.org 阅读完整文档并下载 DAX Studio 的最新版本。该项目目前在 GitHub 上开源 。
- DAX Guide:专业学习DAX的网站,详细讲解DAX 函数的用法,并给出具体示例。点击try it按钮,可打开DAX.do在线网站,执行查询,获取结果。
- DAX.do:是一个DAX 查询练习和分享平台,内置了Contoso和DAX Guide两个数据集。在DAX Guide上的所有DAX函数示例,都可以在此网站进行测试和编辑。在此网站进行的任何查询操作和自定义设置,都可以保存成自己的私人版本,下次打开网站时自动打开此私人版本。
1.2 DAX.do教程
本章节参考网站主页的介绍视频
1.2.1 DAX.do主界面
以上是网站的主界面:
- 元数据窗格:用于选择数据库和查看数据库的表、列、度量等元数据信息。
- 编辑器:编写和运行 DAX 查询的主要区域。
- 结果窗格:显示查询的执行结果。
- 历史记录窗格:执行的每个查询都记录在“查询历史记录”窗格中,点击可以重新运行查询并查看结果,也可点击界面右侧…,选择清除所有历史记录
- 选项:用于自定义编辑器行为和其他设置
1.2.2 元数据窗格和模型视图
DAX.do 预附带一些托管在云中的示例数据库可供选择(Contoso和DAX Guide),表按字母顺序排序,其中包含表列和度量值。单击某个元素时,底部窗格会显示该元素的详细信息(见右下图),如表中的行数、一列中唯一值的数量等。
点击表名右侧的…按钮,弹出选项列表。左下图三个选项,分别用于在结果窗格中查看预览信息(比如查看表的前10行数据)、显示或隐藏表列/度量值、将表列和度量值也按表格类型分组(事实表和维度表)。
分组后,展开表格下的关系文件夹,可以看到表格之间的关系。关系的方向和基数会突出显示,关系两侧的列右侧有∞标识。大多数列名是灰色显示,表示隐藏。你也可以在元数据窗格中点击右上的模型视图按钮,进入模型视图(自动打开新页面),查看模型关系。
如果把关系视图和当前界面并排显示,当你点击某列时,关系视图中对应列会高亮显示。在模式视图中可以移动表格、更改缩放比例、扩展和调整表的大小。对版式所做的每次更改会保存在浏览器中,下次使用相同数据库时会看到上次修改的版本,点击上方“Reset”按钮可恢复默认布局。
1.2.3 编辑器窗格
创建与编辑查询:创建查询最简单的方式是点击表格右侧的…,选择Preview data
,这将自动创建一个查询,查看表格前10行的数据。如果需要编辑查询,通过左键双击或选择insert into editor,可将表名或列名等插入编辑器中光标所在位置。
编辑器界面右下角三个图标,分别用于清除编辑器内容、缩放编辑器界面显式大小,以及切换结果窗格显示位置(结果窗格默认显示在下方,点击后可显示在右侧)。
编辑器上方的几个按钮,分别用于保存/分析分享查询;撤销、恢复、复制、粘贴;清除编辑器和结果窗格内容,注释/恢复光标所在行代码,以及格式化代码。
当光标在函数名称上时,可以在右侧 DAX 指南窗格中查看有关该函数的更多详细信息。下拉列表显示最近查看的所有函数,可以进行快速跳转。
运行查询:
- 点击编辑器中的“Run”按钮( Ctrl+Enter),可以运行编辑器中的所有内容;
- 点击“Run line”按钮(Alt+Ctrl+Enter),可以仅执行光标的位置之前的代码;
- 选择代码块,“Run line”按钮自动变为"Run Section“,点击之后运行当前所选的活动状态代码;
- 当执行多个求值语句,返回多个查询结果时,可以点击左上方的查询结果编号,进行切换。
1.2.4 结果窗格
默认情况下,查询结果显示为表格。也可以点击右侧按钮,将数据进行可视化,。可视化时,可以更改分配给 x 轴和 y 轴的列。
- 自动格式化:模型中列和度量的格式字符串在 DAX.do 中无法使用。但可以点击此按钮进行自动格式化(日期和数值默认情况下会自动格式化)。
- 自定义数字格式,应用于所有数字列
- 自动调整大小后,启用后每列的宽度进行自适应以查看整个表格,而无需拉动水平滚动条来查看。
- 使表格自动适合宽度
- 启用/禁用单元自动换行
1.2.5 分享和保存DAX查询
- 在线保存和共享:点击撤销左侧的“共享”按钮,可以获得一个公共链接,用于恢复编写的查询以及对视图的定制。公共链接可以在博客文章、电子邮件和社交网络中共享。
- 专用链接和修改:如果要修改保存的查询,可以使用专用链接。
私人链接可以替换内容,而无需创建新的公共链接,这样可以在发表文章后进行小的更正,而不会破坏现有的引荐网址。也可以存储私人代码段集合,如果要修改现有的代码段,可以使用专用链接。 - 嵌入代码:
- 如果想在博客文章中分享代码,可以遵循嵌入部分中的说明。
- 可以复制并粘贴 CSS 和 HTML 代码、查询以及结果; 还可以下载代码并将结果转换为 Word 文件。
- 如果在 WordPress 上运行博客,可以复制兼容的代码段与语法荧光笔演进插件。
- 可以在示例页面中查看 HTML 代码的预览。
1.2.6 DAX.do选项
- 自定义编辑器行为:
- 如果行较长,可以启用自动换行,避免使用水平滚动条。
- “保存历史标记”可以跟踪查询历史记录窗格中对查询的所有编辑。
- 如果查询包含来自不同表的列,可以选择是否显示他们的名字,通过使用“隐藏表名称”选项,保持该标志为活动状态可以使结果更易于阅读。
- DAX 格式化程序:有关 DAX 格式化程序选项的部分控制结果格式代码按钮。
- 匿名数据收集:DAX.do 用户界面可以向开发者发送匿名数据,关于您如何使用网站的信息,这有助于改善服务。
1.3 使用 Performance Analyzer 查看 Power BI 实际生成的 DAX 查询
Performance Analyzer 是 Power BI 内置的性能分析工具,可以帮助您查看报表元素实际生成的 DAX 查询,以下是启用步骤:
- 切换到"视图"选项卡:打开最新版本的 Power BI Desktop,在顶部菜单栏中选择"视图"(View)
- 打开 Performance Analyzer 面板:在"显示"(Show)区域勾选"Performance Analyzer",或使用快捷键 Ctrl+Shift+P打开 Performance Analyzer 面板
- 捕获和分析 DAX 查询
- 开始记录:在 Performance Analyzer 面板中点击"开始记录"(Start Recording)按钮;
- 刷新可视化元素:点击面板中的"刷新视觉对象"(Refresh visuals)按钮刷新当前页所有可视化,或单独点击每个可视化右上角的"刷新"图标
- 查看生成的查询:在记录列表中展开您感兴趣的可视化,找到"DAX 查询"(DAX query)条目即可。点击右侧的"复制"按钮可复制完整查询。
- 查询分析: Power BI可能添加一些隐藏逻辑,比如自动添加的度量值(如 COUNTROWS)。可以将将复制的查询粘贴到 DAX Studio 中进一步分析,获取查询计划和详细性能指标。
1.4 DAX Studio教程(待补)
二、 EVALUATE
参考DAX权威指南《理解EVALUATE》
2.1 基本语法和用法
EVALUATE 语句是 DAX 查询的核心,其基本结构如下,可分为三个主要部分:
-- Definition section
[DEFINE { MEASURE <tableName>[<name>] = <expression> }
{ COLUMN <tableName>[<name>] = <expression> }
{ TABLE <tableName> = <expression> }
{ VAR <name> = <expression>}]
-- Query expression
EVALUATE <table>
-- Result modifiers
[ORDER BY {<expression> [{ASC | DESC}]}[, …]
[START AT {<value>|<parameter>} [, …]]]
-
Definition Section
:定义部分(可选),由 DEFINE 关键字引入,用于定义表、列、变量和度量值等实体。这些定义的作用域是与整批EVALUATE
语句一起执行的。 -
Query Expression
:查询表达式,由EVALUATE
关键字引入。EVALUATE 需要一个表作为输入(可以是表或者表表达式),其输出也是一个表。这部分是EVALUATE
语句的核心,必须存在。查询表达式可以包含多个EVALUATE
语句,每个都有自己的结果修饰符集。 -
Result Modifiers
:结果修饰符(可选),由 ORDER BY关键字引入。它不仅可以定义返回结果的排序顺序,还可以通过提供 START AT 起始点来指定从哪一行开始返回结果。
以下是一个简单的 EVALUATE
查询示例,返回红色产品的信息:
EVALUATE
CALCULATETABLE (
'Product',
'Product'[Color] = "Red"
)
以下是一个更复杂的查询示例,展示了如何使用 DEFINE
部分定义变量和查询表达式:
DEFINE
VAR MinimumAmount = 2000000
VAR MaximumAmount = 8000000
EVALUATE
FILTER (
ADDCOLUMNS (
SUMMARIZE (
Sales,
'Product'[Category]
),
"CategoryAmount", [Sales Amount]
),
AND (
[CategoryAmount] >= MinimumAmount,
[CategoryAmount] <= MaximumAmount
)
)
ORDER BY [CategoryAmount]
- 定义变量:定义销售额的上下限
MinimumAmount
和MaximumAmount
; - 分组汇总:使用
SUMMARIZE
函数对Sales
表按产品类别进行分组汇总; - 添加列:使用
ADDCOLUMNS
函数在汇总表中添加类别销售额列"CategoryAmount"
,其计算结果是筛选数据的Sales Amount; - 筛选数据:使用
FILTER
函数筛选出销售额在上下限之间的行; - 排序结果:使用
ORDER BY
修饰符按销售额对结果进行排序。
最终返回的结果是一个表,包含销售额在 200 万到 800 万之间的所有产品类别及其对应的销售额,并且按销售额升序排列。
要注意的是,DEFINE 部分和 ORDER BY 修饰符只能在 EVALUATE 语句中使用,它们是 DAX 查询的特定功能。如果开发人员编写了一个查询,并且后续打算将这个查询的结果用作计算表,那么只有EVALUATE 语句中的内容是通用的,DEFINE 和 ORDER BY在DAX公式中并不支持。
2.2 使用 VAR
2.2.1 查询变量
在DEFINE
部分使用VAR
关键字定义的变量,称之为查询变量。查询变量不需要 RETURN 部分,作用域为整个查询(不能在定义前使用,不能跨查询使用)。所以如果一个查询包含多个求值部分(多个EVALUATE语句),则所有这些求值部分都可以使用查询变量。
例如,在 Power BI 中,系统会将切片器(Slicer)的过滤条件存储为查询变量,并在多个 EVALUATE 语句中使用这些变量来分别计算可视化报表的不同部分。这种做法可以提高查询的可维护性和可读性,同时确保过滤条件在多个计算中保持一致。
查询变量也可以是值或表,比如上一节的代码,也可以通过定义表变量来实现:
DEFINE
VAR MinimumAmount = 2000000
VAR MaximumAmount = 8000000
VAR CategoriesSales =
ADDCOLUMNS (
SUMMARIZE (
Sales,
'Product'[Category]
),
"CategoryAmount", [Sales Amount]
)
EVALUATE
FILTER (
CategoriesSales,
AND (
[CategoryAmount] >= MinimumAmount,
[CategoryAmount] <= MaximumAmount
)
)
ORDER BY [CategoryAmount]
-- 添加第二个EVALUATE语句,同样可以引用查询变量
EVALUATE
FILTER (
CategoriesSales,
[CategoryAmount] >= MinimumAmount
)
在这个例子中,CategoriesSales
是一个查询变量,它包含了一个表表达式。查询变量可以在后续所有EVALUATE部分中使用。
2.2.2 表达式变量
变量也可以在EVALUATE
部分定义,称之为表达式变量,此时,它是表表达式的本地变量。表达式变量等价于在DAX公式(比如一般的度量值公式)中定义的变量,所以同样需要 RETURN 关键字来定义表达式的结果,其作用域也同样是整个VAR/RETURN块。
本地变量是在表表达式内部定义的变量,它们的作用域仅限于该表表达式,其主要作用是存储中间计算结果,使表达式更加清晰和易于维护。
以上代码可改写为:
EVALUATE
VAR MinimumAmount = 2000000
VAR MaximumAmount = 8000000
VAR CategoriesSales =
ADDCOLUMNS (
SUMMARIZE (
Sales,
'Product'[Category]
),
"CategoryAmount", [Sales Amount]
)
RETURN
FILTER (
CategoriesSales,
AND (
[CategoryAmount] >= MinimumAmount,
[CategoryAmount] <= MaximumAmount
)
)
ORDER BY [CategoryAmount]
尽可能使用表达式变量,并在严格必要时使用查询变量:如果某个变量仅在单个表达式中使用,而不是在多个 EVALUATE 部分或其他定义中重复使用,那么最好将其定义为表达式变量,而不是查询变量。查询变量仅在当前查询有效,如果需要在模型中复用,因作用域和依赖关系,需更多手动调整。表达式变量虽在当前查询内,但与模型无缝集成。例如:
- 查询变量可能依赖外部筛选条件或计算上下文, 若其他查询需要类似的逻辑但不同区域(如"Asia"),需修改原始变量或创建新变量,导致代码碎片化
DEFINE VAR FilteredSales = CALCULATE(SUM(Sales[Amount]), Sales[Region] = "Europe")
- 表达式变量通过度量值+参数化设计规避此问题。当逻辑需要复用时,将表达式变量提升为模型层的度量值。
SalesByRegion = VAR TargetRegion = SELECTEDVALUE(RegionParam[Region], "Europe") -- 参数化 RETURN CALCULATE(SUM(Sales[Amount]), Sales[Region] = TargetRegion)
以上是AI生成,不一定正确,意会即可。
2.3 使用MEASURE
除了变量,还可以在定义部分定义查询度量值,定义时,必须指定承载该度量值的表。查询度量值在所有方面都像常规度量值一样,但它们只在查询的生存期内存在。例如:
DEFINE
MEASURE Sales[LargeSales] =
CALCULATE (
[Sales Amount],
Sales[Net Price] >= 200
)
EVALUATE
ADDCOLUMNS (
VALUES ( 'Product'[Category] ),
"Large Sales", [LargeSales]
)
度量值通常是基于某个表的上下文来计算的,即使该度量值不直接引用该表的字段。使用“宿主表”来定义其上下文,可以确保计算逻辑的清晰性和一致性,这种做法也延续了 DAX 模型中度量值与表关联的语义规则。
查询度量值(Query Measures)有两个主要用途:
-
在查询中复用复杂表达式,等同于在DAX模型中复用度量值
-
调试和性能优化。
假设一个复杂公式结果异常,可以分步定义查询度量来验证中间值,快速定位错误。而且这些度量值只在查询执行期间存在,不会保存到数据模型中,避免破坏现有逻辑,比直接修改原始度量公式更安全。DEFINE MEASURE Sales[TotalSales] = SUM(Sales[Amount]) -- 步骤1:验证基础计算 MEASURE Sales[FilteredSales] = CALCULATE([TotalSales], Sales[Region] = "Europe") -- 步骤2:验证筛选逻辑 MEASURE Sales[FinalResult] = DIVIDE([FilteredSales], COUNTROWS(Sales)) -- 步骤3:验证最终计算 EVALUATE ROW("Debug", [FinalResult]) -- 输出结果检查
查询度量可以覆盖模型中已定义的同名度量值。这意味着,如果你在查询中定义了一个与模型中同名的度量值,查询中会优先使用查询度量,而不是模型中的度量值。这一点为调试和性能优化提供了很大的灵活性。
综上所述,如果需要测试度量,最佳实践是先编写一个使用该度量值的查询,并在查询中添加该度量值的本地定义。然后,通过执行各种测试来调试或优化代码。完成这些测试后,可以将经过优化的度量值代码更新到数据模型中,以供后续使用。
三、 常见查询
参考DAX权威指南《常见的 DAX 查询模式》
函数名称 | 简要说明 |
---|---|
ROW | 将单个值或多个值转换为表,常用于测试度量值或生成单行表。 |
SUMMARIZE | 按指定列对表进行分组,并可添加列(不推荐直接添加聚合值)。支持 ROLLUP 和 ISSUBTOTAL 用于添加小计行。 |
SUMMARIZECOLUMNS | 强大的查询函数,支持分组、添加列和过滤器,自动删除全为空值的行。支持小计和分组功能。 |
GROUPBY | 按一个或多个列对表进行分组,支持对非数据模型列进行分组,需要使用迭代器聚合数据。 |
ADDMISSINGITEMS | 添加 SUMMARIZECOLUMNS 可能跳过的行,确保结果中包含所有必要的行。 |
TOPN | 返回表的前 N 行,支持按多个列排序,并可处理相同值的情况。 |
GENERATE | 迭代表的每一行,计算表达式,并将结果与原表连接。类似于 SQL 的外连接。 |
GENERATEALL | 与 GENERATE 类似,但即使表达式返回空表,也会保留第一张表的行。 |
ISONORAFTER | 用于分页和浏览报表,帮助 Power BI 检索当前页所需的行。 |
NATURALINNERJOIN | 基于同名列对两个表执行内连接。 |
NATURALLEFTOUTERJOIN | 基于同名列对两个表执行左外连接,保留第一个表的所有行。 |
SUBSTITUTEWITHINDEX | 将矩阵的列名转换为索引,用于在 Power BI 中正确显示矩阵。 |
SAMPLE | 返回表中均匀分布的行样本,常用于生成图表轴的值或执行统计分析。 |
3.1使用 ROW 函数测试度量值
在 DAX 中,EVALUATE
语句需要一个表作为输入,并返回一个表作为结果。如果直接使用度量值,EVALUATE
无法接受,因为度量值不是表。为了测试度量值,可以使用 ROW
函数或表构造函数(花括号)将度量值转换为表。例如:
EVALUATE
ROW ( "Result", [Sales Amount] )
EVALUATE
{ [Sales Amount] }
两者都返回一个只有一行的表,其中包含度量值 [Sales Amount]
的结果。不同的是,表构造函数则自动生成列名,而ROW
函数可自定义列名,可以引入多个列。如果需要在筛选上下文中(模拟切片器的存在)计算度量值,可以结合 CALCULATETABLE
使用:
EVALUATE
CALCULATETABLE (
ROW (
"Sales", [Sales Amount],
"Cost", [Total Cost]
),
'Product'[Color] = "Red"
)
3.2 使用 SUMMARIZE 分组
3.2.1 基本语法与用法
SUMMARIZE
有两个主要功能:分组和添加列。前者用于提取多个列的有效组合,后者因为性能原因,并不推荐,其语法为:
SUMMARIZE(
<Table>, -- 要进行汇总的表
<GroupBy_Expression1>, ..., -- 分组表达式,可以有多个
<Name1>, <Expression1>, ... -- 定义新列的名称和表达式,可以有多个
[, <Filter_Expression>] -- 可选的过滤表达式
)
3.2.2 汇总功能(不推荐)
此外,SUMMARIZE
提供了两个附加功能:ROLLUP
和 ISSUBTOTAL
,用于在结果中添加分层汇总(也不推荐,只是为了说明其用法)。例如:
EVALUATE
SUMMARIZE (
Sales,
ROLLUP (
'Product'[Category],
'Date'[Calendar Year]
),
"Sales", [Sales Amount]
)
ORDER BY
'Product'[Category],
'Date'[Calendar Year]
下图左有两侧分别是不使用和使用ROLLUP
的结果。使用ROLLUP
后,除了计算每个类别和年份的销售额,还会添加分层汇总(包括每一层级的小计和最后的总计)。
在上述查询的ROLLUP
里面再套一层ROLLUPGROUP
,就只返回总计,而不返回小计。此外,使用ROLLUP
得到的结果中,分层汇总行会出现BLANK(空白),如果数据中本身存在空值,则会导致混淆。为了区分普通行和汇总行,可以使用 ISSUBTOTAL
函数进行标记,对于普通行返回False,分层汇总行返回True:
EVALUATE
SUMMARIZE (
Sales,
ROLLUP (
'Product'[Category],
'Date'[Calendar Year]
),
"Sales", [Sales Amount],
"SubtotalCategory", ISSUBTOTAL ( 'Product'[Category] ),
"SubtotalYear", ISSUBTOTAL ( 'Date'[Calendar Year] )
)
ORDER BY
'Product'[Category],
'Date'[Calendar Year]
SUMMARIZE
函数推荐只用来进行分组,得到各列的有效组合。使用它来增加扩展列的计算非常复杂,新增列的计算同时处于行上下文和过滤上下文中,以至于结果可能出乎意料。比如,第一次测试时OK,但是部署到生产环境中就会出错,且难以调试。
3.2.3 ADDCOLUMNS+SUMMARIZE
所以,出于性能和内部兼容性等方面的原因,上述代码直接使用 SUMMARIZE
添加聚合值的列([Sales Amount])并不推荐。建议使用ADDCOLUMNS和SUMMARIZE组合来代替。当然,最优的方式还是使用SUMMARIZECOLUMNS进行汇总。
EVALUATE
ADDCOLUMNS (
SUMMARIZE ( Sales, 'Product'[Category], 'Date'[Calendar Year] ),
"Sales", [Sales Amount]
)
ORDER BY
'Product'[Category],
'Date'[Calendar Year]
3.3 使用 SUMMARIZECOLUMNS 查询
3.3.1 基本用法:分组+添加列
SUMMARIZECOLUMNS 是一个功能强大的查询函数,集成了执行查询所需的所有功能。它可以指定分组列、添加新列以及应用过滤器,Power BI 几乎对其运行的所有查询都使用 SUMMARIZECOLUMNS
。其语法为:
SUMMARIZECOLUMNS(
<groupBy_columnName>[,...], -- 要分组的列,必须是表中的列名
[<filterTable>], -- 筛选器表
[<filter1>], [<filter2>], ..., -- 筛选条件,可选,用于限制汇总的数据范围
[<name1>, <expression1>], ... -- 汇总列,可选,用于在汇总表中添加新的计算列(列名+计算表达式)
)
例如上一节中,需要添加列,可以这么写:
EVALUATE
SUMMARIZECOLUMNS (
'Product'[Category],
'Date'[Calendar Year],
"Amount", [Sales Amount]
)
ORDER BY
'Product'[Category],
'Date'[Calendar Year]
3.3.2 使用 IGNORE保留空白行
当 SUMMARIZECOLUMNS
的结果中某一行的所有计算列都为空值时,这一行默认会被自动删除(比如2005年没有销售额),避免结果表中出现完全空白的行。你可以通过 IGNORE 修饰符指定哪些列可以保留空白行,例如:
EVALUATE
SUMMARIZECOLUMNS (
'Product'[Category],
'Date'[Calendar Year],
"Amount", IGNORE ( [Sales Amount] )
)
ORDER BY
'Product'[Category],
'Date'[Calendar Year]
3.3.3 使用ADDMISSINGITEMS恢复空值行
ADDMISSINGITEMS 的主要作用是恢复 SUMMARIZECOLUMNS 过滤掉的空值行,确保分组结果的完整性。其基本语法为:
ADDMISSINGITEMS(
[<显示列>],
[<表表达式>],
[<分组列>]
[, [<筛选器1>], [<筛选器2>]...]
)
比如以下查询,会过滤掉无销售的年份:
EVALUATE
SUMMARIZECOLUMNS (
'Date'[Calendar Year],
"Amt", [Sales Amount]
)
ORDER BY 'Date'[Calendar Year]
使用 ADDMISSINGITEMS 补充缺失项:
EVALUATE
ADDMISSINGITEMS (
'Date'[Calendar Year],
SUMMARIZECOLUMNS ( 'Date'[Calendar Year], "Amt", [Sales Amount] ),
'Date'[Calendar Year]
)
ORDER BY 'Date'[Calendar Year]
3.3.4 添加小计
-
添加单列小计:
ROLLUPADDISSUBTOTAL
用于在汇总表中添加小计行,并生成一个布尔列(如"YearTotal"
或"CategoryTotal"
),用于标记哪些行是小计行。比如需要年度小计,则应使用 RollupAddIsSubtotal 标记’Date’[Calendar Year],并提供小计的列名称::EVALUATE SUMMARIZECOLUMNS ( 'Product'[Category], ROLLUPADDISSUBTOTAL ( 'Date'[Calendar Year], "YearTotal" ), "Amount", [Sales Amount] ) ORDER BY 'Product'[Category], 'Date'[Calendar Year]
生成的表中包含按
'Product'[Category]
和'Date'[Calendar Year]
的分组汇总,以及按'Date'[Calendar Year]
的小计行。YearTotal
列中TRUE
的表示当前行是按'Date'[Calendar Year]
统计的的小计行。 -
添加多列小计。
以下代码生成的表中包含按'Product'[Category]
和'Date'[Calendar Year]
的分组汇总,以及按'Product'[Category]
和'Date'[Calendar Year]
的小计行。EVALUATE SUMMARIZECOLUMNS ( ROLLUPADDISSUBTOTAL ( 'Product'[Category], "CategoryTotal" ), ROLLUPADDISSUBTOTAL ( 'Date'[Calendar Year], "YearTotal" ), "Amount", [Sales Amount] ) ORDER BY 'Product'[Category], 'Date'[Calendar Year]
-
只添加总计:使用 ROLLUPGROUP可以只添加总计行,不添加分层汇总:
EVALUATE SUMMARIZECOLUMNS ( ROLLUPADDISSUBTOTAL ( ROLLUPGROUP ( 'Product'[Category], 'Date'[Calendar Year] ), "CategoryYearTotal" ), "Amount", [Sales Amount] ) ORDER BY 'Product'[Category], 'Date'[Calendar Year]
3.3.4 过滤
添加表筛选器:与 CALCULATETABLE 类似,SUMMARIZECOLUMNS
可以使用表作为定筛选器,但不能是简单的布尔筛选器。以下是一个简单的示例,检索受过高中教育的客户的销售额:
EVALUATE
SUMMARIZECOLUMNS (
ROLLUPADDISSUBTOTAL (
ROLLUPGROUP (
'Product'[Category],
'Date'[Calendar Year]
),
"CategoryYearTotal"
),
FILTER (
ALL ( Customer[Education] ),
Customer[Education] = "High School"
),
"Amount", [Sales Amount]
)
如果将筛选条件改为: Customer[Education] = "High School"
,则会报错,因为筛选器只能是表。另一种简单而紧凑的方法是使用 TREATAS:
EVALUATE
SUMMARIZECOLUMNS (
ROLLUPADDISSUBTOTAL (
ROLLUPGROUP ( 'Product'[Category], 'Date'[Calendar Year] ),
"CategoryYearTotal"
),
TREATAS ( { "High School" }, Customer[Education] ),
"Amount", [Sales Amount]
)
上下文转换限制:SUMMARIZECOLUMNS
适用于直接编写查询,但无法处理上下文转换,所以不适合用于度量值的计算(度量值通常会在报表工具中触发上下文转换,比如矩阵或表格中),也不能替代ADDCOLUMNS
。
3.4 TOPN 函数
3.4.1 基本用法
TOPN
函数用于对表进行排序,并返回前 N 行,它在减少结果集大小和检索顶级执行者(如顶级产品或客户)时非常有用。其基本语法为:
TOPN (
<N>, -- 返回的行数
<Table>, -- 要筛选的表
<OrderBy_Expression>[, <Order> -- 用于排序的表达式以及排序方式。0/FALSE/DESC表示降序,1/TRUE/ASC表示升序(默认)
[, <OrderBy_Expression> [, <Order> [, … ] ] ] ]
)
比如返回销售额最高的前三个产品,结果包含所有列:
EVALUATE
TOPN ( 3, 'Product', [Sales Amount] )
3.4.2 常规用法:返回所需列并排序
在实际应用中,我们通常不需要源表的所有列,而是只对某些特定列感兴趣,此时可以通过 SUMMARIZE 选择需要的列组合。进一步的,我们想根据Sales Amount值进行排序,可以使用ADDCOLUMNS
添加Sales Amount
计算列,得到新表,然后选其前N行,并按Sales Amount计算列的值排序:
EVALUATE
VAR ProductsBrands =
SUMMARIZE ( Sales, 'Product'[Product Name], 'Product'[Brand] )
VAR ProductsBrandsSales =
ADDCOLUMNS ( ProductsBrands, "Product Sales", [Sales Amount] )
VAR Result =
TOPN ( 3, ProductsBrandsSales, [Product Sales] )
RETURN
Result
ORDER BY [Product Sales] DESC
不要混淆 TOPN 的排序顺序和查询结果的排序顺序;后者由 EVALUATE 语句的 ORDER BY 条件管理。TOPN 的第三个参数只影响如何对 TOPN 本身在内部生成的表进行排序。
3.4.3 使用多个排序列
在某些情况下,可能存在多个行具有相同的排序值。TOPN 函数会返回所有具有相同值的行,而不仅仅是前 N 行。为了避免这种情况,可以通过添加额外的排序列来减少相同值的行数,比如在具有相同值时,按品牌名进行排序:
EVALUATE
VAR SalesByBrand =
ADDCOLUMNS (
VALUES ( 'Product'[Brand] ),
"Product Sales", MROUND ( [Sales Amount], 1000000 )
)
VAR Result =
TOPN ( 4, SalesByBrand, [Product Sales], 0, 'Product'[Brand], 1 )
RETURN
Result
ORDER BY [Product Sales] DESC
下图是使用添加与不添加'Product'[Brand]
排序列的结果:
添加第二个排序列,还是不能保证一定筛选出前N行。如果要保证检索的行数准确无误,则应使用唯一值列进行排序。
3.4.4 汇总“剩余”数据
下面考虑一个更复杂的情况:筛选出销售金额最高的前 10 个产品,并汇总所有剩余产品的销售额。解题思路是:
- 使用 ADDCOLUMNS 函数,为每个产品名称添加一个销售金额列
"Product Sales"
- 使用 TOPN 函数,取销售金额最高的前 10 个产品
- 使用 EXCEPT 函数,排除掉 TopNProducts,得到剩余的产品表
- 使用 ROW 函数,创建一个包含“其它”行的表 OtherRow
- 使用 UNION 函数,将 TopNProducts 和 OtherRow 合并成一个表 Result
EVALUATE
VAR N = 10
VAR ProdsWithSales =
ADDCOLUMNS (
VALUES ( 'Product'[Product Name] ),
"Product Sales", [Sales Amount]
)
VAR TopNProducts =
TOPN ( N, ProdsWithSales, [Product Sales] )
VAR RemainingProducts =
EXCEPT ( ProdsWithSales, TopNProducts )
VAR OtherRow =
ROW (
"Product Name", "Others",
"Product Sales", SUMX ( RemainingProducts, [Product Sales] )
)
VAR Result =
UNION ( TopNProducts, OtherRow )
RETURN
Result
ORDER BY [Product Sales] DESC
上述代码得到的结果,Others行排在最前面。一般来说,肯定是希望将其它行排在最后,这可以通过添加一个排序列来解决:
EVALUATE
VAR N = 10
VAR ProdsWithSales =
ADDCOLUMNS (
VALUES ( 'Product'[Product Name] ),
"Product Sales", [Sales Amount]
)
VAR TopNProducts =
TOPN ( N, ProdsWithSales, [Product Sales] )
VAR RemainingProducts =
EXCEPT ( ProdsWithSales, TopNProducts )
VAR RankedTopProducts =
ADDCOLUMNS (
TopNProducts,
"SortColumn", RANKX ( TopNProducts, [Product Sales] )
)
VAR OtherRow =
ROW (
"Product Name", "Others",
"Product Sales", SUMX ( RemainingProducts, [Product Sales] ),
"SortColumn", N + 1
)
VAR Result =
UNION ( RankedTopProducts, OtherRow )
RETURN
Result
ORDER BY [SortColumn]
3.4.5 TOPNSKIP(略)
TOPNSKIP 是 DAX 中一个用于分页查询的强大函数,它结合了 TOPN 和 SKIP 的功能,专门用于实现高效的数据分页加载。
3.5 使用 GENERATE 和 GENERATEALL
3.5.1 GENERATE
GENERATE 是一个功能强大的函数,它借鉴了 SQL 语言中的外部应用逻辑——迭代一个表,在每行的上下文中计算一个表达式,然后将迭代的行与表达式返回的行连接起来,所以参数只有表与计算表达式两个。此行为类似于常规联接,但不是与静态表联接,而是与为每行动态计算的表达式联接,是一个非常通用的功能。
假设我们需要计算每年销售额排前三名产品,我们可以使用TOPN先计算销售额排前三的产品:
EVALUATE
VAR ProductsSold =
SUMMARIZE ( Sales, 'Product'[Product Name] )
VAR ProductsSales =
ADDCOLUMNS ( ProductsSold, "Product Sales", [Sales Amount] )
VAR Top3Products =
TOPN ( 3, ProductsSales, [Product Sales] )
RETURN
Top3Products
ORDER BY [Product Sales] DESC
接着,我们使用 GENERATE 来迭代年份,并为每一年计算 TOPN 表达式。GENERATE 在每次迭代时将年份与表达式的结果连接起来:
EVALUATE
GENERATE (
VALUES ( 'Date'[Calendar Year] ),
CALCULATETABLE (
VAR ProductsSold = SUMMARIZE(Sales, 'Product'[Product Name])
VAR ProductsSales = ADDCOLUMNS(ProductsSold, "Product Sales", [Sales Amount])
VAR Top3Products = TOPN(3, ProductsSales, [Product Sales])
RETURN Top3Products
)
)
ORDER BY 'Date'[Calendar Year], [Product Sales] DESC
如果需要按类别计算TopN产品,则公式中唯一需要更新的是 将GENERATE
迭代的表改为VALUES ( 'Product'[Category] )
:
EVALUATE
GENERATE (
VALUES ( 'Product'[Category] ),
CALCULATETABLE (
VAR ProductsSold = SUMMARIZE ( Sales, 'Product'[Product Name] )
VAR ProductsSales = ADDCOLUMNS ( ProductsSold, "Product Sales", [Sales Amount] )
VAR Top3Products = TOPN ( 3, ProductsSales, [Product Sales] )
RETURN
Top3Products
)
)
ORDER BY
'Product'[Category],
[Product Sales] DESC
3.5.2 GENERATEALL
在GENERATE
中,如果第二个参数的表达式返回空表,则在结果中跳过该行。如果对于空行的结果也要保留,则需要使用GENERATEALL。比如2005 年没有销售,因此 2005 年没有前三名产品,GENERATE 不返回 2005 年的任何行。但是使用GENERATEALL,可以返回所有年份的统计结果:
EVALUATE
GENERATEALL (
VALUES ( 'Date'[Calendar Year] ),
CALCULATETABLE (
VAR ProductsSold = SUMMARIZE(Sales, 'Product'[Product Name])
VAR ProductsSales = ADDCOLUMNS(ProductsSold, "Product Sales", [Sales Amount])
VAR Top3Products = TOPN(3, ProductsSales, [Product Sales])
RETURN Top3Products
)
)
ORDER BY 'Date'[Calendar Year], [Product Sales] DESC
总结:GENERATE
是一个非常通用的函数,特别适合需要为某个分组的每个成员重复相同计算逻辑的场景。通过结合 CALCULATETABLE ,可以在不同的筛选上下文中执行计算,从而实现复杂的分组分析需求。
3.6 ISONORAFTER
ISONORAFTER用于确定某行是否在指定的排序键值之后或等于该值,它通常用于实现分页逻辑。在用户浏览报表时,通过ISONORAFTER
可以只检索当前页所需的行。其语法为:
ISONORAFTER(<column1>, <value1>, <sort1>[, <column2>, <value2>, <sort2>]...)
当用户浏览一个 Products 表时,他们可能会在扫描过程中到达某个特定位置。比如下图中,用户浏览到的最后一行产品名为"WWI Stereo Bluetooth Headphones New Generation M370 Yellow"。
当用户继续向下滚动时,Power BI 需要检索下一行,这本质上还是一个TopN问题:
- 使用
TOPN
函数检索下一批数据。 - 使用
ISONORAFTER
函数来确保检索到的下一行是当前最后一行之后的。
通过这种方式,ISONORAFTER
函数帮助 Power BI 在用户向下滚动时高效地检索和显示下一批数据:
EVALUATE
TOPN (
501, -- 加载501行(500行作为当前页,1行作为下一页的预览,触发下一页加载)
FILTER (
SUMMARIZECOLUMNS (
'Product'[Category],
'Product'[Color],
'Product'[Product Name],
"Sales_Amount", 'Sales'[Sales Amount]
),
ISONORAFTER ( -- "获取所有在以下条件之后或等于的记录
'Product'[Category], "Audio", ASC,
'Product'[Color], "Yellow", ASC,
'Product'[Product Name], "WWI Stereo Bluetooth Headphones New Generation M370 Yellow", ASC
)
),
'Product'[Category], 1, -- 排序方向(1=ASC)
'Product'[Color], 1,
'Product'[Product Name], 1
)
ORDER BY
'Product'[Category],
'Product'[Color],
'Product'[Product Name]
- 创建一个包含类别、颜色、产品名和销售额的表;
- 使用
ISONORAFTER
从特定的"锚点"开始获取数据("Audio"类别,"Yellow"颜色,特定产品名称之后) - 获取501条记录(当前页+下一页预览),并按指定列进行排序
ISONORAFTER
的上述用法等效于:
'Product'[Category] > "Audio"
|| ( 'Product'[Category] = "Audio"
&& 'Product'[Color] > "Yellow" )
|| ( 'Product'[Category] = "Audio"
&& 'Product'[Color] = "Yellow"
&& 'Product'[Product Name] >= "WWI Stereo Bluetooth Headphones New Generation M370 Yellow" )
ISONORAFTER
的优势的优势是,比复杂的布尔逻辑更易编写和维护、查询执行计划更优。
3.7 GROUPBY
GROUPBY
用于按一个或多个列对表进行分组,并聚合其他数据,类似于 ADDCOLUMNS
+ SUMMARIZE
的组合使用,但有一些区别:
对比维度 | GROUPBY | SUMMARIZE |
---|---|---|
分组列来源 | 可以对计算列或查询中添加的列进行分组 | 只能使用数据模型中已定义的列 |
聚合方式 | 必须使用迭代器函数(如 SUMX , AVERAGEX 等) | 可以直接使用聚合函数(如 SUM , AVERAGE ) |
GROUPBY 的典型结构:
GROUPBY (
表名,
分组列1,
分组列2,
"新列名", 迭代器函数(CURRENTGROUP(), 表达式)
)
SUMMARIZE 的典型结构:
SUMMARIZE (
表名,
分组列1,
分组列2,
"新列名", 聚合函数(表达式)
)
3.7.1 性能问题
按年和月对销售进行分组并计算销售金额,可以使用GROUPBY :
EVALUATE
GROUPBY (
Sales,
'Date'[Calendar Year],
'Date'[Month],
'Date'[Month Number],
"Amt", AVERAGEX ( CURRENTGROUP (), Sales[Quantity] * Sales[Net Price] )
)
ORDER BY
'Date'[Calendar Year],
'Date'[Month Number]
要注意的是,GROUPBY处理大数据集(数万行以上)时可能较慢,大多数情况下,ADDCOLUMNS + SUMMARIZE 组合是更好的选择:
EVALUATE
ADDCOLUMNS (
SUMMARIZE ( Sales, 'Date'[Calendar Year], 'Date'[Month], 'Date'[Month Number] ),
"Amt", AVERAGEX ( RELATEDTABLE ( Sales ), Sales[Quantity] * Sales[Net Price] )
)
ORDER BY
'Date'[Calendar Year],
'Date'[Month Number]
在前面的查询中,值得注意的是 SUMMARIZE 的结果是一个包含日期表中的列的表。因此,当 AVERAGEX 稍后对 RELATEDTABLE 的结果进行迭代时,RELATEDTABLE 返回的表是 ADDCOLUMNS 当前对 SUMMARIZE 结果进行迭代的年度和月份的表。请记住,保留数据沿袭;因此,SUMMARIZE 的结果是一个表及其数据沿袭。
3.7.2 GROUPBY 的独特优势
GROUPBY 函数可以对在查询中动态计算的临时列进行分组,而 SUMMARIZE 函数则无法做到这一点:
EVALUATE
VAR AvgCustomerSales =
AVERAGEX ( Customer, [Sales Amount] )
VAR ClassifiedCustomers =
ADDCOLUMNS (
VALUES ( Customer[Customer Code] ),
"Customer Category", IF ( [Sales Amount] >= AvgCustomerSales, "Above Average", "Below Average" )
)
VAR GroupedResult =
GROUPBY (
ClassifiedCustomers,
[Customer Category],
"Number of Customers", SUMX ( CURRENTGROUP (), 1 )
)
RETURN
GroupedResult
ORDER BY [Customer Category]
总结:一般来说,ADDCOLUMNS + SUMMARIZE 组合是更好的选择。当需要按查询中添加的列分组时,可以使用 GROUPBY 。但要注意,用于分组的列应该具有较小的基数,否则会面临性能和内存消耗问题。
3.8 NATURALINNERJOIN 和 NATURALLEFTOUTERJOIN
DAX 会自动使用数据模型中定义的关系来运行查询,但有时需要连接两个没有直接关系的表,这一点可以通过定义变量和计算表来实现。比如计算每个类别的平均销售额,并根据销售额与平均值的关系(低于、接近、高于)对类别进行分类。
使用 SWITCH 函数可以轻松实现分类,但如果需要对结果进行排序,就需要额外计算类别描述和排序顺序,这会导致代码冗余。可以通过创建一个带有临时关系的临时表,只计算一个值,然后从临时表中检索描述,从而避免重复计算。
NATURALINNERJOIN
和 NATURALLEFTOUTERJOIN
用于连接两个表,它们基于同名列进行连接。
NATURALINNERJOIN
:执行内连接,只返回匹配的行NATURALLEFTOUTERJOIN
:执行左外连接,保留左表所有行
EVALUATE
VAR AvgSales = -- 计算所有品牌产品的平均销售额
AVERAGEX ( VALUES ( 'Product'[Brand] ), [Sales Amount] )
VAR LowerBoundary = AvgSales * 0.8 -- 定义分类边界
VAR UpperBoundary = AvgSales * 1.2
VAR Categories = -- 创建分类描述表,表中有两列:Cat Sort(用于排序的整数值)和 Category(分类描述)。
DATATABLE (
"Cat Sort", INTEGER,
"Category", STRING,
{
{ 0, "Below Average" },
{ 1, "Around Average" },
{ 2, "Above Average" }
}
)
VAR BrandsClassified = -- 对品牌进行分类,添加分类值
ADDCOLUMNS (
VALUES ( 'Product'[Brand] ),
"Sales Amt", [Sales Amount],
"Cat Sort",
SWITCH (
TRUE (),
[Sales Amount] <= LowerBoundary, 0,
[Sales Amount] >= UpperBoundary, 2,
1
)
)
VAR JoinedResult = -- 根据Cat Sort列,将分类表和品牌表连接起来
NATURALINNERJOIN ( Categories, BrandsClassified )
RETURN
JoinedResult
ORDER BY
[Cat Sort],
'Product'[Brand]
如果表间存在关系,会优先使用关系
-- 内连接(只返回匹配行)
EVALUATE NATURALINNERJOIN(Sales, Product)
-- 左外连接(保留左表所有行)
EVALUATE NATURALLEFTOUTERJOIN(Product, Sales)
无关系时要求列有相同数据沿袭,否则无法连接。可使用TREATAS函数修改数据沿袭,详见《From SQL to DAX: Joining Tables》。
// 错误示例:因数据沿袭不匹配导致连接失败,AI生成
EVALUATE
VAR Table1 = VALUES(Customer[CustomerKey])
VAR Table2 =
SELECTCOLUMNS(
FILTER(Sales, Sales[Amount] > 1000),
"CustomerKey", Sales[CustomerKey]
)
// 以下连接会失败,因为Table2的CustomerKey沿袭自Sales表
// RETURN NATURALINNERJOIN(Table1, Table2)
// 正确解决方案:使用TREATAS
RETURN NATURALINNERJOIN(
Table1,
TREATAS(Table2, Customer[CustomerKey])
)
3.9 SUBSTITUTEWITHINDEX(略)
SUBSTITUTEWITHINDEX
用于将矩阵的列名转换为对应的索引值,其行为非常复杂,不是常用的查询函数,一般是使用它来正确显示矩阵。
3.10 SAMPLE
SAMPLE 是 DAX 中用于从表中返回代表性样本行的函数,它能够按照指定的排序规则均匀地从数据集中选取样本,其语法为如下。其中,排序方向(可选):ASC/DESC,默认为 ASC(升序)。
SAMPLE(<样本行数>, <表名>, <排序列>[, <排序方向>])
工作原理:首先按照指定的列对输入表进行排序;然后从排序后的表中均匀选取样本,包括第一行(最小值)、最后一行(最大值)、中间均匀分布的 (N-2) 行。
示例:按产品名称对输入表排序之后,采样10个产品:
EVALUATE
SAMPLE (
10,
ADDCOLUMNS (
VALUES ( 'Product'[Product Name] ),
"Sales", [Sales Amount]
),
'Product'[Product Name]
)
ORDER BY 'Product'[Product Name]
使用场景:
- 数据分析:替代全表扫描进行初步分析,快速了解数据分布情况
- 报表开发:创建轻量级预览报表
- 数据验证:检查大型数据集的代表性记录
四、 Auto-Exists
4.1 对同一个表的列分组,启用Auto-Exists
Auto-Exists 是一种自动筛选机制,当查询按来自同一表的列分组时会被触发。它的核心作用是仅返回已存在的值组合,而不是生成所有可能的交叉联接组合。这是一种隐式的过滤行为,防止返回没有实际业务意义的组合。
SUMMARIZECOLUMNS 常用于生成数据的摘要表。当使用 SUMMARIZECOLUMNS 对来自同一表的列进行分组时,Auto-Exists 机制会起作用。例如,以下表达式,结果会是类别和子类别的有效组合,而不是完全交叉联接。
EVALUATE
SUMMARIZECOLUMNS ( 'Product'[Category], 'Product'[Subcategory] )
ORDER BY
'Product'[Category],
'Product'[Subcategory]
当在 SUMMARIZECOLUMNS 中包含聚合表达式时,如果聚合表达式的结果为空,SUMMARIZECOLUMNS 会删除这些空白行,即使聚合表达式的结果为 0也一样:
DEFINE
MEASURE Sales[Sales Amount] =
SUMX ( Sales, Sales[Quantity] * Sales[Net Price] )
EVALUATE
SUMMARIZECOLUMNS (
'Product'[Category],
'Product'[Subcategory],
"Sales", [Sales Amount] + 0
)
ORDER BY
'Product'[Category],
'Product'[Subcategory]
4.2 不同表的列分组,生成所有交叉连接
如果 SUMMARIZECOLUMNS 中使用的列来自不同的表,结果会是两个表的完全交叉联接,即使某些年份和类别之间并没有实际的关联数据:
EVALUATE
SUMMARIZECOLUMNS ( 'Product'[Category], 'Date'[Calendar Year] )
ORDER BY
'Product'[Category],
'Date'[Calendar Year]
如果聚合表达式的结果为空,SUMMARIZECOLUMNS 会删除这些列。但如果聚合表达式的结果为 0 而不是空白,SUMMARIZECOLUMNS 会保留这些行。
DEFINE
MEASURE Sales[Sales Amount] =
SUMX ( Sales, Sales[Quantity] * Sales[Net Price] )
EVALUATE
SUMMARIZECOLUMNS (
'Product'[Category],
'Date'[Calendar Year],
"Sales", [Sales Amount] + 0
)
ORDER BY
'Product'[Category],
'Date'[Calendar Year]
计算表达式改为[Sales Amount] + 0
,本来为空的结果变成0之后,被保留:
4.3 ADDMISSINGITEMS不改变AUTO EXISTS逻辑
ADDMISSINGITEMS专门用于添加由于 SUMMARIZECOLUMNS 的空结果而被删除的行,但它不会为同一表中因 AUTO EXISTS 逻辑而被删除的行添加回缺失项。比如以下查询用于计算销售金额并按产品类别和子类别分组,SUMMARIZECOLUMNS 因为AUTO EXISTS 会自动过滤掉没有销售数据的组合:
DEFINE
MEASURE Sales[Sales Amount] =
SUMX ( Sales, Sales[Quantity] * Sales[Net Price] )
EVALUATE
SUMMARIZECOLUMNS (
'Product'[Category],
'Product'[Subcategory],
"Sales", [Sales Amount] + 0
)
ORDER BY
'Product'[Category],
'Product'[Subcategory]
如果尝试使用ADDMISSINGITEMS 添加回这些因空结果被删除的行是无效的,因为这些行是因为AUTO EXISTS 逻辑而被删除的,即ADDMISSINGITEMS无法改变AUTO EXISTS 逻辑,所以以下查询返回相同的结果:
DEFINE
MEASURE Sales[Sales Amount] =
SUMX ( Sales, Sales[Quantity] * Sales[Net Price] )
EVALUATE
ADDMISSINGITEMS (
'Product'[Category],
'Product'[Subcategory],
SUMMARIZECOLUMNS (
'Product'[Category],
'Product'[Subcategory],
"Sales", [Sales Amount] + 0
),
'Product'[Category],
'Product'[Subcategory]
)
ORDER BY
'Product'[Category],
'Product'[Subcategory]
4.4 SUMMARIZE 行为
SUMMARIZE需要一个基础表作为"桥梁"来连接不同表的列,并返回返回那些在基础表中实际存在的组合。比如,以下查询只返回在 Sales 表(基础表))中有销售记录的组合:
EVALUATE
SUMMARIZE (
Sales, -- 使用 Sales 表作为桥梁连接 Product 和 Date 表
'Product'[Category], -- 来自 Product 表
'Date'[Calendar Year] -- 来自 Date 表
)
虽然结果可能与 SUMMARIZECOLUMNS相同,但实现机制不同。SUMMARIZECOLUMNS 不依赖特定表作为桥梁,而是直接考虑模型中的关系。
4.5 Power BI 中的 Auto-Exists 行为
当用户仅拖拽维度字段(如年份和类别)到报表而不添加任何度量值时,Power BI 会自动在生成的 DAX 查询中添加一个隐藏计算:COUNTROWS('Sales')
,只显示在 Sales 表中有实际数据记录的维度组合。这不是标准的 DAX Auto-exists 逻辑,而是 Power BI 添加的额外业务规则,目的是避免显示大量无实际数据的空组合。而一旦报表中添加了任何显式度量值时,Power BI 停止这种特殊行为,转而直接使用用户定义的度量值作为过滤条件。
EVALUATE
TOPN (
501,
SELECTCOLUMNS (
KEEPFILTERS (
FILTER (
KEEPFILTERS (
SUMMARIZECOLUMNS (
'Date'[Calendar Year],
'Product'[Category],
"CountRowsSales", CALCULATE ( COUNTROWS ( 'Sales' ) )
)
),
OR (
NOT ( ISBLANK ( 'Date'[Calendar Year] ) ),
NOT ( ISBLANK ( 'Product'[Category] ) )
)
)
),
"'Date'[Calendar Year]", 'Date'[Calendar Year],
"'Product'[Category]", 'Product'[Category]
),
'Date'[Calendar Year], 1,
'Product'[Category], 1
)
使用 SUMMARIZECOLUMNS 但添加了隐藏的计数度量,通过 FILTER 和 ISBLANK 检查确保只返回有数据的组合,使用 TOPN 限制返回行数(501行)
了解这种隐式行为可以解释为什么简单报表可能显示与预期不同的结果。使用 Performance Analyzer 可查看 Power BI 实际生成的 DAX 查询。如果需要显示所有可能的组合(即使没有数据),需要显式添加度量或使用特殊技术。