Power BI Desktop DAXの再実行の合計列


9

私はすべての人がその年の毎日の記録を持っているテーブルを持っています。この関数を使用して、日次残高列に基づいて現在までの合計を達成しました

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

しかし、Type = Workingであり、Daily Balanceの実行合計がゼロ未満で、かつ前の行のTypeがWorkingに等しくない場合、実行合計は1から再開する必要があります。以下はExcelからのスクリーンショットです。必要な関数の列は、取得する必要があるものです。

ここに画像の説明を入力してください


1
11月5日の1行目のPerson 1で、テストデータの型が空白であるとします。「必須機能」は11月6日に1または2を返しますか?
ライアンB.

11月6日の場合は2を返します。11月5日が1(負の数ではない)になるため、「リセット」は行われません。あなたの詳細な投稿をありがとう。私は今日レビューしています
LynseyC

回答:


1

ロジックはIDレベルで適用する必要があるため、これは条件付きの実行中の合計だけでなく、ネスト/クラスター化された合計でもあります。大きなテーブルの場合、MはRAMをあまり使用しないため、DAXよりも優れています。(私はここでそれについてブログを書いています: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


これで問題は解決しました。完全に動作し、レポートの速度が低下していません。ありがとう
LynseyC

5

概観

これはPowerBIに依頼するのが難しいことなので、整然としたアプローチを見つけるのは難しいかもしれません。

最大の問題は、PowerBIのデータモデルが、実行中の集計の概念をサポートしていないことです。少なくとも、Excelで行う方法はそうではありません。Excelでは、列は同じ列の「前の行」にある値を参照し、別の列にリストされている「毎日の変更」によって調整できます。

PowerBIは、行の一部のサブセットで毎日のすべての変更を合計することによってのみこれを模倣できます。現在の行の日付値を取得し、すべての日付がこの現在の行の日付よりも小さいフィルター処理されたテーブルを作成してから、そのサブセットからのすべての毎日の変更を合計します。これは微妙な違いに思えるかもしれませんが、非常に重要です。

つまり、現在の合計を「上書き」する方法はありません。行われている唯一の計算は、毎日の変更を含む列で行われます。「積算合計」を含む列は結果にすぎません。後続の行の計算では決して使用されません。

「リセット」の概念を放棄し、代わりに「調整」値を含む列を作成することを想像する必要があります。私たちの調整は、記載された条件が満たされたときに、1日の残高と調整の合計が1になるように含めることができる値になります。

OPによって計算されたランニングを見ると、「稼働」日の直前の「非稼働」日のランニング合計の値は、逆にした場合、合計がゼロになり、翌営業日の累計を1つ増やします。これが望ましい動作です(後で説明する1つの問題があります)。

結果

ここに画像の説明を入力してください

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])」によって返されるテーブル全体を指します。つまり、現在の行のタイプが「Working」であり、前日の行のタイプが他のタイプである場所を見つけます。

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

これで、すべての行に、調整として使用する1日の残高を探す場所を説明するフィールドができたので、テーブルからそれを調べることができます。

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

最後に、最終結果の現在の合計に調整を適用します。

問題

このアプローチでは、実行中の1日の残高がゼロを下回らない限り、カウントがリセットされないようにする必要があります。私は以前に間違っていることが証明されましたが、循環依存関係を作成するため、これはDAXだけでは達成できないと言えます。基本的に、要件を作成します。集約された値を使用して、集約に何を含めるかを決定します。

それで、私はあなたを連れて行くことができます。それが役に立てば幸い。


1
あなたの最後の点について、私はあなたが正しいと信じています。DAXは再帰を実行できません。
アレクシスオルソン

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

これがすべてのシナリオをカバーするかどうかはわかりませんが、正しいアプローチのようです。
マイクハニー

これが機能するのは、各人の最初のタイプが「作業中」の場合のみです。また、DAXの例と同様に、前の行の累積合計が正の数になると、Workingムーブメントの番号付けを再開します。私の写真はこのシナリオしか含まれていないため、誤解を招くものだったと思います。タイプが機能するように変更された時間を含める必要がありましたが、前の行の合計は正でした。
LynseyC

@LynseyCまあ、もちろん、このコードは完璧で完全なソリューションではなく、使用できるメソッドの例です。シナリオに合わせて変更してください。
ユージーン

@LynseyCも、DAXではなくPowerQueryでこの計算を行う利点の1つは、一時列をデータモデルから切り離す簡単な方法です。
ユージーン

3

私はそれを持っていると思います!

以前に投稿したソリューションを基にした結果は次のとおりです。

結果

ここに画像の説明を入力してください

詳細

(1)「Adjusted Running Daily Balance」および「Daily Balance Adjustment」列をドロップします。わずか1ステップ少ない同じ結果が得られます。

(2)次の列を作成します(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]) 
))

「作業完了前の最新の日付」を作成したので、以前は不可能だと主張していた「リセット」を実行するために必要な部分が実際にあります。このフィールドでフィルタリングすることにより、各スライスを「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)この最後のステップは、最終結果に調整をもたらします。2つの新しい列を合計すると、最終的に調整済みの実行中の毎日のバランスが得られます。出来上がり!

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

この結果に至るまでに多くの追加の列を作成しましたが、これは通常、私の好きなことではありません。しかし、これはトリッキーなものでした。


こんにちは@ライアンB.これは私の組織の200人以上の人々に完全に機能しますが、1人は機能していません。自分でコードを変更しようとしましたが、問題を解決するために何かを得ることができません。それは彼らが長い間働いていて、それからもっと休む前にたった一日だけ働いたからだと思います。問題を示すために画像にリンクしました。ありがとう 画像
LynseyC

「グループ化されたRDB調整」メジャーを変更して、複数の「作業/作業なし」サイクルにわたって休暇の大きな見越しを通過させる必要があります。
ライアンB.

2
こんにちは、すべての努力に感謝します。残念ながら、修正によって問題は解決しませんでした。ただし、フィルター「Leave [作業完了前の最新の日付] <> Blank()」の最後の条件を削除した場合、問題は解決しましたが、元の人々の計算が再び破られました:-(
LynseyC

シュート。まあ、私はあなたがうまくいく何かを見つけることができると思います。
ライアンB.

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

小さなサンプルで作業したため、これは完成品ではない可能性があることに注意してください。ただし、これで開始できます。お役に立てれば。


@ CR7SMSに感謝します。タイプ=機能している場合は積算合計が再開されますが、タイプが空白の場合は積算合計が機能しません。11月7日の場合は3に減少しますが、8月14日からは2を返します。タイプが空白の場合に、積算合計が機能するようにコードを修正できますか?ありがとう
LynseyC

こんにちはリンジー、私は別の計算を試みました。計算が少し長かったので、別の回答として追加しました。しかし、うまくいけば、新しい計算がうまくいきます。
CR7SMS

@ CR7SMSでは、1つの質問に複数の回答を追加しないでください。同様の問題/解決策を探す可能性のある他のユーザーを混乱させ、それは良くありません。代わりに、1つの回答の解決策として考えられるものをすべて追加し、すべての異なる側面をセクションに分割する必要があります。
Christos Lytras

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

ここでは多くの変数を使用しました。あなたはおそらくもっと短いバージョンを考え出すことができるでしょう。基本的には、「Working」の前の最初の出現を見つけて、どこから計算を開始するかを見つけるという考え方です。これは、変数「Prev_Blank2」で計算されます。開始点(ここでは1から始まります)がわかったら、Prev_Blank2と現在のレコードの日付の間にある "Working"またはblank()を使用して日数を数えるだけです。これらの日を使用して、積算合計の最終値を返すことができます。

うまくいけば、これはトリックを行います;)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.