Power BI Desktop DAX yeniden başlatma toplam sütununu çalıştır


9

Herkesin yılın her günü için rekoru olduğu bir masam var. Bu fonksiyonu günlük bakiye sütununa dayalı bir toplam elde etmek için kullandım

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

ancak Type = Working AND Daily Balance'ın çalışan toplamı sıfırdan küçükse VE önceki satırın Type'ı Working'e eşit değilse, 1'den yeniden başlatmak için çalışan toplama ihtiyacım var. Aşağıda Excel'den bir ekran görüntüsü verilmiştir. Gerekli işlev sütunu almak gerekir.

resim açıklamasını buraya girin


1
5 Kasım satırında, Kişi 1, test verilerimizin türünün boş olduğunu varsayalım. 'Gerekli işlev' 6 Kasım'da 1 veya 2 döndürür mü?
Ryan B.

6 Kasım için 2 döndürür. 5 Kasım 1 (negatif sayı değil) olacağından "sıfırlama" gerçekleşmez. Ayrıntılı yazınız için teşekkürler. Bugün gözden geçiriyorum
LynseyC

Yanıtlar:


1

Bu sadece bir koşulu olan bir toplam değil, aynı zamanda iç içe / kümelenmiş bir toplamdır, çünkü mantık kimlik düzeyinde uygulanmalıdır. Büyük tablolar için M, çok fazla RAM kullanmadığı için DAX'tan daha iyidir. (Bu konuda burada blog yazdım: yazdım Blogpost bağlantısı

Aşağıdaki işlev bu mantığı geçerli duruma uyarlar ve kimlik düzeyinde uygulanmalıdır: (Gerekli sütun adları şunlardır: "Tür", "Günlük İzin", "Ayarlamalar")

(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


Bu sorunu çözdü. Mükemmel çalışıyor ve raporu yavaşlatmadı. Thanks
LynseyC

5

genel bakış

Bu, PowerBI'den yapmasını istemek zor bir şeydir, bu nedenle düzenli bir yaklaşım bulmak zor olabilir.

En büyük sorun, PowerBI'nin veri modelinin çalışan bir çetele kavramını desteklememesi - en azından Excel'de yaptığımız gibi değil. Excel'de bir sütun, aynı sütunun 'önceki satırında' ortaya çıkan değerlere başvurabilir ve ardından farklı bir sütunda listelenen bazı 'günlük değişiklikler' ile ayarlanabilir.

PowerBI bunu ancak bazı satır alt kümelerine tüm günlük değişiklikleri toplayarak taklit edebilir. Geçerli satırımızdaki tarih değerini alır ve tüm tarihlerin bu geçerli satırın tarihinden daha az olduğu filtrelenmiş bir tablo oluştururuz ve ardından bu alt kümedeki tüm günlük değişiklikleri toplarız. Bu ince bir fark gibi görünebilir, ancak oldukça önemlidir:

Bu, çalışan toplamımızı 'geçersiz kılmanın' hiçbir yolu olmadığı anlamına gelir. Yapılan tek matematik günlük değişiklikleri içeren sütunda gerçekleşiyor - 'toplam çalışan' içeren sütun sadece bir sonuçtur - daha sonraki satırların hesaplamasında asla kullanılmaz.

'Sıfırlama' kavramından vazgeçmeli ve bunun yerine 'ayar' değeri içeren bir sütun oluşturmayı hayal etmeliyiz. Düzenlememiz, açıklanan koşullar karşılandığında günlük bakiyeler ve düzenlemelerin toplamı 1 olacak şekilde eklenebilecek bir değer olacaktır.

OP tarafından verilen hesaplanan koşuya bakarsak, bir 'çalışma' gününden hemen önce 'çalışmayan' bir günde koşu toplamımızın değerinin bize, eğer tersine çevrilirse, sıfıra ve takip eden her iş gününde toplamın birer birer artmasına neden olur. Bu bizim arzulanan davranışımızdır (daha sonra açıklanacak bir problem ile).

Sonuç

resim açıklamasını buraya girin

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

Satır ve filtre bağlamları arasındaki farkı ve EARLIER'ın bu hesaplamayı takip etmek için nasıl çalıştığını bilmeye yardımcı olur. Bu senaryoda, "EARLIER" anlamını "bu referans geçerli satırdaki değere işaret eder" ve aksi takdirde "ALLEXCEPT (Ayrıl, Bırak [Id])" ile döndürülen tüm tabloya işaret eder. mevcut satırın "Çalışıyor" ve önceki günün satırının başka bir türü olduğu yerleri buluruz.

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

Bu hesaplama 'doldurma' türünde bir işlemi taklit eder. "Tarihi BU satırdaki tarihten önce olan tüm satırlara bakarken," Çalışmadan Önce En Son Tarih "deki en büyük değeri döndürün.

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

Artık her satırın, ayarlamalarımız olarak kullanmak için günlük dengeyi nerede bulacağınızı açıklayan bir alanı olduğundan, tablodan bakabiliriz.

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

Son olarak ayarlamayı nihai sonuç için koşu toplamımıza uygularız.

Sorun

Bu yaklaşım, çalışan günlük bakiye sıfırın altında olmadığı sürece sayımın sıfırlanmaması gerektiğini belirtmez. Daha önce yanlış olduğumu kanıtladım, ancak bunun sadece DAX'ta gerçekleştirilemeyeceğini söyleyebilirim çünkü dairesel bir bağımlılık yaratır. Temel olarak, bir gereksinim yaparsınız: toplamaya neyin dahil edilmesi gerektiğini belirlemek için toplanmış değeri kullanın.

Bu yüzden size getirebildiğim kadarıyla. Umarım yardımcı olur.


1
Son noktanıza gelince, haklı olduğuna inanıyorum. DAX özyineleme yapamaz.
Alexis Olson

3

Bir dahaki sefere resim yerine örnek veri üreten bir csv veya kod yapıştıracağınızı umuyoruz. :)

Bunun yerine hesaplarınızı PowerQuery'de yapmanızı öneriyorum. Okunabilirliği artırmak için kodu birkaç adımda bölmeye çalıştım. Bu biraz daha karmaşık görünebilir, ancak iyi çalışır. Sadece gelişmiş düzenleyiciye yapıştırın ve ardından kaynağı kaynak verilerinizle değiştirin. İyi şanslar!

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

Bunun her senaryoyu kapsadığından emin değilim, ama doğru yaklaşım gibi görünüyor.
Mike Honey

Bunu ancak her kişi için ilk tip Çalışıyorsa çalıştırabilirim. Ayrıca DAX örneklerinde olduğu gibi, önceki satırın kümülatif toplamı pozitif bir sayı olduğunda, Çalışma hareketi için numaralandırmayı yeniden başlatır. Sanırım resmim sadece bu senaryoyu içerdiği için yanıltıcıydı. Tür çalışma olarak değişti bir zaman eklemeliydim ama önceki satır toplamı olumlu oldu.
LynseyC

@LynseyC iyi, bu kod elbette mükemmel ve eksiksiz bir çözüm değil, kullanılabilecek yöntemlere bir örnektir. Sadece senaryonuz için değiştirin.
Eugene

@LynseyC, bu matematiği DAX yerine PowerQuery'de yapmanın avantajlarından biri, geçici sütunları veri modelinden uzak tutmanın kolay bir yoludur.
Eugene

3

Sanırım bende var!

Daha önce yayınladığım çözümü temel alan sonuç şöyledir: (Veriler, daha fazla "iş / iş yok" davranışı ve kullanım senaryoları gösterecek şekilde değiştirildi)

SONUÇ

resim açıklamasını buraya girin

DETAYLAR

(1) "Düzeltilmiş Günlük Günlük Bakiye" ve "Günlük Denge Ayarı" Sütunlarını bırakın. Aynı sonucu bir dakika içinde daha az adımla elde edeceğiz.

(2) Aşağıdaki sütunu oluşturun (RDB = "günlük bakiyeyi çalıştırıyor") ...

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

"Çalışmadan Önce En Son Tarih" i oluşturduktan sonra, aslında daha önce imkansız olduğunu iddia ettiğim "sıfırlama" yı yapmak için gereken parçaya sahibiz. Bu alana filtre uygulayarak, her dilimi '1'de başlatma olanağına sahibiz.

(3) Hala aynı problemimiz var, sütundaki sonuca bakamayız ve bunu aynı sütunda daha sonra ne yapacağımıza karar vermek için kullanamayız. Ancak bu bilgileri tutacak yeni bir ayarlama sütunu oluşturabiliriz! Ve zaten 'İşten Önce En Son Tarih'e bir referansımız var - bu bir önceki gruptaki son gün ... ihtiyacımız olan bilgileri içeren satır!

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)

Yani her gün son güne bakıyoruz. önceki gruptaki bu ayarlamaların toplamı pozitif bir değere sahipse onu uygularız ve eğer negatifse onu yalnız bırakırız. Ayrıca, eğer kişimizin ilk birkaç günü iş günü değilse, ayarımızda ilk negatif bitin hiç olmasını istemiyoruz, bu yüzden de filtreleniyor.

(4) Bu son adım, düzeltmeyi nihai sonuca getirecektir. İki yeni sütunu toplayın ve nihayet Düzeltilmiş Çalışan Günlük Bakiyemize sahip olmalıyız. İşte bu kadar!

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

Bu sonuca giden yolda genellikle benim en sevdiğim şey olmayan bir sürü ekstra sütun oluşturduk. Ama bu zor bir işti.


Merhaba @Ryan B. Bu, kuruluşumda 200'den fazla kişi için mükemmel çalışıyor ancak biri çalışmıyor. Kodu kendim değiştirmeye çalıştım ama sorunu çözmek için hiçbir şey alamıyorum. Bence uzun zaman çalıştılar ve sonra daha fazla zaman geçirmeden sadece bir gün önce çalıştılar. Sorunu göstermek için bir resme bağlandım. Teşekkürler Image
LynseyC

"Gruplandırılmış RDB Ayarlaması" ölçüsünü değiştirdim, böylece birden fazla "çalışma / çalışma yok" döngüsü boyunca büyük izin tahakkukları geçmelidir.
Ryan B.

2
Merhaba, tüm çaba için teşekkürler, çok takdir etmek. Maalesef değişiklik sorunu çözmedi. Ancak, filtrede son koşulu kaldırırsam [İşi Tamamlamadan Önce En Son Tarih] <> Boş () "sorunu çözdü, ancak orijinal kişilerin hesaplarını tekrar kırdı :-(
LynseyC

Ateş etmek. Umarım işe yarayan bir şey bulabilirsin.
Ryan B.

2

Biraz zaman aldı, ama bir çözüm buldum. Boşluklar için denge değeri her zaman -1 ve değer "Çalışma" için 1'dir ve veriler boşluk olmadan tüm tarihler için kullanılabilir, aşağıdaki hesaplama gibi bir şey işe yarayabilir:

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

Unutmayın, küçük bir örnekle çalıştığım için bu bitmiş bir ürün olmayabilir, ancak bu işe başlamanız gerekir. Bu yardımcı olur umarım.


CR7SMS @ teşekkürler. Type = Çalışma olduğunda toplam çalışanı yeniden başlatır, ancak tip boş olduğunda çalışan toplamı yeniden çalıştırmaz. 7 Kasım için 3'e düşer, ancak 8-14 Kasım'dan -2'ye döner. Tür boş olduğunda çalışan toplamın çalışması için kodu değiştirmede yardımcı olabilir misiniz? Teşekkürler
LynseyC

Merhaba Lynsey, farklı bir hesaplama denedim. Hesaplama biraz uzun olduğu için başka bir cevap olarak ekledim. Ama umarım yeni hesaplama işe yarar.
CR7SMS

@ CR7SMS lütfen tek bir soruya birden fazla cevap eklemekten kaçının. Benzer bir sorun / çözüm arayabilecek diğer kullanıcıları karıştırır ve hoş değildir. Bunun yerine, bir cevaba çözüm olarak gelebilecek her şeyi eklemeli ve her farklı yönü bölümlere ayırmalısınız.
Christos Lytras

2

Hesaplama biraz uzun, ancak kullandığım örnek verilerde çalışıyor gibi görünüyor. Bunu deneyin:

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

Burada bir sürü değişken kullandım. Belki daha kısa bir sürümle gelebilirsiniz. Temel olarak, hesaplamaya nereden başlanacağını bulmak için önceki "Çalışma" nın ilk örneğini bulmaktır. Bu, "Prev_Blank2" değişkeninde hesaplanır. Başlangıç ​​noktasını öğrendikten sonra (burada 1 ile başlar), Prev_Blank2 ile geçerli kaydın tarihi arasında "Çalışma" veya blank () ile gün sayısını sayabiliriz. Bu günleri kullanarak, toplam koşu için nihai değeri döndürebiliriz.

Umarım bu hile yapar;)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.