DAX:这个月的一列中的值是否与上一次相同?

时间:2015-10-05 16:30:29

标签: powerpivot dax

我需要创建一个计算列。我有一个带有序列号的项目列表,这些项目每月都会分配给某人。我需要知道(0/1)本月该项目的所有者是否与上个月该项目的所有者相同。 (因此,我可以创建一个衡量标准,以平均每月更改所有者的数量。)

基本上,我试图找到最后一栏:

Month       ItemID  Owner   Same Owner as Prev Mth
2015/01/31  A1      Al  
2015/01/31  A2      Bob 
2015/01/31  A3      Carl    
2015/02/28  A1      Al      1
2015/02/28  A2      Carl    0
2015/02/28  A3      Carl    1
2015/03/31  A1      Bob     0
2015/03/31  A2      Bob     0
2015/03/31  A3      Bob     0
2015/04/30  A1      Bob     1
2015/04/30  A2      Bob     1
2015/04/30  A3      Al      0

我尝试了一个CALCULATE(Max([Owner]),FILTER(tbl,DATEADD([Month], - 1,MONTH)= EARLIER([Month]),FILTER(tbl,[ItemID] = EARLIER([ItemID] ]))

但Max并不处理文本字段。所以我有点难过。我知道这不应该那么难......

1 个答案:

答案 0 :(得分:1)

日期逻辑几乎总是建模而不是巧妙的功能。

您需要一个具有单调递增整数id的日期表数月。我通常将其称为MonthSequential或MonthIndex,具体取决于模型的目标受众。此字段仅在日期表中每月递增1,而不包括年份边界。因此,如果模型中的第一个月是2014年1月,则该月将具有MonthSequential = 1。 2014年2月有MonthSequential = 2,依此类推到2014年12月,MonthSequential = 12。 2015年1月有MonthSequential = 13。

这允许非常简单的算术来识别从当月开始的任意月份的任何月份或月份范围。在日期维度中包含此字段(以及与DimDate [Date]字段相关的Items [Month]字段)后,生活变得非常简单:

SameOwnerPreviousMonth=
IF(
    CALCULATE(
        VALUES(Items[Owner])
        ,FILTER(
            ALLEXCEPT(Items, Items[ItemID])
            ,RELATED(DimDate[MonthSequential]) =
                EARLIER(RELATED(DimDate[MonthSequential])) - 1
        )
    ) = Items[Owner]
    ,1
    ,0
)

这里有一些关于行上下文的乐趣,我将解释。

任何计算列都由某个公式定义。该公式在表的行上下文中计算。会发生什么是通过表的逐行迭代。您提供的公式每行评估一次,并为该计算列创建值。

话虽如此,DAX背后的存储引擎和公式引擎没有行排序的概念。这意味着,如果我们需要,我们为计算列定义的任何公式都必须提供自己的排序或对另一行的引用。

那么,我们如何在上个月找到所有者呢?好吧,我们需要查看整个Items表,找到具有相同[ItemId]的行并且在当前行的月份之前的月份中。我们的[MonthSequential]使得上个月的日期变得微不足道,DAX提供了许多上下文操作函数来保存或消除上下文。

注意:我将在位置上引用函数参数,并使用(1)指示的函数的第一个参数。

让我们逐步解决问题。我们将忽略IF(),因为这是微不足道的。公式的主要在于CALCULATE(),用于标识上个月的[所有者]:

CALCULATE(
    VALUES(Items[Owner])
    ,FILTER(
        ALLEXCEPT(Items, Items[ItemID])
        ,RELATED(DimDate[MonthSequential]) =
            EARLIER(RELATED(DimDate[MonthSequential])) - 1
    )
)

CALCULATE()首先计算参数(2) - (n),以创建新的过滤器上下文。然后使用该过滤器上下文来评估(1)中的表达式。

FILTER()通过(1)中提供的表逐行迭代,并为(1)中的每一行计算(2)中的布尔表达式。它返回一个由(1)行的子集组成的表,其中(2)的计算结果为true。由于我们在评估计算列时已经遍历整个Items表,因此最终得到两组行上下文。外行上下文是整个表的迭代。内部行上下文是我们过滤器的迭代(1)。外行上下文影响内部,我们必须根据需要修改/删除外部上下文的选择部分。

我们迭代的表是ALLEXCEPT(Items,Items [ItemId])。除了名为的字段外,ALLEXCEPT()删除所有上下文。在外部上下文中的任何给定行上,我们保留Items [ItemId]的值并删除所有其他上下文([Month]和[Owner],以及您未在示例数据中命名的任何其他字段)。这为我们提供了一个表,我们的FILTER()由Items中的每一行组成,它共享外部过滤器上下文中当前行的[ItemId]。该子集表成为我们内部行上下文的生成器。

现在我们正在迭代FILTER()' s(1),如上所述。 RELATED()允许我们调用以从另一个与当前表相关的表中获取值。我们获取与内部行上下文中当前行相关联的[MonthSequential]值。我们希望在外行上下文中找到当前月份之前的月份。要引用外部行上下文中的值,我们需要转义内部。

EARLIER()允许我们转义当前(内部)行上下文并引用最后一个有效(外部)行上下文。这可以通过任意级别的上下文嵌套来实现。幸运的是,我们只有两个。 EARLIER(RELATED(DimDate [MonthSequential]))在外部上下文中查找当前行的[MonthSequential]值。我们只需从中减去1即可获得前一个月(由于我们使用[MonthSequential],我们无需实施任何逻辑来处理年度障碍)。

因此,我们评估VALUES(Items [Owner])的上下文是我们的Items表的子集,其中[ItemId]等于我们外部行上下文中的当前行,而[MonthSequential]的值少一个比外部行上下文中的当前行。 VALUES()返回组成列引用的值列表。在这种情况下,由于每个[ItemId]在任何给定的月份中只与一个[所有者]相关联,因此该列表只是一个值,可以隐式地转换为标量值并在我们的计算列中表示。

我们的IF()只是根据外部行上下文中的当前行测试此[Owner]值,并根据需要返回1或0。

如果您有一个[ItemId]在给定月份内有多个不同的[所有者],这将会中断。

模型图:

enter image description here