DAX扩展表(DAX的入门修炼基础)
(根据官方资料的简体笔记修改、补充) QQ:2889374742
一、数据模型基础
DAX语录:DAX的一切都是关于列表的。
我们最简单的说下DAX内部运行机制:
DAX里有三大引擎,其内部引擎运行的过程是:VertiPaq压缩引擎压缩数据-->存储引擎存储数据-->公式引擎引用存储数据计算。其中公式引擎的典型运算符包括:
(1)列表之间的联接(即列表关系);
(2)具有复杂条件的筛选、聚合以及查询。
这些公式运算符通常就是指数据模型里的列表构成(包含列表关系、列表类型等)。
一般情况下,公式引擎总是使用存储引擎返回的数据缓存或与其他公式引擎运算符定义的数据结构一起工作(比如定义变量、度量中引用度量等)。
这就是,公式引擎向存储引擎发送请求,存储引擎通过返回一个数据缓存(虚拟列表集)来响应该请求。而该数据缓存是由存储引擎创建并由公式引擎读取的临时存储区域。
所以, 打从一开始,数据缓存就没有被压缩。换句话说,它们不是VertiPaq存储列,而是内存中缓存的普通列表!也就是内存中单独的未压缩列表。简单的说,这一过程称之为“物化”,“物化”的结果不能为列表(列表筛选),或者不能为行(行筛选,也就是迭代)。
“物化”的结果不需要持久化(不会保留)。通常,每当“物化”一个结果时,就会引用一个数据缓存,这在复杂查询中的大多数情况下都是如此。
虽然,存储引擎查询的执行可以使用不同的线程,并在多个内核之间并行执行。并且当所有线程完成时,VertiPaq才会将这些结果合并到一个最后的数据缓存中。
然而,DAX公式引擎只在每个线程中使用数据缓存。这才是我们关注的重点。
因此,通常我们所说的列表,是指组成数据模型里的单个列表(单个线程运行),以及单个列表再由关系(例如一对多)连接构成关系列表集:即数据模型(不管是物理的、动态的、虚拟的、活动的、非活动的等等),你可以将数据模型理解为连接在一起的一个或多个单列表扩展的表(数据模型里某个物理“表”,也是多个单列表按不同类型组合在一起,其实还是单个的列表集或单个)。
例如:我们写一个求和公式:
SUM=Sales[数量]
如果Sales表是单独存在的一个表,那么该公式仅针对该Sales表计算;如果Sales与其他表存在关系,那么,公式应针对由关系连接成的扩展的Sales表(包含其他列表)计算。
关系是本地物理数据模型的一部分,除非你删除它,否则它总是存在(更新数据时连同关系一起会更新)。通常计算采用的列表集是通过原数据模型里已有的列表的再次组合、“视图”、虚拟出来(DAX里有个专门的词:物化)。这个列表集是随着当前定义的计算而变化的。也就是说,不同的DAX计算式代表不同的计算列表集。但是,最后还是会回到源数据模型的某个列表中计算。
由于DAX引擎具有以列式存储并执行列式运行的优化特性,所以,所谓数据模型---列表集就是单个列表的集合,并没有表、行的概念!
为了阐明DAX计算使用的列表集是一个关系列表构成的列表数据模型,下面借助准官方的文献,做如下简单记录。其实许多DAX问题,都涉及这个概念的理解。
首先说明:数据模型里其实是没有扩展表一说的,只有单个或多个关系列表的集合,即数据模型(列表模型)。但是,我们能以这种方式来理解它。这就是关于DAX的一个最大的特点:你可以以这样的、那样的、适合于自己理解的方式去了解它,但这并不代表真正的事实结论就是这样,目前为止,DAX内部还属于一个专门领域。
所以,采用表的扩展或扩展表的说法,只是为了说明DAX的关系列表集的原理与DAX的概念。列表的变化不单单是列表的扩展,说扩展表的概念只是关系列表的一种也行。当然,它对于帮助我们理解DAX是非常有用的。记住,我们只是在陈述一个概念。
二、 理解扩展表
DAX的语义大都基于扩展表的概念,可看成是DAX的核心概念之一。
理解扩展表的工作方式至关重要。本文提供了扩展表的理论基础,以及读写DAX代码时有用的基本概念。
为了理解DAX的行为方式,列表的扩展(扩展表)是从DAX中引入的一个非常独特的概念,其实际是列表关系的概念。虽然一开始你会觉得很奇怪,但慢慢就会习惯。
本文的目的不是用来解决实际问题。而是希望读者从书写DAX公式时开始并一直以扩展表的方式来进行思考。DAX提供了许多高级特性,一旦具备了扩展表的良好思维概念,将能够进一步了解到DAX的特性。
扩展表除了包含基表所在的所有列之外,还包含通过多对一或一对一关系、以一个或多个级联方式链接到该基表的其他所有列表。
请考虑以下关系图:
它包含下图中的三个列表集,每个列表集都具有自己的扩展表:
(1)扩展的Product表:包含Product[Product] 以及TopSelloduct[Product];
(2)扩展的TopSellerProduct表:包含TopSellerProduct[Product]和Product[Product];
(3)扩展的Sales表:包含了这三个表的所有列。
Product表和TopSellerProduct表的扩展模型是相同的。事实上,一对一的关系被称为同一性关系。任何情况下,可以认为这两个扩展的表是相同的(一个表的基列是另一个表的关系列)。
提示:如果组成该数据模型的列表是相同的,那么,它们是同一表,这与顺序无关。
扩展表是通过将两个表的列加入到一个更大的表中,并使用一个完整的外部联接来创建的(其实是另一个新列表模型)。比如普通的多对一关系通常使用的左外连接。
所谓“扩展”总是要满足两个条件:
(1)总是发生在关系的一端;
(2)总是一段的关系,而不能级联(想像成围住一个事实表的星型模式)。
显然,扩展表与双向筛选并没有任何关系。如果在某一段关系中激活双向交叉过滤器,那么,该段上的关系就不再依赖于列表的扩展关系。反之,这时候引擎在代码中执行特定的筛选条件,以便在关系的两端同时应用筛选器。
因此,在前面的数据模型中,即便对Sale表和Product表之间启用双向交叉筛选关系,也不会将Sale表的列添加到扩展的Produc表中(遵循关系只能从一端到多端的原则)。
每个扩展表都包含基列(原有的列)和关系列。基列是最初出现在数据模型中的列,而关系列都是其他关系表的列,是通过扩展表而添加到原始列表中的列。
DAX内部的VertiPaq引擎实际上始终只针对存储于本地的列表。因为扩展表的扩展性质并不是物理性的。这就是为什么一开始我们就着重陈述的:扩展表只是一个帮助我们理解的概念而已。尽管如此,整个DAX的语义却是基于扩展表的理论概念。
三、 列表关系传递
当你学习CALCULATE函数时,你会发现在一段关系的一端应用一个过滤器会影响到关系的多端列表。事实上,如果写这个度量表:
AppleSales := CALCULATE (
SUM ( Sales[Amount] ),
Product[Product] = "Apple")
应用于Product [Product] 的筛选器遵循从Product表与Sales表之间的一对多关系,因此筛选器同时会筛选到Sales表。对相同的筛选器关系传递的更好描述是:使用扩展表的概念。当过滤Product [Product]时,所有包含该列的列表——无论是本身的原列表还是关系列表---实际上都被过滤了。因此,Sales表是由Product [Product]过滤的,因为扩展后的Sales表的模型中包含Product [Product](实际是构成了一个新的关系列表集--扩展表)。
1、 RELATED, RELATEDTABLE与扩展表
扩展表包含列表关系的概念。事实上,每当列表模型被扩展时,就总会使用只有一级的关系(而不是级联),一旦开始考虑扩展表,就不再需要考虑关系了。
注:列表关系一旦建立,就不需要再考虑它,除非必要,请使用物理关系。只有基于某些需要,才会用到虚拟关系或非活动关系以及双向筛选关系。
(1) RELATED函数。
初学习DAX时,通常会认为RELATED可以访问关系列表。更准确的表述应该是:使用RELATED能访问扩展表的相关列。作为一个例子,考虑以下模型:
销售、产品、日期表以及客户表之间存在直接的关系。用更准确的DAX语言表述,应该是:扩展表模型的销售表包括产品表、日期表和客户表的所有列。因此,Product [Product] 属于扩展的销售表模型,当然扩展的“销售表”包括这三个表扩展后的整个模型。因此,可以使用RELATED函数在该扩展的销售表模型中新增两个列,如下:
Sales[TopSellerProduct] = RELATED ( TopSellerProduct[Product] )
Sales[Month] = RELATED ( 'Date'[Month] )
结果:
因为并非所有的产品都是Top 畅销产品,这就是为什么在TopSellerProduct 列中Apple产品的销售有空白值的原因。如果有 SQL 基础,或者习惯了关系型数据库,则可能会认为RELATED应该遵循了关系。因此, 若要计算Month--月份列, 会认为引擎遵循Sales 和Date--销售和日期之间的关系, 并通过在 date 表上执行查找来获取月份的值。
可是,DAX会不一样。Date[Month]属于扩展模型的“销售表”里的一个列,实际上,已经存在RELATED(Date[Month])列,因为“销售表”已扩展为包括使用关系的日期。可以理解为只是使用RELATED将该列挪动了一下位置(从所在的时期表位置挪动到扩展的Sales表所在的位置,实际还是同一个Sales扩展表)。
(2)RELATED需要行筛选处于活动状态。
如果移除CALCULATE的行筛选, 则RELATED将不再起作用。例如, 下面的计算列会引发错误, 因为CALCULATE移除了执行行值转换的行筛选:
Sales[Wrong] = CALCULATE ( RELATED ( TopSellerProduct[Product] ) )
注:实际上RELATED是行筛选行为操作,不能作为CALCULATE的第一参数(值列表),表述为“移除”,是因为添加CALCULATE后,强制性显式将 RELATED ( )转换为列表筛选,RELATED是行筛选行为被破坏(被删除)而不同正常工作。
2、VAR变量与 扩展表
扩展表的一个重要规则是:只在被定义为表时发生。例如, 请看以下查询:
这两个VAR变量使用两种不同的关系来存储销售表。
(1)SalesA 使用默认关系;
(2)SalesB 使用与Sales[DueDate]的关系而不是Sales[Date]。
最后一个 ADDCOLUMNS 迭代 SalesB 并返回RELATED Date[Month]。结果会是什么?是Sales[Date]列还是Sales[DueDate]列的月份内容?
如果你还在考虑关系, 这就有些麻烦了。实际上, 当 ADDCOLUMNS 被执行时, 活动关系使用的是Sales[Date]列的关系, 你会认为月份是该日期的月份。对吗?错!
正确的推理应该是: RELATED访问的是扩展的Sales表的关系列。SalesB 已经包含扩展的Sales--销售表,当活动关系与Sales[DueDate]连接关系时, 就会发生扩展。因此, SalesB 中包含的Date[Month]与Sales[DueDate]列为关系列,而不是Sales[Date]。显然, 如果改为遍历 SalesA,结果也会不同。因为扩展表的列表内容已发生改变。
3、在计算列中使用RELATED
如果需要使用计算列中的非活动关系来获取关系列,则会遇到麻烦。事实上, 正如前面所演示的, 可以使用 USERELATIONSHIP来激活非活动关系。但是, 需要使用CALCULATE,而一旦使用CALCULATE,反过来又会破坏行筛选(将被强制转换为列表筛选)。因此, 计算列的以下定义将生成错误:
通过引入CALCULATE,控制了RELATED参数的使用。如果将 USERELATIONSHIP 定义的关系指定为RELATED的一部分,这将是一个很好的选项, 但是,到目前为止,DAX中还没有这种语法。因为,RELATED始终使用活动关系,无法将非活动关系指定为其语法的一部分。换句话说,RELATED需要的是显式的活动关系,而不接受非活动关系。
一个可能的解决方案是:在 USERELATIONSHIP的非活动关系更改为活动关系之后才引入行筛选,那么,相当于扩展表在两种不同的活动关系集上使用。如下Sales[DueMonth]度量提供了正确的结果, 尽管它不是很有效:
这个代码有点复杂,但对于理解扩展表的概念有帮助,同时,也说明DAX公式并不是越长越难理解,也不是越长就越逻辑条件复杂,而是列表的关系与条件越复杂就越难。你可以拿这个公式作为检测别人的DAX理解程度......。
以下是简单的解释:
CALCULATE引入的列表计算通过 USERELATIONSHIP 激活非活动关系。这时候,为了绕过由于CALCULATE引入的列表转换传递到Date表的筛选器,需要ALL(Date)处理,因为实际上,这时候列表转换仍然在原始关系下执行,需要使ALL(Date)来清除来自Date表的筛选。按编者关于ALL的理解,其实是ALL()删除了关系(这里Date表的所有关系。所以,可以由内部重新引入新的行筛选,即可以使用RELATED从扩展的销售表中检索日期表。 RELATED访问的正是扩展的Sales表的关系列(这里是[Month]列),因为很显然,这种扩展发生在需要活动关系的时候。
看起来很复杂对吧?应该是……。但在实际运用中,建议永远不要编写这样的代码。这里只是为了说明扩展表概念问题而采用的一个实验。如果真的需要这样的逻辑代码,就像有些朋友那样,有一种研究精神,那么使用更简单的模型就好了,因为它根本就不需要使用关系:
Sales[DueMonth] =
LOOKUPVALUE ( 'Date'[Month],
'Date'[Date],
Sales[DueDate])
这不正是你想要的?而且更快、更安全、更容易理解。当然,使用RELATED版本以便于掌握一下非活动关系也很有必要。但只需要理解它,然后不要使用它!奇怪的公式总是来自不按DAX听得懂的语言来进行交流所造成的。
之外, 值得讨论的还有一个问题:为什么[DueMonth]度量,此代码的下列版本不起作用:
它看起来非常类似于代码 #6的工作原理。然而这一次,公式使用的是VALUES而不是MINX。此代码不工作的原因是:
USERELATIONSHIP 只更改模型中的关系为活动关系, 以便CALCULATE中的扩展表使用新激活的关系而不是默认的物理关系。USERELATIONSHIP 本身并不引入筛选器。它只会激活一段关系。因此, CALCULATE的列表转换仍然会发生在原关系列表上,即使ALL(‘Date’)会删除来自日期表的关系影响,但 USERELATIONSHIP 并不能将筛选器传递到日期表。因此, 所有日期仍然可见(并没有被筛选)。
如果需要应用筛选器 (例如使用TREATAS的虚拟列表关系而不是 USERELATIONSHIP的非活动关系)。这时,代码将工作正常, 尽管它并不依赖于扩展表:
本例中,TREATAS 使用 DueDate列的当前值来引入筛选器,并将其作为新的关系筛选器传递给Date[Date](反过来说,Date接受DueDate筛选)。
最后,值得注意的是, 在本例中, 不再需要日期表中的ALL( )列表数据,因为 TREATAS 引入的新筛选器会覆盖它(组成新的关系列表集)。
四、结论
扩展表是在 DAX 中引入的一个独特概念, 它包含了列表关系。虽然在开始的时候看起来很奇怪,但是在DAX的学习中,它是一个特别重要的概念。
引用扩展表:可以作为理解并问答 DAX 为什么有这样的行为方式?
本文的目的不是解决特定的问题,而只是陈述一个DAX中的重要概念。重要到你再不理解它,就会在DAX中停下进步的脚步! 反之,一旦理解了扩展表的概念,将能够进一步提高探索DAX的能力。