Starten Sie Power BI Desktop DAX neu, indem Sie die Gesamtspalte ausführen

9

Ich habe einen Tisch, an dem jede Person für jeden Tag des Jahres einen Rekord hat. Ich habe diese Funktion verwendet, um eine laufende Summe basierend auf der Spalte "Tagesguthaben" zu erzielen

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

Aber ich brauche die laufende Summe, um von 1 neu zu starten, wenn Typ = Arbeiten UND die laufende Summe des täglichen Guthabens kleiner als Null ist UND der Typ der vorherigen Zeile nicht gleich Arbeiten ist. Unten sehen Sie einen Screenshot aus Excel. Die erforderliche Funktionsspalte ist das, was ich erreichen muss.

Geben Sie hier die Bildbeschreibung ein

LynseyC
quelle
1
Angenommen, in der Zeile für den 5. November, Person 1, hatten unsere Testdaten einen leeren Typ. Würde die 'erforderliche Funktion' am 6. November eine 1 oder eine 2 zurückgeben?
Ryan B.
Es würde eine 2 für den 6. November zurückgeben. Das "Zurücksetzen" würde nicht stattfinden, da der 5. November 1 wäre (keine negative Zahl). Vielen Dank für Ihren ausführlichen Beitrag. Ich überprüfe heute
LynseyC

Antworten:

1

Dies ist nicht nur eine laufende Summe mit einer Bedingung, sondern auch eine verschachtelte / gruppierte, da die Logik auf der ID-Ebene angewendet werden muss. Bei großen Tabellen ist M besser als DAX, da es nicht so viel RAM benötigt. (Ich habe hier darüber gebloggt : Link zu Blogpost

Die folgende Funktion passt diese Logik an den aktuellen Fall an und muss auf ID-Ebene angewendet werden: (Erforderliche Spaltennamen sind: "Typ", "Tagesgeld", "Anpassungen")

(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

ImkeF
quelle
Dies hat das Problem behoben. Funktioniert einwandfrei und hat den Bericht nicht verlangsamt. Danke
LynseyC
5

Überblick

Dies ist eine herausfordernde Aufgabe für PowerBI. Daher ist es möglicherweise schwierig, einen ordentlichen Ansatz zu finden.

Das größte Problem ist, dass das Datenmodell von PowerBI das Konzept einer laufenden Abrechnung nicht unterstützt - zumindest nicht so, wie wir es in Excel tun. In Excel kann eine Spalte auf Werte verweisen, die in der 'vorherigen Zeile' derselben Spalte vorkommen, und dann durch eine in einer anderen Spalte aufgeführte 'tägliche Änderung' angepasst werden.

PowerBI kann dies nur nachahmen, indem alle täglichen Änderungen über eine Teilmenge von Zeilen addiert werden. Wir nehmen den Datumswert in unserer aktuellen Zeile und erstellen eine gefilterte Tabelle, in der alle Daten kleiner als das Datum dieser aktuellen Zeile sind, und fassen dann alle täglichen Änderungen aus dieser Teilmenge zusammen. Dies scheint ein subtiler Unterschied zu sein, ist aber ziemlich bedeutsam:

Dies bedeutet, dass es keine Möglichkeit gibt, unsere laufende Summe zu überschreiben. Die einzige Berechnung, die durchgeführt wird, ist die Spalte mit den täglichen Änderungen - die Spalte mit der 'laufenden Summe' ist nur ein Ergebnis - sie wird niemals in der Berechnung einer nachfolgenden Zeile verwendet.

Wir müssen das Konzept des 'Zurücksetzens' aufgeben und uns stattdessen vorstellen, eine Spalte zu erstellen, die einen 'Anpassungs'-Wert enthält. Unsere Anpassung ist ein Wert, der einbezogen werden kann, sodass bei Erfüllung der beschriebenen Bedingungen die Summe der täglichen Salden und Anpassungen 1 ergibt.

Wenn wir uns den berechneten Lauf von OP ansehen, sehen wir, dass der Wert unserer laufenden Summe an einem "arbeitsfreien" Tag unmittelbar vor einem "arbeitenden" Tag den erforderlichen Betrag ergibt, der, wenn er umgekehrt wird, zu Null und Null summiert Die laufende Summe an jedem folgenden Arbeitstag erhöht sich um eins. Dies ist unser gewünschtes Verhalten (mit einem Problem, das später beschrieben wird).

Ergebnis

Geben Sie hier die Bildbeschreibung ein

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"
))

Es ist hilfreich, den Unterschied zwischen Zeilen- und Filterkontexten zu kennen und zu wissen, wie EARLIER arbeitet, um dieser Berechnung zu folgen. In diesem Szenario können Sie sich "EARLIER" als "diese Referenz verweist auf den Wert in der aktuellen Zeile" vorstellen, und ansonsten verweist eine Referenz auf die gesamte Tabelle, die von "ALLEXCEPT (Leave, Leave [Id])" zurückgegeben wird Auf diese Weise finden wir die Stellen, an denen die aktuelle Zeile den Typ "Working" und die Zeile des Vortages einen anderen Typ hat.

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

Diese Berechnung ahmt eine Art "Auffüllen" nach. Darin heißt es: "Wenn Sie alle Zeilen betrachten, deren Datum vor dem Datum in DIESER Zeile liegt, geben Sie den größten Wert in" Letztes Datum vor der Arbeit "zurück."

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

Jetzt, da jede Zeile ein Feld enthält, in dem erklärt wird, wohin das tägliche Guthaben als Anpassung verwendet werden muss, können wir es einfach von der Tabelle aus nachschlagen.

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

Und schließlich wenden wir die Anpassung für das Endergebnis auf unsere laufende Summe an.

Die Angelegenheit

Bei diesem Ansatz wird nicht berücksichtigt, dass die Zählung nicht zurückgesetzt werden sollte, es sei denn, der laufende Tagesguthaben liegt unter Null. Ich habe mich zuvor als falsch erwiesen, aber ich würde sagen, dass dies nicht allein im DAX erreicht werden kann, da dadurch eine zirkuläre Abhängigkeit entsteht. Im Wesentlichen stellen Sie eine Anforderung: Verwenden Sie den aggregierten Wert, um zu bestimmen, was in der Aggregation enthalten sein soll.

So weit kann ich dich bringen. Ich hoffe es hilft.

Ryan B.
quelle
1
In Bezug auf Ihren letzten Punkt glaube ich, dass Sie richtig sind. DAX kann keine Rekursion durchführen.
Alexis Olson
3

Ich hoffe, dass Sie beim nächsten Mal eine CSV oder einen Code einfügen, der Beispieldaten anstelle von Bildern generiert. :) :)

Lassen Sie mich nur vorschlagen, dass Sie Ihre Berechnungen stattdessen in PowerQuery durchführen. Ich habe versucht, den Code für einige Schritte aufzuteilen, um die Lesbarkeit zu verbessern. Dies mag etwas komplexer aussehen, funktioniert jedoch gut. Fügen Sie es einfach in den erweiterten Editor ein und ersetzen Sie die Quelle durch Ihre Quelldaten. Viel Glück!

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
Eugene
quelle
Ich bin nicht sicher, ob dies jedes Szenario abdeckt, aber es scheint der richtige Ansatz zu sein.
Mike Honey
Ich kann dies nur zum Laufen bringen, wenn der erste Typ für jede Person "Arbeiten" ist. Ebenso wie bei den DAX-Beispielen wird die Nummerierung für eine Arbeitsbewegung neu gestartet, wenn die kumulierte Summe für die vorherige Zeile eine positive Zahl ist. Ich denke, mein Bild war irreführend, da es nur dieses Szenario enthielt. Ich hätte eine Zeit einschließen sollen, in der der Typ auf "Arbeiten" geändert wurde, aber die vorherige Zeilensumme positiv war.
LynseyC
@LynseyC Nun, dieser Code ist natürlich keine perfekte und vollständige Lösung, sondern ein Beispiel für Methoden, die verwendet werden können. Ändern Sie einfach, ob für Ihr Szenario.
Eugene
@LynseyC Auch einer der Vorteile dieser Berechnung in PowerQuery anstelle von DAX ist eine einfache Möglichkeit, temporäre Spalten vom Datenmodell fernzuhalten.
Eugene
3

Ich glaube ich habe es!

Hier ist das Ergebnis, das auf der zuvor veröffentlichten Lösung aufbaut: (Die Daten wurden geändert, um mehr Verhaltensweisen und Anwendungsfälle für "Arbeit / keine Arbeit" anzuzeigen.)

ERGEBNIS

Geben Sie hier die Bildbeschreibung ein

EINZELHEITEN

(1) Löschen Sie die Spalten "Adjusted Running Daily Balance" und "Daily Balance Adjustment". Wir werden in einem Moment das gleiche Ergebnis mit einem Schritt weniger erzielen.

(2) Erstellen Sie die folgende Spalte (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]) 
))

Nachdem wir das "Letzte Datum vor Abschluss der Arbeit" erstellt haben, haben wir tatsächlich das Stück, das für unser "Zurücksetzen" benötigt wird, von dem ich behauptete, dass es vorher unmöglich war. Durch Filtern nach diesem Feld haben wir die Möglichkeit, jedes Slice bei '1' zu beginnen.

(3) Wir haben immer noch das gleiche Problem, aber wir können das Ergebnis in unserer Spalte nicht betrachten und es verwenden, um zu entscheiden, was später in derselben Spalte zu tun ist. Aber wir KÖNNEN eine neue Anpassungsspalte erstellen, die diese Informationen enthält! Und wir haben bereits einen Verweis auf "Letztes Datum vor der Arbeit" - das ist der letzte Tag in der vorherigen Gruppe ... die Zeile mit den Informationen, die wir brauchen!

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)

Also schauen wir uns den letzten Tag in Each an vorherigen Gruppe an. Wenn die Gesamtsumme dieser Anpassungen einen positiven Wert hat, wenden wir ihn an. Wenn er negativ ist, lassen wir ihn stattdessen in Ruhe. Wenn die ersten Tage unserer Person arbeitsfreie Tage sind, möchten wir dieses anfängliche negative Bit überhaupt nicht in unserer Anpassung haben, damit es auch weggefiltert wird.

(4) Dieser letzte Schritt bringt die Anpassung in das Endergebnis. Fassen Sie die beiden neuen Spalten zusammen und wir sollten endlich unser angepasstes laufendes Tagesguthaben haben. Voila!

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

Auf dem Weg zu diesem Ergebnis haben wir viele zusätzliche Spalten erstellt, was normalerweise nicht meine Lieblingsbeschäftigung ist. Aber das war eine schwierige Frage.

Ryan B.
quelle
Hallo @ Ryan B. Dies funktioniert perfekt für über 200 Personen in meiner Organisation, aber eine funktioniert nicht. Ich habe versucht, den Code selbst zu ändern, aber ich kann nichts bekommen, um das Problem zu lösen. Ich denke, das liegt daran, dass sie lange gearbeitet haben und dann nur einen Tag gearbeitet haben, bevor sie mehr Freizeit hatten. Ich habe ein Bild verlinkt, um das Problem zu zeigen. Danke Bild
LynseyC
Ich habe die Maßnahme "Gruppierte RDB-Anpassung" so geändert, dass sie über mehrere "Arbeits- / Nichtarbeits" -Zyklen hinweg große Urlaubsrückstellungen aufweist.
Ryan B.
2
Hallo, danke für all die Mühe, sehr geschätzt. Leider hat die Änderung das Problem nicht behoben. Wenn ich jedoch die letzte Bedingung im Filter "Lassen Sie [Letztes Datum vor Abschluss der Arbeit] <> Blank ()" entfernt habe, wurde das Problem behoben, aber dann wurden die Berechnungen der ursprünglichen Personen erneut unterbrochen :-(
LynseyC
Schießen. Nun, ich hoffe, Sie können etwas finden, das funktioniert.
Ryan B.
2

Es hat eine Weile gedauert, aber ich konnte eine Problemumgehung finden. Angenommen, der Saldowert für Leerzeichen ist immer -1 und der Wert 1 für "Arbeiten", und diese Daten sind für alle Daten ohne Lücke verfügbar. So etwas wie die folgende Berechnung könnte funktionieren:

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

Denken Sie daran, dass dies möglicherweise kein fertiges Produkt ist, da ich mit einer kleinen Probe gearbeitet habe, aber dies sollte Ihnen den Einstieg erleichtern. Hoffe das hilft.

CR7SMS
quelle
Danke @ CR7SMS. Die laufende Summe wird neu gestartet, wenn der Typ = funktioniert, aber die laufende Summe, wenn der Typ leer ist, funktioniert nicht. Für den 7. November reduziert es sich auf 3, aber vom 8. bis 14. November gibt es -2 zurück. Können Sie bei der Änderung des Codes helfen, damit die laufende Summe funktioniert, wenn der Typ leer ist? Danke
LynseyC
Hallo Lynsey, ich habe eine andere Berechnung versucht. Ich habe es als weitere Antwort hinzugefügt, da die Berechnung etwas lang war. Aber hoffentlich funktioniert die neue Berechnung.
CR7SMS
@ CR7SMS Bitte vermeiden Sie, mehr als eine Antwort auf eine einzelne Frage hinzuzufügen. Es verwirrt andere Benutzer, die möglicherweise nach einem ähnlichen Problem / einer ähnlichen Lösung suchen, und es ist nicht nett. Stattdessen sollten Sie einer Antwort alles hinzufügen, was Sie als Lösung finden, und jeden Aspekt in Abschnitte aufteilen.
Christos Lytras
2

Die Berechnung ist etwas langwierig, scheint aber in den von mir verwendeten Beispieldaten zu funktionieren. Probieren Sie es aus:

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

Ich habe hier eine Reihe von Variablen verwendet. Vielleicht können Sie sich eine kürzere Version einfallen lassen. Grundsätzlich besteht die Idee darin, das vorherige erste Vorkommen von "Arbeiten" zu finden, um herauszufinden, von wo aus die Berechnung gestartet werden soll. Dies wird in der Variablen "Prev_Blank2" berechnet. Sobald wir den Startpunkt kennen (er beginnt hier mit 1), können wir einfach die Anzahl der Tage mit "Working" oder blank () zwischen Prev_Blank2 und dem Datum des aktuellen Datensatzes zählen. An diesen Tagen können wir den endgültigen Wert für die laufende Summe zurückgeben.

Hoffentlich macht das den Trick;)

CR7SMS
quelle