简介:《DAX权威指南》第二版英文原版PDF,由Marco Russo与Alberto Ferrari合著、微软授权Pearson出版,面向数据分析师、商业智能开发者及使用Power BI、SQL Server Analysis Services与Excel构建分析方案的技术人员,帮助系统掌握DAX数据建模与计算语言。全书从DAX基础语法、表函数与求值上下文讲起,深入CALCULATE、SUMX、FILTER等核心函数、表间关系与多对多建模、上下文转换等高级特性,并覆盖性能优化、最佳实践与真实案例研究,兼顾入门理解与进阶实战。资源包共1个PDF文件,大小约13.03MB,内容完整、便于本地检索与离线阅读。目前已有827人学习下载,适合希望从函数用法走向模型设计与查询优化的读者按章节精读,也可作为日常DAX开发中的案头参考。
1. 一份把 DAX 讲透的英文原版:为什么它值得放在手边
如果你写过CALCULATE,却说不清它到底改了哪一层筛选上下文;如果你用SUMX算出来的数对不上,只能靠反复试错去凑;如果你打开性能分析器看到一堆CallbackDataID就头皮发麻——那这份《DAX 权威指南》第二版英文原版 PDF,基本就是给你准备的。它不是函数字典,而是一本把 DAX 求值机制从底层拆开讲的书:筛选上下文、行上下文、上下文转换、计算组、DAX 引擎、VertiPaq 存储、查询计划,一条线拉到底。全书 20 章、700 多页,覆盖 Power BI、SQL Server Analysis Services 表格模型和 Excel 三套宿主环境。适合已经能写度量值、但一遇到"结果不对/慢得离谱"就卡壳的从业者,也适合想从"会写"跨到"知道为什么这么写"的建模人员。英文原版的好处是术语和官方文档完全对齐,查资料时不用做二次翻译。
2. 先立住求值模型:上下文、CALCULATE 与上下文转换
DAX 的坑,九成出在"这段表达式在什么上下文里求值"这个问题上。这本书前五章几乎都在围绕这一件事反复碾压,而这也正是它区别于普通教程的地方——先把模型讲清楚,再谈函数怎么用。
2.1 两种上下文:筛选上下文与行上下文
筛选上下文(filter context)是度量值求值时当前生效的一组筛选条件,来自切片器、视觉对象的分组、矩阵的行列标题,以及CALCULATE显式加上的筛选器。行上下文(row context)则出现在计算列和迭代函数内部,表示"当前正在处理哪一行"。
关键区别在于:行上下文不会自动筛选模型。在计算列里写SUM(Sales[Amount]),拿到的是整张表的总和,不是当前行的值——因为行上下文只提供"当前行"这个概念,不提供筛选。这一点书里用了一整节的对照实验来讲,我建议照着做一遍:
-- 计算列:行上下文存在,但不产生筛选 Total All Sales = SUM ( Sales[Amount] ) -- 想要当前行的值,直接引用列即可 Line Amount = Sales[Quantity] * Sales[UnitPrice]第一行返回的是全表合计,第二行才是逐行计算。很多新手把这两者混为一谈,结果在计算列里写聚合函数,得到一列一模一样的数字,还以为是软件出问题。书里对此的判断很直接:计算列里出现聚合函数,先怀疑是不是漏了上下文转换。
2.2 CALCULATE 到底做了什么
CALCULATE是 DAX 里唯一能修改筛选上下文的函数,它的行为可以拆成三步:先复制当前筛选上下文,再用参数里的筛选器覆盖或叠加,最后在新的上下文里求值第一个参数。书里第 5 章把这三步拆得很细,尤其是"覆盖"和"叠加"的区别——直接写Sales[Color] = "Red"是覆盖该列上已有的筛选,而KEEPFILTERS包裹后变成叠加(取交集)。
Red Sales = CALCULATE ( SUM ( Sales[Amount] ), Sales[Color] = "Red" -- 覆盖 Color 列上原有筛选 ) Red Sales Keep = CALCULATE ( SUM ( Sales[Amount] ), KEEPFILTERS ( Sales[Color] = "Red" ) -- 与原有筛选取交集 )参数说明:第一个参数必须是返回标量的表达式;后续参数可以是布尔表达式、表表达式,或USERELATIONSHIP、CROSSFILTER这类关系修饰符。逻辑上,CALCULATE会先做上下文转换(见下一节),再应用筛选器,最后求值。顺序搞反,百分比类度量值就会算错——书里用"占总额比"这个经典例子演示了ALL与ALLSELECTED的差异,分母选错,整张报表的占比全歪。
2.3 上下文转换:行上下文如何变成筛选上下文
上下文转换是 DAX 最反直觉的机制:在行上下文里调用CALCULATE(或任何带CALCULATE语义的度量值引用),DAX 会把当前行的每一列值转成等价的筛选条件,作用到模型上。这就是为什么在迭代函数里引用度量值能拿到"当前行对应"的结果。
-- 逐行计算每个产品的销售额占该品类总额的比例 Product Share = SUMX ( VALUES ( Product[ProductKey] ), DIVIDE ( [Total Sales], -- 度量值,触发上下文转换 CALCULATE ( [Total Sales], ALL ( Product ) ) -- 清空产品维度筛选 ) )逻辑说明:SUMX在VALUES(Product[ProductKey])上建立行上下文,每次迭代时[Total Sales]作为度量值被调用,触发上下文转换,把当前产品键转成筛选条件;分母用ALL(Product)清掉产品维度上的所有筛选,得到品类总额。参数上要注意ALL的范围——只清Product表还是清整张事实表,结果完全不同。书里第 7 章专门讲迭代器与CALCULATE的组合,核心结论是:迭代器的基数(cardinality)直接决定性能,能在粒度更细的表上迭代就别在宽表上迭代。
2.4 变量:让复杂表达式可读、可复用
第 6 章讲VAR,看似简单,实则有两个容易翻车的点:变量是常量,定义后不会随上下文变化;变量的作用域只在定义它的表达式内。书里强调"惰性求值"——变量只在被引用时才计算,且只算一次。
Sales YoY % = VAR CurrentSales = [Total Sales] VAR PriorSales = CALCULATE ( [Total Sales], SAMEPERIODLASTYEAR ( 'Date'[Date] ) ) RETURN DIVIDE ( CurrentSales - PriorSales, PriorSales )这里CurrentSales和PriorSales各算一次,DIVIDE里复用不会重复求值。参数上注意SAMEPERIODLASTYEAR依赖标记为日期表的连续日期列,日期表不连续或没标记,这个函数会直接报错。我一般会在模型里先确认日期表被标记,再写时间智能,省得后面排查半天。
3. 把计算组和时间智能落到模型里:从写法到结构
求值模型立住之后,这本书的重心转向"怎么组织计算"。第 8 章时间智能、第 9 章计算组、第 10 章筛选上下文操作,是实际项目里复用率最高的三块。它们的共同点是:写法不难,难在模型结构要配合。
3.1 时间智能的前提:一张合格的日期表
时间智能函数(TOTALYTD、SAMEPERIODLASTYEAR、DATEADD等)全部依赖一张标记为"日期表"的连续日期维度,且与事实表通过日期列建立一对多关系。书里反复强调:日期表必须覆盖事实表的全部日期范围,不能有断档,否则年初累计、同比这类计算会在边界上出错。
Sales YTD = TOTALYTD ( [Total Sales], 'Date'[Date] ) Sales PY = CALCULATE ( [Total Sales], SAMEPERIODLASTYEAR ( 'Date'[Date] ) )参数说明:TOTALYTD第二个参数是日期列,第三个可选参数是财年结束日(如"06-30"),做非自然年财年时必须传,否则累计起点会错。SAMEPERIODLASTYEAR返回的是去年同期的一组日期,本质是DATEADD(..., -1, YEAR)的语法糖。常见误用是把事实表里的日期列直接传给这些函数——必须用日期维度表的列,否则筛选传播路径不对,结果会莫名其妙地偏。
3.2 计算组:把"同比、环比、累计"从度量值里抽出来
第 9 章的计算组是第二版相对第一版最重要的增补之一。传统做法是给每个基础度量值各写一套同比、环比、YTD,度量值数量爆炸;计算组把这些"计算行为"抽成独立的一列,通过SELECTEDMEASURE()动态作用到任意度量值上。
-- 计算项:同比 VAR CurrentValue = SELECTEDMEASURE () VAR PriorValue = CALCULATE ( SELECTEDMEASURE (), SAMEPERIODLASTYEAR ( 'Date'[Date] ) ) RETURN DIVIDE ( CurrentValue - PriorValue, PriorValue )逻辑说明:SELECTEDMEASURE()返回当前被计算组作用的基础度量值,计算项本身不绑定具体度量值,所以一套同比逻辑可以服务所有度量值。参数上要注意计算组的优先级(precedence)——多个计算组同时作用时,优先级决定应用顺序,设错会导致"先算占比再算同比"还是"先同比再占比"的差异,结果完全不同。书里给了优先级冲突的排查方法,我一般会把时间类计算组设高优先级,占比类设低,避免嵌套顺序混乱。
3.3 筛选上下文的精细操作:ALLSELECTED 与 KEEPFILTERS
第 10 章处理的是"报表内占比"这类需求。ALLSELECTED恢复的是视觉对象外部(切片器等)的筛选,忽略视觉对象内部的分组;ALL则清掉一切。两者在占比分母上的差异,是报表数字对不上的高频原因。
Share of Selected = DIVIDE ( [Total Sales], CALCULATE ( [Total Sales], ALLSELECTED ( Product ) ) )这段在矩阵里按产品分组时,分母是"当前切片器选中的产品总额",而不是全部产品总额。如果把ALLSELECTED换成ALL,分母变成全量,占比立刻偏小。书里用一张对照表列出了ALL、ALLSELECTED、ALLEXCEPT、REMOVEFILTERS在不同场景下的行为差异,建议直接抄下来贴在显示器边上。
4. 性能这条线:DAX 引擎、VertiPaq 与查询计划
前几章解决"算得对",第 17 到 20 章解决"算得快"。这部分是全书技术密度最高的地方,也是很多读者买这本书的真正原因——网上讲 DAX 语法的资料一大把,讲清楚存储引擎和查询计划的极少。
4.1 两个引擎:公式引擎与存储引擎
DAX 查询由公式引擎(Formula Engine,FE)和存储引擎(Storage Engine,SE)协作完成。FE 负责解析表达式、做上下文转换、处理迭代逻辑;SE 负责从 VertiPaq 列式存储里取数、做简单聚合。性能问题的通用判断是:SE 查询多而快通常没问题,FE 占用高、SE 查询少而慢,往往是表达式写法导致 FE 做了大量逐行运算。
书里第 17 章把两个引擎的分工讲得很清楚:SE 只认简单的列筛选和聚合,复杂逻辑一律回 FE。所以优化方向通常是"把能下推给 SE 的运算下推",比如用SUMX替代逐行IF嵌套,用关系筛选替代FILTER全表扫描。
4.2 VertiPaq 存储:为什么列的顺序和基数很重要
第 18 章讲 VertiPaq 的压缩机制:列式存储、字典编码、值编码、游程编码(RLE)。核心结论是——列的基数(distinct 值数量)越低,压缩率越高,扫描越快。这直接影响建模决策:高基数的列(如订单号、时间戳)尽量别放进维度表做分组;能拆成低基数属性的,别合并成一个长文本列。
| 列类型 | 典型基数 | 压缩效果 | 建模建议 |
|---|---|---|---|
| 日期键(整数) | 数千 | 好 | 保留,用于关系 |
| 产品名称 | 数百到数千 | 中 | 可作维度属性 |
| 订单号 | 百万级 | 差 | 避免用于分组 |
| 自由文本备注 | 极高 | 极差 | 不进入模型 |
书里还提到,计算列的基数如果很高,会显著拖慢刷新和查询,能用度量值表达的尽量别落成计算列。
4.3 读查询计划:从 CallbackDataID 找性能瓶颈
第 19 章教你怎么看 DAX Studio 里的查询计划。最该警惕的信号是CallbackDataID——它意味着 SE 无法独立完成运算,必须回调 FE 逐行处理,通常出现在FILTER里引用了度量值、或迭代器内部做了复杂判断的场景。
-- 反例:FILTER 内引用度量值,容易触发 CallbackDataID Bad Measure = CALCULATE ( SUM ( Sales[Amount] ), FILTER ( Product, [Total Sales] > 1000 ) ) -- 改进:把条件改成列级筛选,尽量下推给 SE Better Measure = CALCULATE ( SUM ( Sales[Amount] ), FILTER ( ALL ( Product[ProductKey] ), [Total Sales] > 1000 ) )逻辑说明:第一版在整张Product表上迭代并逐行调用度量值,FE 负担极重;改进版把迭代范围收窄到单列,减少物化开销。参数上注意FILTER的第一个参数尽量用单列或ALL后的窄表,别直接传整张宽表。书里第 20 章给了完整的优化清单,从减少物化表、避免嵌套迭代,到用变量缓存中间结果,逐条都有对应的查询计划对照。
5. 避坑与排查:五条血泪经验
书读得再顺,落到自己的模型上照样翻车。下面这几条是我在实际项目里反复撞到的,配合书里的章节对照看,能省不少时间。
现象一:计算列里聚合函数返回全表合计。原因:行上下文不产生筛选,聚合函数看到的是整表。解决:要么直接引用列做逐行运算,要么用CALCULATE触发上下文转换,参考第 4、5 章的对照实验。
现象二:占比类度量值在矩阵里分母不对。原因:ALL和ALLSELECTED用混,分母范围超出或小于视觉对象。解决:先明确"分母要不要受切片器影响",要就ALLSELECTED,不要就ALL,并用第 10 章的对照表逐项核对。
现象三:时间智能函数报"日期列不连续"或结果偏移。原因:日期表没标记、有断档,或把事实表日期列传给了函数。解决:检查日期表标记状态、补齐连续日期、确认关系方向,参考第 8 章。
现象四:报表打开慢,查询计划里全是 CallbackDataID。原因:FILTER内引用度量值或迭代宽表,FE 逐行回调。解决:收窄FILTER迭代范围到单列,把能下推的运算改成列级筛选,参考第 19、20 章。
现象五:多个计算组叠加后结果不符合预期。原因:计算组优先级设置冲突,应用顺序与预期相反。解决:明确各计算组的职责边界,按"时间类高、占比类低"排优先级,用第 9 章的排查方法逐层验证。
提示:以上五条里,前三条属于"算错",后两条属于"算慢/算乱"。排查顺序建议先对结果、再查性能,别一上来就翻查询计划。
6. 把这本书用成工具书:我的翻阅习惯
700 多页的英文原版,通读一遍不现实,也没必要。我的用法是把它当查证手册:写度量值卡住时翻第 4、5 章对上下文;做时间智能翻第 8 章;性能出问题直接跳到第 17 到 20 章看查询计划那一节。第二版新增的计算组(第 9 章)和高级关系(第 15 章)值得单独精读,这两块是近几年表格模型里变化最大、网上资料最零散的部分。
一个具体技巧:每读完一个机制,就在自己的模型里造一个最小复现。比如学上下文转换,就建一张两列的小表和一条关系,写一个SUMX套度量值,用 DAX Studio 看查询计划里 SE 和 FE 各干了什么。书里的例子再清楚,也不如自己模型里跑一遍记得牢。英文原版还有个隐性好处——函数名、错误信息、查询计划里的术语和官方文档一字不差,遇到报错直接搜原文,命中率比搜翻译高得多。
我自己的习惯是:每次新建模型,先把日期表标记好、关系方向确认一遍,再动第一个度量值。这个动作看着琐碎,但省下的排查时间远超那几分钟——毕竟 DAX 的坑,多半不是函数写错,而是模型结构没铺好。从那以后我每次开新模型都强制走一遍这个检查,翻车次数肉眼可见地少了。希望帮到你。
本文还有配套的精品资源,点击获取