Power BI Desktop DAX 重启运行总计列

Posted

技术标签:

【中文标题】Power BI Desktop DAX 重启运行总计列【英文标题】:Power BI Desktop DAX restart running total column 【发布时间】:2020-05-14 20:13:08 【问题描述】:

我有一张桌子,每个人都有一年中每一天的记录。我用这个函数实现了基于每日余额列的运行总计

CALCULATE(
SUM(Leave[Daily Balance]),
FILTER(
   ALLEXCEPT(Leave, Leave[Employee Id]),
   Leave[Date] <= EARLIER(Leave[Date])
))

但是如果 Type = Working 并且 Daily Balance 的运行总计小于零并且上一行的 Type 不等于 Working,我需要从 1 重新开始运行总计。下面是 Excel 的屏幕截图。所需的功能列是我需要到达的。

【问题讨论】:

在 11 月 5 日的行中,第 1 个人,假设我们的测试数据类型为空白。 “必需函数”会在 11 月 6 日返回 1 还是 2? 它将返回 2 表示 11 月 6 日。“重置”不会发生,因为 11 月 5 日将是 1(不是负数)。感谢您的详细帖子。我今天正在复习 【参考方案1】:

花了一些时间,但我想出了一个解决方法。假设,空白的余额值始终为 -1,“工作”的值为 1,并且该数据可用于所有日期,没有间隙,如下计算可能有效:

Running Total = 
    VAR Employee = Leave[Employee ID]
    VAR Date1 = Leave[Date]
    VAR Prev_Blank = CALCULATE(MAX(Leave[Date]),
                        FILTER(Leave,Leave[Date] < Date1),
                        FILTER(Leave,Leave[Employee ID]=Employee),
                        FILTER(Leave,Leave[Type]=BLANK()))  
    VAR Day_count_Working = CALCULATE(COUNT(Leave[Date]),
                        FILTER(Leave,Leave[Date] > Prev_Blank),
                        FILTER(Leave,Leave[Date] <= Date1),
                        FILTER(Leave,Leave[Employee ID]=Employee),
                        FILTER(Leave,Leave[Type]="Working")) 
    VAR Day_count = CALCULATE(COUNT(Leave[Date]),
                        FILTER(Leave,Leave[Date] >= Prev_Blank),
                        FILTER(Leave,Leave[Date] <= Date1),
                        FILTER(Leave,Leave[Employee ID]=Employee)) 
RETURN (IF(Day_count_Working=BLANK(),Day_count,Day_count-1)-Day_count_Working)*-1 + Day_count_Working

请记住,这可能不是成品,因为我使用了一个小样本,但这应该可以帮助您入门。希望这会有所帮助。

【讨论】:

感谢@CR7SMS。当类型=工作时,它重新启动运行总计,但类型为空白时的运行总计不起作用。对于 11 月 7 日,它减少到 3,但从 11 月 8 日至 14 日,它返回 -2。当类型为空白时,您可以帮助修改代码以使运行总数起作用吗?谢谢 嗨 Lynsey,我尝试了不同的计算。由于计算有点长,我将其添加为另一个答案。但希望新的计算能奏效。 @CR7SMS 请避免为一个问题添加多个答案。它使可能搜索类似问题/解决方案的其他用户感到困惑,这并不好。相反,您应该将可能提出的任何解决方案添加到一个答案中,并将每个不同的方面拆分为部分。【参考方案2】:

计算有点冗长,但它似乎适用于我正在使用的示例数据。试试这个:

Running Total = 
    VAR Employee = Leave[Employee ID]
    VAR Date1 = Leave[Date]
    VAR Prev_Blank = CALCULATE(MAX(Leave[Date]),
                        FILTER(Leave,Leave[Date] < Date1),
                        FILTER(Leave,Leave[Employee ID]=Employee),
                        FILTER(Leave,Leave[Type]=BLANK()))  
    VAR Prev_Working = CALCULATE(MAX(Leave[Date]),
                        FILTER(Leave,Leave[Date] < Date1),
                        FILTER(Leave,Leave[Employee ID]=Employee),
                        FILTER(Leave,Leave[Type]="Working"))    
    VAR Prev_Blank1 = CALCULATE(MAX(Leave[Date]),
                        FILTER(Leave,Leave[Date] < Prev_Working),
                        FILTER(Leave,Leave[Employee ID]=Employee),
                        FILTER(Leave,Leave[Type]=BLANK()))  
    VAR Prev_type = CALCULATE(MAX(Leave[Type]),
                        FILTER(Leave,Leave[Date] = Date1-1),
                        FILTER(Leave,Leave[Employee ID]=Employee))
    VAR Prev_Blank2 = IF(Leave[Type]="Working" && (Prev_Blank1=BLANK() || Prev_type=BLANK()),Date1-1,Prev_Blank1)    
    VAR Day_count_Working = CALCULATE(COUNT(Leave[Date]),
                        FILTER(Leave,Leave[Date] > Prev_Blank2),
                        FILTER(Leave,Leave[Date] <= Date1),
                        FILTER(Leave,Leave[Employee ID]=Employee),
                        FILTER(Leave,Leave[Type]="Working")) 
    VAR Day_count = CALCULATE(COUNT(Leave[Date]),
                        FILTER(Leave,Leave[Date] >= Prev_Blank2),
                        FILTER(Leave,Leave[Date] <= Date1),
                        FILTER(Leave,Leave[Employee ID]=Employee)) 
RETURN (IF(Day_count_Working=BLANK(),Day_count,Day_count-1)-Day_count_Working)*-1 + Day_count_Working

我在这里使用了一堆变量。你也许可以想出一个更短的版本。基本上这个想法是找到之前第一次出现的“工作”来找到从哪里开始计算。这是在变量“Prev_Blank2”中计算的。一旦我们知道了起点(这里从 1 开始),那么我们可以简单地计算 Prev_Blank2 和当前记录日期之间的“Working”或 blank() 天数。使用这些天,我们可以返回运行总计的最终值。

希望这能解决问题;)

【讨论】:

【参考方案3】:

希望下次您将粘贴生成示例数据而不是图片的 csv 或代码。 :)

我只是建议您改为在 PowerQuery 中进行计算。 我尝试将代码拆分为几个步骤以提高可读性。 这可能看起来有点复杂,但效果很好。只需将其粘贴到高级编辑器中,然后将 source 替换为您的源数据。 祝你好运!

let
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45WMjDUMzDSMzIwtFTSUQpILSrOz1MwBDLL84uyM/PSlWJ1gGqMsKuBSBrjkzQhwnRTItSYEaHGHJ9DLPBJWhI23dAAjwGGOAIRIokj9OCmxwIA", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type text) meta [Serialized.Text = true]) in type table [date = _t, name = _t, #"type" = _t]),
    SetTypes = Table.TransformColumnTypes(Source,"date", type date, "name", type text, "type", type text),
    TempColumn1 = Table.AddColumn(SetTypes, "LastOtherType", (row)=>List.Max(Table.SelectRows(SetTypes, each ([name] = row[name] and [type] <> row[type] and [date] <= row[date]))[date], row[date]), type date) //Here for each row we select all rows of other type with earlier date, and take max that date. Thus we know when was previous change from one type to another
 //Here for each row we select all rows of other type with earlier date, and take max that date. Thus we know when was previous change from one type to another
,
    TempColumn2 = Table.AddColumn(TempColumn1, "Count", (row)=>
(if row[type]="working" then 1 else -1) * 
Table.RowCount(
Table.SelectRows(SetTypes, each ([name] = row[name] and [type] = row[type] and [date] <= row[date] and [date] > row[LastOtherType])) /* select all rows between type change (see prev step) and current row */
), /*and count them*/
Int64.Type) // finally multiply -1 if they are not working type
,
    FinalColumn = Table.AddColumn(TempColumn2, "FinalFormula", (row)=> 
(if row[type] = "working" then row[Count] else /* for working days use Count, for others take prev max Count and add current Count, which is negative for non-working*/
Table.LastN(Table.SelectRows(TempColumn2, each [name] = row[name] and [type] = "working" and [LastOtherType] <= row[LastOtherType]),1)[Count]0
+ row[Count])
, Int64.Type),
    RemovedTempColumns = Table.RemoveColumns(FinalColumn,"LastOtherType", "Count")
in
    RemovedTempColumns

【讨论】:

我不确定这是否涵盖所有场景,但它似乎是正确的方法。 只有当每个人的第一个类型是工作时,我才能让它工作。此外,与 DAX 示例一样,当前一行的累计总数为正数时,它会重新开始对工作移动进行编号。我想我的图片具有误导性,因为它只包含这种情况。我应该包括一个类型更改为工作但前一行总数为正数的时间。 @LynseyC 好吧,这段代码当然不是完美和完整的解决方案,而是可以使用的方法示例。只需根据您的情况修改 if。 @LynseyC 同样,在 PowerQuery 而不是 DAX 中进行此数学运算的优势之一是可以轻松地将临时列远离数据模型。【参考方案4】:

概述

要求 PowerBI 完成这是一件具有挑战性的事情,因此可能很难找到一个整洁的方法。

最大的问题是 PowerBI 的数据模型不支持运行计数的概念——至少不支持我们在 Excel 中执行的方式。在 Excel 中,一列可以引用同一列的“上一行”中出现的值,然后通过不同列中列出的某些“每日变化”进行调整。

PowerBI 只能通过将某些行子集的所有日常更改相加来模仿这一点。我们获取当前行中的日期值并创建一个过滤表,其中所有日期都小于当前行的日期,然后汇总该子集中的所有每日更改。这似乎是一个微妙的差异,但它非常重要:

这意味着无法“覆盖”我们的运行总数。唯一要做的数学运算发生在包含每日变化的列上——包含“运行总计”的列只是一个结果——它永远不会在任何后续行的计算中使用。

我们必须放弃“重置”的概念,而是想象制作一个包含“调整”值的列。我们的调整将是一个可以包含的值,这样当满足所述条件时,每日余额和调整的总和将为 1。

如果我们查看 OP 给出的计算运行,我们会看到我们在“工作日”之前的“非工作日”的运行总计值给了我们所需的金额,如果反转,将求和归零,并导致下一个工作日的累计加一。这是我们想要的行为(有一个问题将在后面描述)。

结果

Most Recent Date Prior to Work = 

CALCULATE(
Max(Leave[Date]),
FILTER(
   ALLEXCEPT(Leave, Leave[Id]),
   Leave[Date] = EARLIER(Leave[Date]) -1 && Leave[Type] <> "Working" && Earlier(Leave[Type]) = "Working"
))

这有助于了解行上下文和筛选上下文之间的区别以及 EARLIER 如何操作以遵循此计算。在这种情况下,您可以将“EARLIER”视为“此引用指向当前行中的值”,否则一个引用指向“ALLEXCEPT(Leave, Leave[Id])”返回的整个表。方式,我们找到当前行具有“工作”类型而前一天的行具有其他类型的地方。

Most Recent Date Prior to Work Complete = 

CALCULATE(
Max(Leave[Most Recent Date Prior to Work]),
FILTER(
   ALLEXCEPT(Leave, Leave[Id]),
   Leave[Date] <= EARLIER(Leave[Date])
))

此计算模拟“填充”类型的操作。它说:“当查看日期早于该行日期的所有行时,返回“上班前最近的日期”中的最大值。

Daily Balance Adjustment = 

CALCULATE(
SUM(Leave[Running Daily Balance]),
FILTER(
   ALLEXCEPT(Leave, Leave[Id]),
   Leave[Date] = EARLIER(Leave[Most Recent Date Prior to Work Complete])
))

现在每一行都有一个字段说明在哪里可以找到每日余额以用作我们的调整,我们可以从表格中查找它。

Adjusted Daily Balance = Leave[Running Daily Balance] - Leave[Daily Balance Adjustment]

最后,我们将调整应用到我们的运行总计以获得最终结果。

问题

这种方法无法解决除非运行的每日余额低于零,否则不应重置计数。我之前被证明是错误的,但我会说这不能单独在 DAX 中完成,因为它会创建循环依赖。本质上,您提出了一个要求:使用聚合值来确定应该包含在聚合中的内容。

所以我能带给你的就这么多。希望对您有所帮助。

【讨论】:

关于你的最后一点,我相信你是对的。 DAX 不能进行递归。【参考方案5】:

我觉得我有!

这是基于我之前发布的解决方案的结果:(数据已被修改以展示更多“工作/不工作”行为和用例)

结果

详情

(1) 删除“调整后的跑步每日余额”和“每日余额调整”栏。我们会在短时间内少走一步,得到同样的结果。

(2) 创建以下列(RDB = "running daily balance")...

Grouped RDB = 

CALCULATE(
SUM(Leave[Daily Balance]),
FILTER(
   ALLEXCEPT(Leave, Leave[Id], Leave[Most Recent Date Prior to Work Complete]),
   Leave[Date] <= EARLIER(Leave[Date]) 
))

在创建了“工作完成前的最近日期”之后,我们实际上已经有了我之前声称不可能完成的“重置”所需的部分。通过对该字段进行过滤,我们有机会从“1”开始每个切片

(3) 我们仍然有同样的问题,我们无法查看列中的结果并使用它来决定稍后在同一列中要做什么。但是我们可以建立一个新的调整列来保存该信息!我们已经引用了“上班前的最近日期”——这是上一组中的最后一天......我们需要的信息所在的行!

Grouped RDB Adjustment = 

VAR CalculatedAdjustment =
CALCULATE(
SUM(Leave[Grouped RDB]),
FILTER(
   ALLEXCEPT(Leave, Leave[Id]),
   Leave[Date] IN SELECTCOLUMNS(
        FILTER(
            Leave,
            Leave[Most Recent Date Prior to Work] <> BLANK() &&
            Leave[id] = EARLIER(Leave[Id])), "MRDPtW", Leave[Most Recent Date Prior to Work]) &&
   Leave[Most Recent Date Prior to Work Complete] < EARLIER(Leave[Most Recent Date Prior to Work Complete]) &&
   Leave[Most Recent Date Prior to Work Complete] <> Blank()
))

RETURN if (CalculatedAdjustment > 0, CalculatedAdjustment, 0)

因此,我们查看之前每个组中的最后一天,如果这些调整的总和具有正值,我们将应用它,如果它是负值,我们将不理会它。此外,如果我们的人的前几天是非工作日,我们根本不希望在我们的调整中最初的负面部分,所以它也会被过滤掉。

(4) 这最后一步会将调整带入最终结果。总结这两个新列,我们最终应该得到调整后的每日运行余额。瞧!

Adjusted Running Daily Balance = Leave[Grouped RDB] + Leave[Grouped RDB Adjustment]

在获得这个结果的过程中,我们构建了很多额外的列,这通常不是我最喜欢做的事情。但是,这是一个棘手的问题。

【讨论】:

嗨@Ryan B。这对我组织中的 200 多人来说非常有效,但其中一个人不起作用。我试过自己更改代码,但我无法解决问题。我认为这是因为他们工作了很长时间,然后只工作了一天,然后就有了更多的休息时间。我已链接到图像以显示问题。谢谢Image 我已经修改了“分组 RDB 调整”措施,以便它应该在多个“工作/不工作”周期中传递大量的休假。 您好,感谢您的所有努力,非常感谢。不幸的是,修改并没有解决问题。但是,如果我删除了过滤器“离开[工作完成前的最近日期] Blank()”中的最后一个条件,它解决了问题,但随后又打破了原来人们的计算:-( 射击。好吧,我希望你能找到一些有用的东西。【参考方案6】:

这不仅是一个带有条件的运行总计,而且还是一个嵌套/集群的总计,因为逻辑必须应用于 ID 级别。对于大型表,M 比 DAX 更擅长,因为它不使用那么多 RAM。 (我在这里写过博客:Link to Blogpost

以下函数使该逻辑适应当前情况,并且必须应用于 ID 级别:(必需的列名称是:“类型”、“每日津贴”、“调整”)

(MyTable as table) => let SelectJustWhatsNeeded = Table.SelectColumns(MyTable,"Type", "Daily Allowance", "Adjustments"), ReplaceNulls = Table.ReplaceValue(SelectJustWhatsNeeded,null,0,Replacer.ReplaceValue,"Adjustments"), #"Merged Columns" = Table.CombineColumns(ReplaceNulls,"Daily Allowance", "Adjustments", List.Sum,"Amount"), TransformToList = List.Buffer(Table.ToRecords(#"Merged Columns")), ConditionalRunningTotal = List.Skip(List.Generate( () => [Type = TransformToList0[Type], Result = 0, Counter = 0], each [Counter] <= List.Count(TransformToList), each [ Result = if TransformToList[Counter][Type] = "working" and [Result] < 0 and [Type] <> "working" then TransformToList[Counter][Amount] else TransformToList[Counter][Amount] + [Result] , Type = TransformToList[Counter][Type], Counter = [Counter] + 1 ], each [Result] )), Custom1 = Table.FromColumns( Table.ToColumns(MyTable) & ConditionalRunningTotal, Table.ColumnNames(MyTable) & "Result" ) in Custom1

【讨论】:

这已经解决了问题。工作完美,并没有减慢报告速度。谢谢

以上是关于Power BI Desktop DAX 重启运行总计列的主要内容,如果未能解决你的问题,请参考以下文章

Power BI--DAX函数总结

Power BI 使用 DAX 将八位 yyyymmdd 转换为日期

Power BI - Tabular - 模型类型

Power BI DAX 度量计算

Power BI DAX CountIF 查询

POWER BI - 递归计算我的 dax 度量的解决方法?