Power BI Desktop DAX重新启动运行总计列


9

我有一张桌子,每个人都有一年中每一天的记录。我使用此功能基于每日余额列实现了总计

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

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

在此处输入图片说明


1
在11月5日,人员1的行中,假设我们的测试数据的类型为空白。“必需函数”会在11月6日返回1还是2?
Ryan B.

它将在11月6日返回2。将不会发生“重置”,因为11月5日将是1(不是负数)。感谢您的详细帖子。我今天要评论
LynseyC

Answers:


1

这不仅是具有条件的运行总计,而且是嵌套/集群的总计,因为逻辑必须应用于ID级别。对于大表,M比DAX更好,因为它不占用太多RAM。(我在这里写过博客:链接到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 = TransformToList{0}[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


这样就解决了问题。完美运行,并且没有降低报告速度。谢谢
LynseyC

5

总览

要求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中完成,因为它会产生循环依赖。本质上,您有一个要求:使用合计值来确定应包含在合计中的内容。

这样我就可以带你了。希望能帮助到你。


1
关于你的最后一点,我相信你是正确的。DAX无法进行递归。
Alexis Olson

3

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

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

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

我不确定这是否涵盖所有情况,但似乎是正确的方法。
Mike Honey

仅当每个人的第一种类型为工作时,我才能使其工作。同样,与DAX示例一样,当前一行的累计总数为正数时,它会重新开始工作机芯的编号。我想我的照片令人误解,因为它只包含这种情况。我应该包括一次类型更改为工作但上一行总计为正数的时间。
LynseyC

@LynseyC好,这段代码当然不是完美的解决方案,而只是可以使用的方法示例。只要针对您的情况进行修改。
尤金

@LynseyC同样,在PowerQuery中而不是DAX中执行此数学运算的优势之一是使临时列脱离数据模型的简便方法。
尤金

3

我想我有!

结果是在我之前发布的解决方案的基础上得出的(数据已被修改以显示更多的“工作/不工作”行为和用例)

结果

在此处输入图片说明

细节

(1)删除“调整后的每日余额”和“每日余额调整”列。我们将在短短的一步之内获得相同的结果。

(2)创建以下列(RDB =“每日运行余额”)...

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]

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


嗨,@ RyanB。这对我组织中的200多人来说非常理想,但是其中一个人却没有用。我已经尝试过自己更改代码,但无法解决该问题。我认为这是因为他们工作了很长时间,然后才工作一天,然后再休息。我已链接到图片以显示问题。感谢 图像
LynseyC

我已经修改了“分组RDB调整”度量,以便它应在多个“工作/无工作”周期中通过大量应计休假。
Ryan B.

2
嗨,感谢您的辛勤工作,非常感谢。不幸的是,修改无法解决问题。但是,如果我删除过滤器中的最后一个条件“ [完成工作之前的最新日期] <> Blank()”,则此问题已解决,但随后又打破了原始人员的计算结果:-(
LynseyC

射击。好吧,我希望您能找到可行的方法。
Ryan B.

2

花了一段时间,但我能够提出解决方法。假设空白的余额值始终为-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。当类型= Working时,它将重新启动运行总计,但当类型为空时,它将重新启动运行总计。对于11月7日,它减少到3,但是从11月8日至14日,它返回-2。如果类型为空,您可以帮助修改代码以使运行总计正常工作吗?谢谢
LynseyC

嗨,林西,我尝试了另一种计算方法。由于计算时间过长,我将其添加为另一个答案。但希望新的计算方法能奏效。
CR7SMS

@ CR7SMS请避免为一个问题添加多个答案。它会使可能搜索类似问题/解决方案的其他用户感到困惑,而且效果不佳。相反,您应该将可能提出的解决方案添加到一个答案中,并将每个不同的方面划分为多个部分。
Christos Lytras

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和当前记录日期之间使用“工作”或blank()的天数。使用这些天,我们可以返回运行总计的最终值。

希望这可以解决问题;)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.