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

时间:2020-01-29 10:36:42

标签: powerbi dax powerbi-desktop

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

来获得运行总计
CALCULATE(
SUM(Leave[Daily Balance]),
FILTER(
   ALLEXCEPT(Leave, Leave[Employee Id]),
   Leave[Date] <= EARLIER(Leave[Date])
))

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

enter image description here

6 个答案:

答案 0 :(得分:5)

概述

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

最大的问题是PowerBI的数据模型不支持运行提示的概念-至少不支持我们在Excel中进行的方式。在Excel中,一列可以引用该列的“上一行”中出现的值,然后通过其他列中列出的“每日更改”进行调整。

PowerBI只能通过将行的某些子集上的所有每日更改加起来来模仿这一点。我们在当前行中获取日期值,并创建一个过滤表,其中所有日期均小于当前行的日期,然后汇总该子集中的所有每日更改。这似乎有些细微的差异,但意义非凡:

这意味着无法“替代”我们的总运行量。唯一要进行的数学运算是在包含每日变化的列上进行的-包含“运行总计”的列仅是一个结果-以后的任何计算都不会使用它。

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

如果查看OP给定的计算出的运行量,我们会发现在“工作日”之前的“非工作日”的运行总值为我们提供了所需的金额,如果将其取反,将得出以下总和:为零,并导致接下来的每个工作日的总运行量增加一。这是我们所希望的行为(稍后将描述一个问题)。

结果

enter image description here

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 :(得分: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

答案 2 :(得分:3)

我想我有!

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

结果

enter image description here

详细信息

(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]

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

答案 3 :(得分: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

请记住,当我处理一个小样本时,它可能不是成品,但这应该可以帮助您入门。希望这会有所帮助。

答案 4 :(得分: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()的天数。使用这些天,我们可以返回运行总计的最终值。

希望这能达到目的;)

答案 5 :(得分:1)

这不仅是带有条件的运行总计,而且是嵌套/集群的总计,因为逻辑必须应用于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 = 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