Riavvio DAX di Power BI Desktop in esecuzione nella colonna totale


9

Ho un tavolo in cui ogni persona ha un record per ogni giorno dell'anno. Ho usato questa funzione per ottenere un totale parziale basato sulla colonna del saldo giornaliero

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

ma ho bisogno che il totale parziale si riavvii da 1 se Tipo = Funzionante E il totale corrente del Saldo giornaliero è inferiore a zero E il Tipo della riga precedente non è uguale a Funzionante. Di seguito una schermata di Excel. La colonna della funzione richiesta è quella di cui ho bisogno.

inserisci qui la descrizione dell'immagine


1
Sulla riga per il 5 novembre, Persona 1, supponiamo che i nostri dati di test abbiano uno spazio vuoto. La "funzione richiesta" restituirà un 1 o un 2 il 6 novembre?
Ryan B.

Restituirebbe un 2 per il 6 novembre. Il "reset" non avverrebbe perché il 5 novembre sarebbe 1 (non un numero negativo). Grazie per il tuo post dettagliato. Sto recensendo oggi
LynseyC il

Risposte:


1

Questo non è solo un totale parziale con una condizione, ma anche uno nidificato / cluster, poiché la logica deve essere applicata a livello di ID. Per i tavoli di grandi dimensioni, M è meglio di DAX, in quanto non utilizza tanta RAM. (Ho scritto sul blog qui: Link a Blogpost

La seguente funzione adatta tale logica al caso corrente e deve essere applicata a livello di ID: (I nomi di colonna richiesti sono: "Tipo", "Indennità giornaliera", "Adeguamenti")

(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


Questo ha risolto il problema. Funziona perfettamente e non ha rallentato il rapporto. Grazie
LynseyC il

5

Panoramica

Questa è una cosa stimolante da chiedere a PowerBI, quindi potrebbe essere difficile trovare un approccio ordinato.

Il problema maggiore è che il modello di dati di PowerBI non supporta il concetto di un conteggio in corso - almeno non nel modo in cui lo facciamo in Excel. In Excel, una colonna può fare riferimento a valori che si verificano nella "riga precedente" di quella stessa colonna e quindi essere regolata da alcune "modifiche giornaliere" elencate in una colonna diversa.

PowerBI può solo imitare ciò sommando tutte le modifiche giornaliere su alcuni sottogruppi di righe. Prendiamo il valore della data nella nostra riga corrente e creiamo una tabella filtrata in cui tutte le date sono inferiori alla data della riga corrente e quindi sommiamo tutte le modifiche giornaliere da quel sottoinsieme. Questa può sembrare una differenza sottile, ma è abbastanza significativa:

Ciò significa che non c'è modo di "sovrascrivere" il totale corrente. L'unica matematica che si sta facendo sta accadendo sulla colonna contenente le modifiche giornaliere - la colonna contenente 'totale parziale' è solo un risultato - non viene mai utilizzata nel calcolo di nessuna riga successiva.

Dobbiamo abbandonare il concetto di "reset" e invece immaginare di creare una colonna che contenga un valore di "aggiustamento". La nostra rettifica sarà un valore che può essere incluso in modo tale che quando le condizioni descritte sono soddisfatte, il totale dei saldi e delle rettifiche giornaliere sarà pari a 1.

Se osserviamo la corsa calcolata fornita da OP, vediamo che il valore del nostro totale corrente in un giorno 'non lavorativo' appena prima di un giorno 'lavorativo' ci dà quella quantità necessaria che, se invertita, si sommerebbe a zero e fa aumentare di uno il totale parziale ogni giorno lavorativo successivo. Questo è il comportamento desiderato (con un problema che verrà descritto più avanti).

Risultato

inserisci qui la descrizione dell'immagine

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

Aiuta a conoscere la differenza tra i contesti di riga e filtro e il modo in cui EARLIER opera per seguire questo calcolo. In questo scenario, puoi pensare a "EARLIER" come "questo riferimento punta al valore nella riga corrente" e altrimenti un riferimento a tutta la tabella restituita da "ALLEXCEPT (Leave, Leave [Id])." In questo modo, troviamo i luoghi in cui la riga corrente ha il tipo "Working" e la riga del giorno precedente ha un altro tipo.

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

Questo calcolo imita un tipo di operazione di "riempimento". Dice "Osservando tutte le righe la cui data è precedente alla data in QUESTA riga, restituisci il valore più grande in" Data più recente prima di lavorare ".

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

Ora che ogni riga ha un campo che spiega dove andare per trovare il saldo giornaliero da utilizzare come rettifica, possiamo semplicemente andare a cercarlo dalla tabella.

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

E infine applichiamo l'adeguamento al totale corrente per il risultato finale.

Il problema

Questo approccio non riesce a stabilire che il conteggio non deve essere ripristinato a meno che il saldo giornaliero corrente non sia inferiore a zero. Mi è stato smentito prima, ma direi che questo non può essere realizzato nel solo DAX perché crea una dipendenza circolare. In sostanza, si richiede un requisito: utilizzare il valore aggregato per determinare cosa dovrebbe essere incluso nell'aggregazione.

Quindi questo è quanto posso portarti. Spero che sia d'aiuto.


1
Per quanto riguarda il tuo ultimo punto, credo che tu abbia ragione. DAX non può eseguire la ricorsione.
Alexis Olson,

3

Spero che la prossima volta incollerai un csv o un codice che generi dati di esempio anziché immagine. :)

Lascia che ti suggerisca di eseguire invece i tuoi calcoli in PowerQuery. Ho provato a dividere il codice per alcuni passaggi per migliorare la leggibilità. Questo può sembrare un po 'più complesso, tuttavia funziona bene. Basta incollarlo nell'editor avanzato e quindi sostituire l'origine con i dati di origine. Buona fortuna!

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

Non sono sicuro che questo copra tutti gli scenari, ma sembra l'approccio giusto.
Mike Honey,

Posso farlo funzionare solo se il primo tipo per ogni persona sta funzionando. Inoltre, come con gli esempi DAX, riavvia la numerazione per un movimento di lavoro quando il totale cumulativo per la riga precedente è un numero positivo. Immagino che la mia foto fosse fuorviante in quanto conteneva solo questo scenario. Avrei dovuto includere un momento in cui il tipo è cambiato in funzionante ma il totale della riga precedente è stato positivo.
LynseyC,

@LynseyC bene, questo codice non è una soluzione perfetta e completa, ovviamente, ma piuttosto un esempio di metodi che possono essere utilizzati. Modifica solo se per il tuo scenario.
Eugene,

@LynseyC inoltre, uno dei vantaggi di questa matematica in PowerQuery piuttosto che DAX è un modo semplice per mantenere le colonne temporanee fuori dal modello dati.
Eugene,

3

Penso di averlo!

Ecco il risultato, basato sulla soluzione che ho pubblicato in precedenza: (I dati sono stati modificati per mostrare più comportamenti "lavoro / no lavoro" e casi d'uso)

RISULTATO

inserisci qui la descrizione dell'immagine

DETTAGLI

(1) Rilasciare le colonne "Saldo giornaliero corrente regolato" e "Regolazione del saldo giornaliero". Otterremo lo stesso risultato con un passo in meno in un momento.

(2) Creare la colonna seguente (RDB = "esecuzione del saldo giornaliero") ...

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

Dopo aver creato la "Data più recente prima del completamento del lavoro", abbiamo effettivamente il pezzo necessario per eseguire il nostro "reset" che prima sostenevo fosse impossibile. Filtrando su questo campo, abbiamo l'opportunità di iniziare ogni slice a '1'

(3) Abbiamo ancora lo stesso problema, non possiamo guardare il risultato nella nostra colonna e usarlo per decidere cosa fare in seguito nella stessa colonna. Ma possiamo costruire una nuova colonna di regolazione che conterrà tali informazioni! E abbiamo già un riferimento a "Data più recente prima del lavoro": è l'ultimo giorno del gruppo precedente ... la riga con le informazioni di cui abbiamo bisogno!

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)

Quindi guardiamo l'ultimo giorno in ciascun gruppo precedente e se la somma totale di tali aggiustamenti ha un valore positivo, lo applichiamo e se è negativo lo lasciamo solo. Inoltre, se i primi giorni della nostra persona sono giorni non lavorativi, non vogliamo affatto quel bit negativo iniziale nel nostro aggiustamento in modo che venga filtrato via.

(4) Quest'ultimo passaggio porterà l'adeguamento al risultato finale. Riassumi le due nuove colonne e dovremmo finalmente avere il nostro saldo giornaliero corrente rettificato. Ecco!

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

Abbiamo creato molte colonne extra lungo il percorso per questo risultato che di solito non è la mia cosa preferita da fare. Ma questo è stato difficile.


Ciao @Ryan B. Funziona perfettamente con oltre 200 persone nella mia organizzazione, ma una non funziona. Ho provato a modificare il codice da solo, ma non riesco a ottenere nulla per risolvere il problema. Penso che sia perché hanno lavorato a lungo e poi hanno lavorato solo un giorno prima di avere più tempo libero. Ho collegato un'immagine per mostrare il problema. Grazie Immagine
LynseyC,

Ho modificato la misura "Aggiustamento RDB raggruppati" in modo che debba passare grandi ratei di congedo su più cicli "lavoro / nessun lavoro".
Ryan B.

2
Ciao, grazie per tutto lo sforzo, molto apprezzato. Purtroppo la modifica non ha risolto il problema. Tuttavia, se avessi rimosso l'ultima condizione nel filtro "Lascia [Data più recente prima del completamento del lavoro] <> Vuoto ()" risolveva il problema ma poi rompeva nuovamente i calcoli della gente originale :-(
LynseyC

Sparare. Spero che tu possa trovare qualcosa che funzioni.
Ryan B.

2

Ci è voluto un po ', ma sono stato in grado di trovare una soluzione alternativa. Supponendo, il valore del saldo per gli spazi vuoti è sempre -1 e il valore è 1 per "Lavorare" e che i dati sono disponibili per tutte le date senza gap, qualcosa come il seguente calcolo potrebbe funzionare:

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

Tieni presente che potrebbe non essere un prodotto finito poiché ho lavorato con un piccolo campione, ma questo dovrebbe iniziare. Spero che sia di aiuto.


Grazie @ CR7SMS. Riavvia il totale parziale quando type = Working ma il totale corrente quando il tipo è vuoto non funziona. Per il 7 novembre si riduce a 3 ma poi dall'8 al 14 novembre restituisce -2. Puoi aiutarci a modificare il codice per far funzionare il totale parziale quando il tipo è vuoto? Grazie
LynseyC il

Ciao Lynsey, ho provato un altro calcolo. L'ho aggiunto come un'altra risposta poiché il calcolo era un po 'lungo. Speriamo che il nuovo calcolo funzioni.
CR7SMS

@ CR7SMS, evita di aggiungere più di una risposta a una singola domanda. Confonde altri utenti che potrebbero cercare un problema / soluzione simile e non è carino. Invece, dovresti aggiungere qualsiasi cosa tu possa trovare come soluzione a una risposta e dividere ogni aspetto diverso in sezioni.
Christos Lytras,

2

Il calcolo è un po 'lungo, ma sembra funzionare nei dati di esempio che sto usando. Prova questo:

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

Ho usato un sacco di variabili qui. Forse potresti inventare una versione più breve. Fondamentalmente l'idea è quella di trovare la prima occorrenza precedente di "Working" per trovare da dove iniziare il calcolo. Questo è calcolato nella variabile "Prev_Blank2". Una volta che conosciamo il punto di partenza (inizia con 1 qui), allora possiamo semplicemente contare il numero di giorni con "Working" o vuoto () tra Prev_Blank2 e la data del record corrente. In questi giorni, possiamo restituire il valore finale per il totale parziale.

Spero che questo faccia il trucco;)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.