Power BI Desktop DAX перезапустить загальну колонку


9

У мене є таблиця, де кожна людина має запис за кожен день року. Я використовував цю функцію для досягнення загальної сумарної кількості на основі стовпця щоденного балансу

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

але мені потрібен загальний обсяг, щоб перезапустити з 1, якщо Тип = Робочий І загальний показник щоденного балансу менше нуля І Тип попереднього рядка не дорівнює Робочому. Нижче - знімок екрана з Excel. Необхідний стовпець функції - це те, до чого мені потрібно звернутися.

введіть тут опис зображення


1
На ряду за 5 листопада, особа 1, припустимо, що наші тестові дані мали пробіл. Чи поверне «необхідна функція» 6 або 2 листопада?
Райан Б.

Це поверне 2 за 6 листопада. "Скидання" не відбудеться, оскільки 5 листопада буде 1 (не від'ємне число). Дякуємо за детальну публікацію. Я переглядаю сьогодні
LynseyC

Відповіді:


1

Це не тільки поточний загальний з умовою, але і вкладений / кластеризований, оскільки логіка повинна застосовуватися на рівні ID. Для великих таблиць M краще в ньому, ніж 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.

Якщо ми подивимось на розрахунковий показник, заданий ОП, то побачимо, що значення нашої загальної суми за «неробочий» день безпосередньо перед «робочим» днем ​​дає нам необхідну суму, яка, якщо буде зворотна, складе нуль і зробіть загальний обсяг кожного наступного робочого дня на одиницю. Це наша бажана поведінка (одна проблема буде описана далі).

Результат

введіть тут опис зображення

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])". Таким чином, ми знаходимо місця, де поточний рядок має тип "Робочий", а рядок попереднього дня має якийсь інший тип.

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

Тепер, коли в кожному рядку є поле, в якому пояснюється, куди звернутися, щоб знайти щоденний баланс, який слід використовувати як наше коригування, ми можемо просто переглянути його з таблиці.

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

І, нарешті, ми застосовуємо коригування до нашої загальної суми для остаточного результату.

Питання

Цей підхід не дозволяє вирішити, що підрахунок не повинен скидатися, якщо поточний щоденний баланс не перевищує нуля. Мені раніше було доведено неправильно, але я б сказав, що цього неможливо зробити лише в 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, він відновлює нумерацію робочого руху, коли сукупний загальний результат для попереднього рядка є додатним числом. Я думаю, що моя картина була оманливою, оскільки вона містила лише цей сценарій. Я повинен був включити час, коли тип змінився на робочий, але попередній підсумковий рядок був позитивним.
LynseyC

@ LynseyC, звичайно, цей код не є ідеальним і повним рішенням, але, скоріше, прикладом методів, які можна використовувати. Просто змініть, якщо для вашого сценарію.
Євген

@LynseyC також одна з переваг, яка займається такою математикою в PowerQuery, а не DAX, - це простий спосіб утримати темп-стовпці від моделі даних.
Євген

3

Я думаю, що в мене є!

Ось результат, спираючись на рішення, яке я опублікував раніше: (Дані були змінені, щоб показати більше поведінки "без роботи" та використання випадків)

РЕЗУЛЬТАТ

введіть тут опис зображення

ДЕТАЛІ

(1) Скиньте колони "Налаштований щоденний баланс" та "Щоденний баланс". Ми отримаємо той самий результат одним меншим кроком лише за мить.

(2) Створіть наступний стовпець (RDB = "ведення щоденного балансу") ...

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) Цей останній крок приведе коригування в кінцевий результат. Підведіть підсумки двох нових стовпців, і ми, нарешті, повинні мати скоригований щоденний баланс. Вуаля!

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

Ми створили багато додаткових стовпців на шляху до цього результату, що зазвичай не є моєю улюбленою справою. Але, це було хитро.


Привіт @Ryan B. Це прекрасно працює для понад 200 людей у ​​моїй організації, але одна не працює. Я сам спробував змінити код, але нічого не можу отримати, щоб вирішити проблему. Я думаю, це тому, що вони довго працювали, а потім працювали лише один день, перш ніж мати більше часу. Я пов’язав зображення, щоб показати проблему. Завдяки Image
LynseyC

Я змінив захід "Групова корекція RDB", щоб він міг проходити великі нарахування відпусток протягом декількох циклів "робота / без роботи".
Райан Б.

2
Привіт, спасибі за всі зусилля, дуже вдячний. На жаль, модифікація не вирішила проблему. Однак якщо я видалив останню умову у фільтрі "Залишити [Найновіша дата перед початком роботи] <> Blank ()", це вирішило проблему, але потім воно порушило початкові
калькування

Стріляти. Ну, я сподіваюся, ви зможете знайти щось, що працює.
Райан Б.

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. Він перезапускає загальний запуск, коли type = Робочий, але загальний запуск, коли тип порожній, не працює. Для 7 листопада вона зменшується до 3, але потім з 8-14 листопада повертається -2. Чи можете ви допомогти зі зміною коду, щоб змусити загальний облік працювати, коли тип порожній? Спасибі
LynseyC

Привіт Лінсей, я спробував інший розрахунок. Я додав це як ще одну відповідь, оскільки розрахунок був трохи довгим. Але, сподіваємось, новий розрахунок спрацює.
CR7SMS

@ CR7SMS, будь-ласка, уникайте додавання більш ніж одного відповіді на одне запитання. Це бентежить інших користувачів, які можуть шукати подібну проблему / рішення, і це не приємно. Натомість слід додати те, що ви можете вирішити як одну відповідь, і розділити кожен різний аспект на розділи.
Крістос Літрас

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

Я тут використав купу змінних. Можливо, ви зможете придумати більш коротку версію. В основному ідея полягає в тому, щоб знайти попереднє перше виникнення "Робочої", щоб знайти, з чого почати розрахунок. Це обчислюється у змінній "Prev_Blank2". Як тільки ми знаємо початкову точку (вона починається з 1 тут), ми можемо просто порахувати кількість днів із "Робочим" або порожнім () між Prev_Blank2 та датою поточного запису. Користуючись цими днями, ми можемо повернути остаточне значення для загальної кількості.

Сподіваємось, це робить трюк;)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.