Power BI Desktop DAX restart menjalankan total kolom


9

Saya memiliki meja di mana setiap orang memiliki catatan untuk setiap hari sepanjang tahun. Saya menggunakan fungsi ini untuk mencapai total berjalan berdasarkan kolom saldo harian

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

tapi saya perlu total menjalankan untuk memulai kembali dari 1 jika Jenis = Bekerja DAN total menjalankan Saldo Harian kurang dari nol DAN Jenis baris sebelumnya tidak sama dengan Bekerja. Di bawah ini adalah cuplikan layar dari Excel. Kolom fungsi yang diperlukan adalah apa yang saya butuhkan.

masukkan deskripsi gambar di sini


1
Pada baris tanggal 5 November, Orang 1, anggaplah data pengujian kami memiliki tipe kosong. Apakah 'fungsi wajib' mengembalikan 1 atau 2 pada 6 November?
Ryan B.

Ini akan mengembalikan 2 untuk 6 November. "Reset" tidak akan terjadi karena 5 November akan menjadi 1 (bukan angka negatif). Terima kasih atas pos detail Anda. Saya meninjau hari ini
LynseyC

Jawaban:


1

Ini bukan hanya total berjalan dengan kondisi, tetapi juga yang bersarang / berkerumun, karena logika harus diterapkan pada level ID. Untuk tabel besar, M lebih baik daripada DAX, karena tidak menggunakan banyak RAM. (Saya sudah membuat blog tentang hal itu di sini: Tautan ke Blogpost

Fungsi berikut menyesuaikan logika itu dengan kasus saat ini dan harus diterapkan pada level ID: (Nama kolom yang diperlukan adalah: "Tipe", "Tunjangan Harian", "Penyesuaian")

(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


Ini telah menyelesaikan masalah. Bekerja dengan baik dan tidak memperlambat laporan. Terima kasih
LynseyC

5

Gambaran

Ini adalah hal yang menantang untuk diminta PowerBI lakukan, sehingga pendekatan yang rapi mungkin sulit ditemukan.

Masalah terbesar adalah bahwa model data PowerBI tidak mendukung konsep penghitungan berjalan - setidaknya bukan cara kita melakukannya di Excel. Di Excel, sebuah kolom bisa mereferensikan nilai yang terjadi di 'baris sebelumnya' pada kolom yang sama dan kemudian disesuaikan dengan beberapa 'perubahan harian' yang terdaftar di kolom yang berbeda.

PowerBI hanya bisa meniru ini dengan menambahkan semua perubahan harian pada beberapa bagian baris. Kami mengambil nilai tanggal di baris kami saat ini dan membuat tabel yang difilter di mana semua tanggal kurang dari tanggal baris ini, dan kemudian menjumlahkan semua perubahan harian dari subset tersebut. Ini mungkin tampak perbedaan yang halus, tetapi cukup signifikan:

Ini berarti bahwa tidak ada cara untuk 'mengganti' total lari kami. Satu-satunya matematika yang sedang dilakukan adalah terjadi pada kolom yang berisi perubahan harian - kolom yang berisi 'total berjalan' hanyalah hasil - itu tidak pernah digunakan dalam perhitungan baris berikutnya.

Kita harus meninggalkan konsep 'reset' dan malah membayangkan membuat kolom yang berisi nilai 'penyesuaian'. Penyesuaian kami akan menjadi nilai yang dapat dimasukkan sehingga ketika kondisi yang dijelaskan dipenuhi, total saldo dan penyesuaian harian akan berjumlah 1.

Jika kita melihat running yang dihitung yang diberikan oleh OP, kita melihat bahwa nilai total running kita pada hari 'non-kerja' tepat sebelum hari 'bekerja' memberi kita jumlah yang dibutuhkan itu, jika dibalik, akan berjumlah nol dan menyebabkan total berjalan pada setiap hari kerja berikutnya bertambah satu. Ini adalah perilaku yang kita inginkan (dengan satu masalah yang akan dijelaskan nanti).

Hasil

masukkan deskripsi gambar di sini

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

Ini membantu untuk mengetahui perbedaan antara konteks baris dan filter dan bagaimana EARLIER beroperasi untuk mengikuti perhitungan ini. Dalam skenario ini, Anda dapat menganggap "EARLIER" sebagai yang berarti 'referensi ini menunjuk ke nilai di baris saat ini "dan sebaliknya sebuah referensi menunjuk ke seluruh tabel yang dikembalikan oleh" ALLEXCEPT (Leave, Leave [Id]). " cara, kami menemukan tempat-tempat di mana baris saat ini memiliki tipe "Bekerja" dan baris hari sebelumnya memiliki beberapa jenis lainnya.

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

Perhitungan ini meniru jenis operasi 'isi'. Dikatakan, "Ketika melihat semua baris yang tanggalnya sebelum tanggal pada baris INI, kembalikan nilai terbesar dalam 'Tanggal Terbaru Sebelum Bekerja."

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

Sekarang setiap baris memiliki bidang yang menjelaskan ke mana harus mencari saldo harian untuk digunakan sebagai penyesuaian kami, kita bisa mencarinya dari tabel.

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

Dan akhirnya kami menerapkan penyesuaian pada total berjalan kami untuk hasil akhir.

Masalah

Pendekatan ini gagal menunjukkan bahwa penghitungan tidak boleh direset kecuali saldo harian berjalan di bawah nol. Saya telah terbukti salah sebelumnya, tetapi saya akan mengatakan bahwa ini tidak dapat diselesaikan dalam DAX saja karena itu menciptakan ketergantungan melingkar. Pada dasarnya, Anda membuat persyaratan: gunakan nilai agregat untuk menentukan apa yang harus dimasukkan dalam agregasi.

Jadi sejauh yang bisa saya bawa. Semoga ini bisa membantu.


1
Mengenai poin terakhir Anda, saya yakin Anda benar. DAX tidak dapat melakukan rekursi.
Alexis Olson

3

Semoga lain kali Anda akan menempelkan csv atau kode yang menghasilkan data sampel, bukan gambar. :)

Biarkan saya menyarankan Anda melakukan perhitungan di PowerQuery saja. Saya mencoba memecah kode untuk beberapa langkah untuk meningkatkan keterbacaan. Ini mungkin terlihat sedikit lebih rumit, namun bekerja dengan baik. Cukup tempel di editor tingkat lanjut dan ganti sumber dengan data sumber Anda. Semoga berhasil!

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

Saya tidak yakin ini mencakup setiap skenario, tetapi sepertinya pendekatan yang tepat.
Mike Honey

Saya hanya bisa mengaktifkan ini jika jenis pertama untuk setiap orang adalah Bekerja. Seperti halnya dengan contoh DAX, ia memulai kembali penomoran untuk gerakan yang bekerja ketika total kumulatif untuk baris sebelumnya adalah angka positif. Saya kira gambar saya menyesatkan karena hanya memuat skenario ini. Saya seharusnya memasukkan waktu ketika tipe berubah menjadi berfungsi tetapi total baris sebelumnya adalah positif.
LynseyC

@LynseyC, kode ini bukan solusi yang sempurna dan lengkap, tentu saja, melainkan contoh metode yang dapat digunakan. Cukup modifikasi jika untuk skenario Anda.
Eugene

@LynseyC juga, salah satu keuntungan melakukan matematika ini di PowerQuery daripada DAX adalah cara mudah untuk menjaga kolom temp dari model data.
Eugene

3

Saya rasa saya memilikinya!

Inilah hasilnya, membangun solusi yang saya posting sebelumnya: (Data telah dimodifikasi untuk memamerkan lebih banyak perilaku "bekerja / tidak bekerja" dan menggunakan kasus)

HASIL

masukkan deskripsi gambar di sini

RINCIAN

(1) Drop Kolum "Saldo Harian Disesuaikan" dan "Penyesuaian Saldo Harian". Kami akan mendapatkan hasil yang sama dengan satu langkah lebih sedikit hanya dalam beberapa saat.

(2) Buat kolom berikut (RDB = "saldo harian berjalan") ...

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

Setelah membuat "Tanggal Terkini Sebelum Bekerja Selesai," kami sebenarnya memiliki bagian yang diperlukan untuk melakukan 'reset' yang saya klaim tidak mungkin dilakukan sebelumnya. Dengan memfilter pada bidang ini, kami memiliki kesempatan untuk memulai setiap irisan di '1'

(3) Kami masih memiliki masalah yang sama, kami tidak dapat melihat hasilnya di kolom kami dan menggunakannya untuk memutuskan apa yang harus dilakukan nanti di kolom yang sama. Tapi kami BISA membangun kolom penyesuaian baru yang akan menampung info itu! Dan kami sudah memiliki referensi ke 'Tanggal Terbaru Sebelum Bekerja' - itu adalah hari terakhir di grup sebelumnya ... baris dengan informasi yang kami butuhkan!

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)

Jadi kami melihat hari terakhir di Setiap grup sebelumnya dan jika jumlah total penyesuaian tersebut memiliki nilai positif, kami menerapkannya dan jika negatif kami membiarkannya saja. Juga, jika beberapa hari pertama orang kami adalah hari yang tidak bekerja, kami sama sekali tidak ingin hal negatif awal dalam penyesuaian kami sehingga bisa disaring juga.

(4) Langkah terakhir ini akan membawa penyesuaian ke hasil akhir. Ringkas dua kolom baru dan akhirnya kita harus memiliki Saldo Harian Berjalan Disesuaikan. Voila!

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

Kami membuat banyak kolom tambahan di sepanjang jalan menuju hasil ini yang biasanya bukan hal favorit saya untuk dilakukan. Tapi, ini rumit.


Hai @Ryan B. Ini berfungsi dengan baik untuk lebih dari 200 orang di organisasi saya tetapi satu tidak berfungsi. Saya sudah mencoba mengubah kode sendiri tetapi saya tidak bisa mendapatkan apa pun untuk menyelesaikan masalah. Saya pikir itu karena mereka telah bekerja lama dan kemudian bekerja hanya sehari sebelum memiliki lebih banyak waktu istirahat. Saya telah menautkan ke gambar untuk menunjukkan masalah. Terima kasih Image
LynseyC

Saya telah memodifikasi ukuran "Penyesuaian RDB yang Dikelompokkan" sehingga harus lulus cuti akrual besar di beberapa siklus "kerja / tidak bekerja".
Ryan B.

2
Hai, terima kasih untuk semua usahanya, sangat kami hargai. Sayangnya modifikasi tidak menyelesaikan masalah. Namun jika saya menghapus kondisi terakhir di filter "Biarkan [Tanggal Terbaru Sebelum Pekerjaan Selesai] <> Kosong ()" itu menyelesaikan masalah tetapi kemudian memecahkan lagi kalori orang asli :-(
LynseyC

Menembak. Yah, saya harap Anda dapat menemukan sesuatu yang berfungsi.
Ryan B.

2

Butuh waktu beberapa saat, tetapi saya bisa menemukan solusi. Dengan asumsi, nilai saldo untuk kosong selalu -1 dan nilainya 1 untuk "Bekerja" dan bahwa data tersedia untuk semua tanggal tanpa celah, sesuatu seperti perhitungan di bawah ini bisa berfungsi:

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

Perlu diingat, ini mungkin bukan produk jadi karena saya bekerja dengan sampel kecil, tetapi ini harus Anda mulai. Semoga ini membantu.


Terima kasih @ CR7SMS. Ini me-restart total berjalan ketika tipe = Bekerja tetapi total berjalan ketika tipe kosong tidak bekerja. Untuk 7 Nov dikurangi menjadi 3 tetapi kemudian dari 8-14 November kembali -2. Bisakah Anda membantu mengubah kode agar total berjalan berjalan ketika jenisnya kosong? Terima kasih
LynseyC

Hai Lynsey, saya mencoba perhitungan yang berbeda. Saya telah menambahkannya sebagai jawaban lain karena perhitungannya agak panjang. Tapi semoga perhitungannya baru berhasil.
CR7SMS

@ CR7SMS harap hindari menambahkan lebih dari satu jawaban untuk satu pertanyaan. Ini membingungkan pengguna lain yang mungkin mencari masalah / solusi yang sama dan itu tidak baik. Alih-alih, Anda harus menambahkan apa pun yang Anda temukan sebagai solusi untuk satu jawaban dan membagi setiap aspek menjadi beberapa bagian.
Christos Lytras

2

Perhitungannya agak panjang, tetapi tampaknya berfungsi dalam data sampel yang saya gunakan. Cobalah ini:

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

Saya telah menggunakan banyak variabel di sini. Anda mungkin dapat membuat versi yang lebih pendek. Pada dasarnya idenya adalah untuk menemukan kemunculan pertama "Bekerja" untuk menemukan dari mana memulai perhitungan. Ini dihitung dalam variabel "Prev_Blank2". Setelah kita tahu titik awalnya (dimulai dengan 1 di sini), maka kita cukup menghitung jumlah hari dengan "Bekerja" atau kosong () di antara Prev_Blank2 dan tanggal catatan saat ini. Dengan menggunakan hari-hari ini, kami dapat mengembalikan nilai akhir untuk menjalankan total.

Semoga ini berhasil;)

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.