现有文章:
1、初识DAX—DAX是什么?能干什么?怎么用? 2、DAX的度量值、计算列、查询分别是什么?数据模型又是什么? 3、DAX的筛选上下文详解以及CALCULATE的初步应用 4、DAX的常用迭代函数详解 5、DAX的常用表函数之VALUES与DISTINCT 6、DAX的ALL类函数详解以及影子筛选上下文的介绍—上篇 7、DAX的ALL类函数详解以及影子筛选上下文的介绍—下篇
PowerBI/DAX的计算组功能是什么?怎么用? PowerBI/DAX的计算组之间的优先级设置
【停止更新】该专栏的知识分享已有免费的视频课程分享在B站,欢迎前往观看!
经过上篇文章的学习后,相信大家都对DAX有了一个大概的印象,但这个印象还是很模糊的,哪怕知道了很多DAX的基础函数是继承至Excel的,但大多数人应该还是不太清楚该怎么去编写DAX公式。
下面我将从数据的导入开始,一步步的介绍计算列、查询以及度量值,在介绍完这三个可以使用DAX的东西之后,就开始介绍DAX的数据模型,并展示在数据模型下的计算列和度量值的功能变化,进一步加深对计算列、度量值以及数据模型的印象。
将数据导入到数据模型的方式有很多种,下面我将以上篇文章文末后提供的案例数据来演示如何导入,其中案例文件一共有两个版本,一个是Excel版的,另一个是Access版的,Excel版的只有一个大表,Access版的则有多张子表,为了方便本文下面的讲解,建议导入多张表。具体操作如下:
1、第一种方式
2、第二种方式
3、第三种方式
4、第四种方式
5、第五种方式
导入数据的方式多种多样,在PowerBI里的导入方式也是大同小异,只要会了上面的就可以了。如果你的数据源不需要清洗,那么推荐大家采用第四种方式导入,否则采用第三种方式较好。然后,这些导入方式在细微之处略有不同,例如:通过不同方式导入的数据在数据模型管理界面上的表名有可能会有所不同,这时,想改名称需要到公式的名称管理器上修改,具体的细节请大家亲自尝试吧,这里不过多介绍。
在正式开始介绍前,先介绍一个约定俗成的书写规则。在DAX里面,表的表示方式为:‘TableName’,注意表名被单引号括起来了,然后某张表下的列的表示方式为:‘TabelName’[ColumnName],在表名后用中括号括起来的就是这张表下的列,这种写法是完全限定性写法,当然表名也是可以省略的,例如:[ColumnName],这种写法则是非限定性写法。最后,度量值的表示方式为:[MeasureName],是的,你没看错,它与列的非限定性写法一样。
然后因为列的非限定性写法与度量值的表示方式相同,在DAX表达式里很难区分,所以一个约定俗成的书写规则就是:对列的引用要采用完全限定性写法。在你书写DAX表达式时,编辑器有智能感知,能帮你自动补全,只需要按下Tab键即可,所以公式的输入不会很困难。
下面开始介绍计算列,所谓的计算列其实就是通过公式或函数对表上已有的列的数据进行处理和转换后得到的新列。这个新得到的列和表上的原有列是一样的,在功能上不存在差异。添加计算列的方式如下图:
先选择需要添加新列的表,然后单击添加列下的任意空白单元格,然后在上方的公式输入栏里输入DAX公式即可。例如,上图中间靠右边被红框框起来的列,就是这张表里添加的计算列,可以看到列名是黑色的,单元格颜色也较深。下面我给出了上图中添加的计算列用到的公式,推荐你亲自去尝试一下,注意,别选错表了,是在 ‘T3销售’ 这张表里添加的列!!
销售金额 := 'T3销售'[T3销售册数]*'T3销售'[T3销售单价] 销售年份 := YEAR('T3销售'[T3销售日期]) 销售月份 := MONTH('T3销售'[T3销售日期]) 所属子类 := LEFT('T3销售'[T3书号],2) 销售总额 := SUM('T3销售'[销售金额])可以看到,我上面的表达式都是用的基础函数,如果你只使用基础函数,且不存在嵌套,那么这跟Excel的公式几乎是没什么不同的,计算列在不涉及到行上下文嵌套和行上下文转换时就是这么的简单。而更加深入的东西会在后面讲解,讲究的就是循序渐进、系统学习,如果你觉得这些内容太过基础的话也可以去看我后面的文章(如果我有写的话)。
所谓的查询,简单来讲就是返回一个经过处理且满足你的需求的表,一般都是通过表函数来返回表,当然表函数不只是用来做查询,它在计算列、度量值等地方的地位也是不可撼动的。
那么在哪里可以编写查询呢?首先,Power Pivot不支持新建表,所以查询一般都是在PowerBI里完成的,那就是PowerBI的新建表选项,如果你想在PowerPivot里实现查询也不是不可以,只是比较麻烦,需要通过链接回表的方式进行。需要注意的是:在PowerBI里新建表只需要直接写返回表的表达式即可,而在PowerBI之外的其他地方都需要在返回表的表达式的前面加上一个EVALUATE关键字,就是声明查询的意思。
在展示例子之前,先介绍一个重要的返回表的迭代函数,那就是FILTER,它的语法为:FILTER ( Table, FilterExpression ),FILTER 迭代表Table,为每一行计值布尔表达式FilterExpression,当计值结果为 TRUE,FILTER 返回当前行;否则,就跳过当前行。
下面展示的是在Excel中通过链接回表的方式进行的查询,查询内容为销售单价大于20的销售记录,如下图所示:
在链接回表时,可以选择数据量最少的表,这样加载速度比较快,上面查询用到的DAX表达式如下:
EVALUATE FILTER('T3销售','T3销售'[T3销售单价]>20)在上面动图中可以看到,我输入表达式时是很笨拙的,因为链接回表的方式它不支持智能感知,只能手打全部表达式,而我一时间也没适应过来,所以我推荐使用DAX Studio这一款集分析、调优和导出查询为一身的DAX辅助工具,它不仅能格式化DAX表达式,还能导出数据,计算表达式运行时间等等,具体使用方法可自行搜索,下载可前往其官网,这是免费软件。
下面展示的是通过DAX Studio进行的相同查询:
可以看到,通过智能感知,你可以很快速的输入DAX表达式,但这也容易造成一个弊端,那就是离开了IDE后你就不会写代码了,智能感知和手撕代码,你选哪个?
度量值是DAX中最重要的一个东西,通过它你可以重写Excel数据透视表里的汇总规则,实现你自己的自定义规则以达到你的目的。
那么何为度量值?简单理解,度量值就是除计算列和查询之外的DAX表达式,你可能觉得这说了等于没说啊,那么详细点的话,度量值就是从基础数据表里提取数据,然后经过处理和聚合之后可以得到标量值的DAX表达式。也就是说,度量值总是返回标量值,查询总是返回表,计算列是在表上添加新列。
这时,你可能会有所疑惑:既然度量值只能返回标量值,也就是只返回一个值,那么它怎么自定义规则以达到Excel数据透视表里的效果?事实上,度量值的计算需要依赖外部计值环境,但你也可以将度量值理解为一种计算规则,就像:1+2=3,你可以将这个加法的计算规则写成度量值,这样当基础数据(1或者2)发生变化时,你不用重新去输入数据去计算,它就能够自动得到结果。这样描述起来可能有点抽象,但当你深入学习后就会发现,这样的描述还是比较准确的,在我看来,度量值即DAX!,当然以上仅仅只是我自己的理解,也不一定是正确的,希望你能抱着质疑的态度来学习,如果你发现我有错误的地方也请不要给我留面子地指出,谢谢。
我会在文章中穿插一些重要函数的简单说明,因为这些函数过于重要,和DAX的很多原理都有涉及,你不介绍这些函数就根本无法继续介绍DAX的原理,但又因为它们的重要性而很难完全解释,这需要一个很长的篇幅,因此仅作简单说明,在后续的文章中会专门讲解。
CALCULATE的语法为:CALCULATE( 表达式, [ 筛选器 1 ], [ 筛选器 2 ] … ),函数的作用为:在筛选器参数修改过的上下文中对表达式进行求值。我们举一个非常简单的例子:计算销售单价大于20的销售记录的行数,如下图:
代码如下:
单价大于20的订单数量 := CALCULATE(COUNTROWS('T3销售'),'T3销售'[T3销售单价]>20)在这里,‘T3销售’[T3销售单价]>20就是一个布尔值筛选器,CALCULATE的筛选器的结果可以是布尔值也可以是表。CALCULATE还有一种特殊用法,那就是忽略筛选器参数,这样的话,CALCULATE第一参数的表达式就是在外部上下文里计值,而且CALCULATE遇到行上下文还会将其转换成筛选上下文,具体知识以后讲解。
如果你不相信销售单价大于20的销售记录只有73行的话,可以自己手动筛选看看。
在应用度量值时需要注意度量值的两个重要事实,或者说是注意事项吧。如果你不知道这个事情的话,当你的DAX表达式涉及到行上下文转换时,你很容易就会出错。度量值的两个重要事实如下:
任何度量值都会自动套上一个CALCULATE 例如,你定义了这样的一个度量值:Measure1 = SUM(‘T3销售’[销售金额]),它等价于:Measure1 = CALCULATE( SUM(‘T3销售’[销售金额]) )CALCULATE遇到行上下文时会自动地将行上下文转换成筛选上下文经过上面的学习,你已经对计算列、查询以及度量值都有了一些理解,如果要我说它们之间的具体区别的话,我认为其实是没有任何区别的,都是在外部上下文里进行计值的DAX表达式,只是它们身处的外部上下文不同和返回的结果不同而已,逻辑是一致的。但为了方便介绍,我还是像其他资料一样分为了计算列、查询和度量值,等你深入学习后就会理解它们其实没什么区别。
然后,虽说计算列、查询和度量值是没什么区别的,没错,这从逻辑上来说是正确的。但是从内存消耗的角度来考虑的话,度量值和查询只有在其计算的时候才消耗内存,而计算列则不同了,它相当于在基础数据表上增加了列,所以是永久性消耗内存的。你定义的计算列越多,占用的内存就越多,当你的数据量非常多的时候使用计算列是非常致命的,除非你电脑的内存很大。所以,能用度量值完成的工作一般都不要使用计算列。
下面将介绍DAX的数据模型,只有拥有数据模型之后,度量值和计算列才能发挥出更强大的功能,需要注意,下面介绍到的计算列和度量值的例子都是需要数据模型的,断开模型的连接后,结果将会改变,注意是改变而不是出错!
数据模型可以说是DAX的灵魂了,没有了数据模型,那么DAX甚至还不如Excel公式。因此,想要完美地掌握DAX,那么数据模型是必须掌握的。
在这里说的模型并不是指的回归模型、分类模型这些算法模型,而是指的从现实世界抽象出来的数据模型,或者说是关系模型。例如:一个产品大类下有多个产品子类,一个产品子类下有多个产品品牌,而一个产品品牌又有多条销售记录等等。相信学过关系型数据库的人都知道,在将这些产品销售数据存储到数据库的时候需要满足一定的范式要求,以减少冗余。那么DAX里的数据模型跟数据库里的关系模型其实就是一个东西,都是描述数据间的关系的。例如,产品大类与产品子类之间是一对多的关系,产品子类与产品品牌之间也是一对多关系。在DAX里,关系总共有三种:一对多关系、多对一关系以及一对一关系,不支持多对多关系。
在Power Pivot里的关系图视图下即可看见各个数据表之间的关系,如果你是使用我提供的案例数据的话,那么在你没有建立关系时是这样的:
然后,你可以将各个表中的相同字段连接起来(鼠标左键按住拖动即可),例如:T0大类K与T1大类相连,T1子类K与T2子类相连,就可以得到一个数据模型。当然,从广义上来讲,哪怕只有一个表那也是一个数据模型。具体如下:
在数据模型中需要注意的地方有三个,如上图红框所示,分别是:在关系中位于一端的表(模型中有个1字),在关系中位于多端的表(模型中有个星号),以及中间的箭头指向。
在数据模型中,位于一端的表称为上级表或父表,位于多端的表称为下级表或子表,上级表和下级表只是在两个表之间的关系的称呼,是相对的。然后,最后一个下级表一般称为数据表或事实表,在它之上的上级表一般称为维度表。例如,在上面的模型中,'T3销售’就是数据表或事实表,在它之上的就是维度表。最后,还需要注意两个表之间的箭头指向,这个箭头说明的是关系在数据模型中的传播方向,就像一个二极管电阻一样,关系只能沿着箭头指向传播而不能反过来。这里所说的关系传播,一般指的是筛选关系的传播,例如:你在最上面的上级表里设置了一个筛选器,那么这个筛选关系就能向所有下级表传播,使得下级表的可见内容只有被筛选后的内容。反过来的话,你在下级表设置的筛选关系对上级表毫无影响【在你不理解扩展表之前这句话是正确的,理解扩展表后你根本就不再需要从关系的角度去思考】。
然后还需要注意的是,关系箭头的指向不仅仅只有单向的,它还有双向箭头,你可以双击模型中的连接线进行设置双向关系,当然这只有在PowerBI里才可以,在PowerPivot里的话就只有单向的,但你也可以在PowerPivot里使用双向关系,只需要采用筛选调节器CROSSFILTER函数进行控制就可以了。
一个好的数据模型能够让你的计算效率提高不止一倍,绝不是你所以为的将相同字段连接起来就可以的,要知道,位于关系一端的上级表是可以通过筛选关系的传播来控制下级表的,某个表的所在位置越是靠上,那么这个表所能控制的数据就越多。你可以看看下面这个数据模型,看看它与上面的模型之间有何不同:
可以看到,这仅仅是’T2书号’表的连接方向改变了一下,但是这就使得原来不能被’T0大类’、'T1子类’以及’T2书号’三张表控制的’T4图书’表变成能被控制,所以说,一个数据模型的好坏能决定你的计算效率。
在有了数据模型后,计算列就可以使用两个重要的关系函数,那就是:RELATED和RELATEDTABLE,可以说,这两个关系函数在你没有理解扩展表之前都是非常重要的,它们的语法如下:
RELATED 语法:RELATED ( <列名> ),作用:从关系的一端返回标量值RELATEDTABLE 语法:RELATEDTABLE ( <表名> ),作用:从关系的多端返回符合要求的所有记录看不懂函数说明也没关系,我们从实际例子出发,很容易就能弄懂它们的作用,要注意的是:关系在数据模型里的传播是能传递的,所以RELATED和RELATEDTABLE能根据关系的传播方向向所有下级表或上级表提取相关数据。
当你想从位于多端的下级表处获取上级表的对应列的数据时可以使用RELATED,举个例子:你想在’T3销售‘表里的每一行销售记录处获取对应的产品大类和产品子类,那么可以在’T3销售‘表里使用以下的计算列公式来添加列:
产品所属大类 := RELATED('T0大类'[T0大类K]) 产品所属子类 := RELATED('T1子类'[T1子类K])结果如图:
当你想从位于一端的上级表处获取下级表的所有相关行时可以使用RELATEDTABLE,但RELATEDTABLE获取的是表,所以需要通过聚合函数得出标量值才能用于计算列,举个例子:你想知道’T0大类’表的每一个大类在’T3销售‘表里的销售记录有多少,那么可以在’T0大类‘表里使用以下的计算列公式来添加列:
销售记录数量 := COUNTROWS( RELATEDTABLE('T3销售') )结果如图:
可以看到,每个大类的销售纪录数量都是不一样的,是先通过RELATEDTABLE获取与每一个大类相关的销售记录,然后RELATEDTABLE返回表,再通过COUNTROWS来计算行数。还是那句话,不相信的可以手动筛选来验证。
需要注意的是,RELATED和RELATEDTABLE需要在有完整的表级行上下文和存在数据模型时才能够正常工作,虽然列级表也存在行上下文,但是RELATED和RELATEDTABLE是不能工作的,可以参考下面的查询,第一个查询能正常工作,第二个查询是反面教材:
EVALUATE FILTER('T3销售',related('T1子类'[T1子类K])="11机械") EVALUATE FILTER(VALUES('T3销售'[T3书号]),related('T1子类'[T1子类K])="11机械")其中,VALUES函数将列转换成表,因为FILTER的第一参数必须为表或返回表的表达式,它的语法和作用如下:
语法作用VALUES ( <列名> )当使用列参数时,返回指定列非重复值组成的表;VALUES ( <表名> )当使用表作为参数时,返回指定表中的行(保留重复行)在这里,我会涉及到一些还没有进行讲解的DAX原理,例如:行上下文转换,扩展表,筛选上下文等等,但我不会去解释,详细的解释会在后面的文章里讲解,在这里仅仅只做一个DAX原理的前瞻,让你们知道两个强大的关系函数的背后真相,以及懂了原理后能做什么…
首先,RELATED和RELATEDTABLE实际上是在扩展表上工作的,通过它们,能允许你访问到扩展表的相关列。扩展表可以用一句话来描述,那就是:用作筛选的表都是扩展表。因此,上面用到的公式其实可以采用扩展表原理使用基础函数重写,为了方便对比,先将上面用到的公式复制过来:
产品所属大类 := RELATED('T0大类'[T0大类K]) 销售记录数量 := COUNTROWS( RELATEDTABLE('T3销售') )上面这两个公式与下面的公式是等价的:
产品所属大类 := CALCULATE( VALUES('T0大类'[T0大类K]) ) 销售记录数量 := COUNTROWS( CALCULATETABLE('T3销售') )毕竟是还没讲到的东西,就不过多叙述了,你们现在也不用去深究,以后会讲。
在有了数据模型后,计算列能够变强,那么度量值肯定也能够变得更强,首先,就是筛选关系能够在数据模型里传播。
还记得前面在介绍CALCULATE时定义的[单价大于20的订单数量]这个度量值么,不记得也没关系,我把它复制到下面:
单价大于20的订单数量 := CALCULATE(COUNTROWS('T3销售'),'T3销售'[T3销售单价]>20)这个度量值具有的筛选器为:‘T3销售’[T3销售单价]>20,由于这个筛选器本来筛选的就是’T3销售’表,不能很好的说明筛选关系在数据模型里的传播,因此,将其修改成:
产品大类为“1科技”的订单数量 := CALCULATE(COUNTROWS('T3销售'),'T0大类'[T0大类K]="1科技") 产品大类为“2教育”的订单数量 := CALCULATE(COUNTROWS('T3销售'),'T0大类'[T0大类K]="2教育") 产品大类为“3文艺”的订单数量 := CALCULATE(COUNTROWS('T3销售'),'T0大类'[T0大类K]="3文艺") 所有产品大类的订单数量 := CALCULATE(COUNTROWS('T3销售'),VALUES('T0大类'[T0大类K]))将上面四个度量值输入到PowerPivot数据表下面的空白单元格中,就可以看到结果:
可以看到,你对’T0大类’[T0大类K]做了一个筛选,然后通过数据模型将这个筛选传递到了’T3销售’表,可能你会觉得这不算什么。那么可以尝试一下将’T0大类’表与’T1子类’表之间的关系断开,看看会发生什么,结果如下图所示:
可以看到,断开关系后,'T0大类’表再也无法将筛选关系传递给’T3销售’表,从而使得结果返回了’T3销售’表中的所有记录。所以,有了数据模型后,'T0大类’表才能够控制’T3销售’表,筛选上下文才有了用武之地!通过关系的传播,你可以实现很多的奇思妙想!
当你学习完上面的知识后,相信你应该对计算列、度量值和查询有了一定的了解,清楚它们的使用方式和区别。而在掌握了数据模型后,计算列和度量值的功能才能够变得更强,因此必须不断地去练习,以总结经验。上面介绍到的CALCULATE与FILTER这两个函数仅仅只是简单介绍,在后面的文章中还会重点讲解,在你还不熟悉它们的特性之前,强行使用的话,极有可能会得到奇怪的结果,这是正常现象。