0%

对于Filter的第二参数的思考

小思考

最近一直在思考,对于Power Bi的使用来说,如果日常项目中用不到的函数,如何更好的去理解。

简单地说,如果日常项目用加法就可以完成,那么可能不会发现乘法可以更好地优化计算。

对于这个问题,我觉得可能更好的方式有几点:

  1. 自己给自己出题。
  2. 没事就看看别人的代码。
  3. 不停的总结整理,从更方便自己理解的角度去做整理。

正题

最近想要好好理解一下Filter函数,在此过程中,看到一个别人写的代码。觉得很有意思,虽然代码本身没什么难度,但是代码里有一个我觉得我如果不遇到特定的问题,是无法想到的计算方式。

所以这里整理出来。

计算的目的 & 结果

计算当月产生购买的顾客中,有多少是新客户(第一笔购买发生在当月)

Raw data 如下,总行数大概59万行:

image-20221019122829842

用了两种计算方式 (加入了注释,方便理解,嗯,说的就是你,写给你看的。)

CC_A =
VAR AllCustomer = VALUES( ZSMnet[CustomerKey] )
VAR OldCustomer =
    FILTER(
        AllCustomer,
        // 注意Filter是迭代函数,这一点非常重要。意味着下面的表达式均需要对‘AllCustomer’表进行逐行计算。
        // 先算出每个Customer的全局第一次订单日期,再与外部视觉对象中的年月中的第一次订单日期进行比较。
        CALCULATE(
            MIN( ZSMnet[BillingDate] ),
            ALLEXCEPT( ZSMnet, ZSMnet[CustomerKey] )
        // ALLEXCEPT ( <表名>, <列名>, [ <列名>, … ] ) : ALLEXCEPT 从第一个参数指定的表中删除筛选器,只保留后续参数指定的列中的直接筛选器。
        )
            < MIN( ZSMnet[BillingDate] )
            // 这里可能有人会觉得如果当前行客户当前月没有下单岂不是BillingDate为空么,需要注意AllCustomer其实是当前上下文中的YearMonth下的已下单客户。
    )
RETURN
    COUNTROWS( EXCEPT( AllCustomer, OldCustomer ) )
    // EXCEPT ( <左表>, <右表> ) : 返回一个表,其中包含左表的行减去右表的所有行而得到的行。
CC_B =
COUNTROWS(
    FILTER(
        ADDCOLUMNS(
            VALUES( ZSMnet[CustomerKey] ),
            "DateOfFirstBuy",
                CALCULATE(
                    MIN( ZSMnet[BillingDate] ),
                    ALLEXCEPT( ZSMnet, ZSMnet[CustomerKey] )
                )
        ),
        // 注意Filter是迭代函数,这一点非常重要。意味着下面的表达式均需要对上面虚拟表进行逐行计算。
        CONTAINS(
            // CONTAINS ( <表>, <列名>, <值>, [ <列名>, <值> ], [ … ] )
            // 如果每个指定的 <值>可以在对应的<列名>列中找到,或包含在这些列中,则结果为 TRUE;否则函数返回 FALSE 。
            CALCULATETABLE(
                VALUES( ZSMnet[BillingDate] ),
                ALL( ZSMnet[CustomerKey] )
            ),
            ZSMnet[BillingDate],
            [DateOfFirstBuy]
        )
    )
)

计算结果 & 性能分析器

计算结果没什么好说的,肯定都是相同的。

image-20221019123210001

性能分析器,这个就有点意思了:可以看到CC_B的DAX查询性能基本上都是CC_A的2-3倍。

image-20221019123312760

思考

首先是DAX代码,CC_A的写法,基本上是最常见的,下意识的写法。

简单说就是,客户全局首次下单日期,和当月首次下单日期,比大小,就知道当前月下单是否是全局第一次下单。

CC_B的写法,就有点意思了。

简单地说就是,List 当前月所有客户的Billing Date,然后看当前行的Customer的全局首次下单日期,是否在这个 List 中。如果在则表示当前行客户在当前行年月是首次下单,属于新客户。

对于代码,能说的其实就这些。

但是我对这段代码最大的感触倒不完全是代码作者用到了CONTAINS函数来解决这个问题,而是我觉得,Filter 函数的第二参数 ( 布尔表达式 ),这是一个非常重要,但是又常常被我忽略的知识。

FILTER ( <表>, <布尔表达式> )

我相信大部分用Power BI的人都知道Filter的第二参数是布尔表达式,同样,我也知道。

但是我的Point是,如果把DAX所有返回值为布尔值的函数整理出来,也许在解决一些需求的时候,可以拓展很多算法思路。

因为从我的感觉来说,Power BI其实就是 筛选 + 计算。

第一步是如何把你需要计算的 Line 筛选出来,第二步计算反而不是很困难的。

附表

DAX函数 语法 返回值
TRUE TRUE() 标量 一个布尔值,始终返回 TRUE
PATHCONTAINS PATHCONTAINS ( <路径>, <项> ) 标量 一个布尔值
OR OR ( <逻辑表达式 1>, <逻辑表达式 2> ) 标量 一个布尔值(True 或 False)
ISTEXT ISTEXT ( <值> ) 标量 一个布尔值
ISSUBTOTAL ISSUBTOTAL ( <列名> ) 标量 一个布尔值
ISSELECTEDMEASURE ISSELECTEDMEASURE ( <度量值 1>, [<度量值 2> … ] ) 标量 一个布尔值
ISONORAFTER ISONORAFTER ( <值 1>, <值 2>, [<排序>], [<值 1>,<值 2>,<排序>] … ) 标量 一个布尔值
ISODD ISODD ( <数字> ) 标量 一个布尔值
ISNUMBER ISNUMBER ( <值> ) 标量 一个布尔值
ISNONTEXT ISNONTEXT ( <值> ) 标量 一个布尔值
ISLOGICAL ISLOGICAL ( <值> ) 标量 一个布尔值
ISINSCOPE ISINSCOPE ( <列名> ) 标量 一个布尔值
ISFILTERED ISFILTERED ( ) 标量 一个布尔值
ISEVEN ISEVEN ( <数字> ) 标量 一个布尔值
ISERROR ISERROR ( <值> ) 标量 一个布尔值
ISEMPTY ISEMPTY ( <表> ) 标量 一个布尔值
ISCROSSFILTERED ISCROSSFILTERED ( ) 标量 一个布尔值
ISBLANK ISBLANK ( <值> ) 标量 一个布尔值,如果值为空,则返回 TRUE;反之返回 FALSE
HASONEVALUE HASONEVALUE ( <列名> ) 标量 一个布尔值
HASONEFILTER HASONEFILTER ( <列名> ) 标量 一个布尔值
FALSE FALSE() 标量 一个布尔值,始终是 FALSE
EXACT EXACT ( <文本 1>, <文本 2> ) 标量 一个布尔值
CONTAINSSTRINGEXACT CONTAINSSTRINGEXACT ( <查找范围>, <要找的文本> ) 标量 布尔值
CONTAINSSTRING CONTAINSSTRING ( <查找范围>, <要找的文本> ) 标量 布尔值
CONTAINSROW 和 IN CONTAINSROW ( <表>, <值>, [ <值> … ] ) 标量 布尔值,True 或 False
CONTAINS CONTAINS ( <表>, <列名>, <值>, [ <列名>, <值> ], [ … ] ) 标量 一个布尔值
AND AND ( <逻辑表达式 1>, <逻辑表达式 2> ) 标量 一个布尔值(True 或 False)