Отчёт из плоской таблицы с группировкой информации о товаре, Из плоской таблицы получить отчёт с многоуровневой группировкой и несколькими атрибутами (столбцами) для каждой строки
ДАНО Есть таблица товаров, собранная в виде запроса Power Query. У товаров несколько столбцов характеристик: название товара, цвет, размер, цена за ед., требуемое кол-во на разных торговых точках, товарная группа, вид монтажа (наружный, скрытый).
ЗАДАЧА Из таблицы необходимо получить отчёт (спецификацию для закупки) с иерархической группировкой строк по виду монтажа > товарной группе > названию товара и агрегированием кол-ва однотипных товаров на нескольких торговых точках. При этом необходимо, чтобы помимо агрегированного кол-ва товара в каждой торговой точке, в каждой строке для данного названия товара присутствовали все остальные его характеристики (цвет, размер и т.д.). Плюс к этому нужен еще один вычисляемый столбец, в котором указана итоговая сумма по каждой позиции (цена за ед. * общее кол-во по всем торговым точкам).
ИДЕИ РЕАЛИЗАЦИИ Была идея использовать для отчёта сводную таблицу, но разобраться как в строках разместить характеристики товара помимо его названия и добавить вычисляемый столбец не смог.
В PQ есть возможность вставить текстовое значение в подобную кросс-таблицу (через функцию Столбец сведения), но она, насколько я разобрался, позволяет добавить в итоговую таблицу только один столбец с текстовым значением, а в данном случае таких характеристик 4.
Есть идея попробовать проиндексировать все строки исходной таблицы и использовать этот индекс, чтобы вставить соответствующее значение характеристики товара в таблицу целевого отчёта. В эту сторону подтолкнула идея вспомогательной сводной из примера Сводная таблица с текстом в значениях. Но не знаю, сработает ли он и ещё интуиция подсказывает, что должен быть более короткий путь. -> Проверил не подходит: при изменении значений атрибутов или их кол-ва, придется перелопачивать всю трансформацию данных.
Если в Excel такой отчёт нереализуем и нужно смотреть в сторону в сторону Power BI это тоже приемлемый вариант. Просто Excel как средства отчёта в данном случае предпочтительнее с точки зрения пользователя.
Шура, сведение просто, но создание пустых строк и уровней могу предложить только через полный ручной костылинг - один уровень создал - дальше сами п.с.: переименовывать столбцы было лень п.с.2:, нет, костыли такие плохие, неизвестно кол-во срок и уровней заранее, чисто вручную - ерунда, проще выгрузить на лист Excel и раскидать.... Как вариант приходит смутно в голову размножить конечную таблицу в несколько запросов, проставить везде индексы со смещением начала отсчета индекса на +1, а затем Join по индексам...Но навряд ли, т.к. неизвестно кол-во уровней. Может кто-то доделает.
Скрытый текст
Код
let
Источник = Excel.CurrentWorkbook(){[Name="Таблица1"]}[Content],
изм_тип = Table.TransformColumnTypes(Источник,{{"Название", type text}, {"Вид монтажа", type text}, {"Товарная группа", type text}, {"Цвет", type text}, {"Размер", type text}, {"Цена за ед.", type text}}),
сцепить = Table.AddColumn(изм_тип, "Пользовательский", each [Название]&"/"&[Вид монтажа]&"/"&[Товарная группа]&"/"&[Цвет]&"/"&[Размер]&"/"&[#"Цена за ед."]),
удал_столб1 = Table.SelectColumns(сцепить,{"Тор. точка", "Требуемое кол-во на торговой точке", "Пользовательский"}),
Сведенный_столбец = Table.Pivot(Table.TransformColumnTypes(удал_столб1, {{"Тор. точка", type text}}, "ru-RU"), List.Distinct(Table.TransformColumnTypes(удал_столб1, {{"Тор. точка", type text}}, "ru-RU")[#"Тор. точка"]), "Тор. точка", "Требуемое кол-во на торговой точке"),
раздел_столб_по_разделителю = Table.SplitColumn(Сведенный_столбец, "Пользовательский", Splitter.SplitTextByDelimiter("/", QuoteStyle.Csv), {"Пользовательский.1", "Пользовательский.2", "Пользовательский.3", "Пользовательский.4", "Пользовательский.5", "Пользовательский.6"}),
замена_null_на_0 = Table.ReplaceValue(раздел_столб_по_разделителю,null,0,Replacer.ReplaceValue,{"1", "3", "4", "5", "2"}),
изм_тип2 = Table.TransformColumnTypes(замена_null_на_0,{{"Пользовательский.6", Int64.Type}, {"1", Int64.Type}, {"3", Int64.Type}, {"4", Int64.Type}, {"5", Int64.Type}, {"2", Int64.Type}}),
общ_итог_колво = Table.AddColumn(изм_тип2, "Общий итог (кол-во)", each [1]+[3]+[4]+[5]+[2]),
общ_итог_стоимость = Table.AddColumn(общ_итог_колво, "Общий итог по стоимости", each [#"Общий итог (кол-во)"]*[Пользовательский.6]),
переупоряд_столб = Table.ReorderColumns(общ_итог_стоимость,{"Пользовательский.2", "Пользовательский.3", "Пользовательский.1", "Пользовательский.4", "Пользовательский.5", "Пользовательский.6", "1", "3", "4", "5", "2", "Общий итог (кол-во)", "Общий итог по стоимости"}),
сортировка = Table.Sort(переупоряд_столб,{{"Пользовательский.2", Order.Ascending}, {"Пользовательский.3", Order.Ascending}, {"Пользовательский.1", Order.Ascending}}),
s=Table.ToRows(сортировка),
e={{1,1,1,1,1,1,1,1,1,1,1,1,1}},
f=List.Combine({s,e}),
g=Table.FromRows(f),
уровень_вид_монтажа = Table.AddColumn(g, "Пользовательский", each if [Column1]=1 then "НАРУЖНЫЙ МОНТАЖ" else ""),
переупоряд_столб2 = Table.ReorderColumns(уровень_вид_монтажа,{"Пользовательский", "Column1", "Column2", "Column3", "Column4", "Column5", "Column6", "Column7", "Column8", "Column9", "Column10", "Column11", "Column12", "Column13"}),
сортировка2 = Table.Sort(переупоряд_столб2,{{"Пользовательский", Order.Descending}})
in
сортировка2
Спасибо! Не знал про подобный способ создания иерархии.
Если я правильно понял идею, предлагаете последовательно проиндексировать значения на каждом из 3х уровней агрегации (вид монтажа > товарная группа > название товара) при помощи
Код
List.Combine({s,...}) // второй аргумент будет индекс для каждого значения каждого уровня
В результате получим фиксированный по кол-ву строк грид, в который записывать атрибуты для каждого названия товара. Правильно я понял?
Если да, то подход мог бы быть условно рабочим: кол-во уровней агрегации фиксированное (три), кол-во значений в уровнях вид монтажа и товарная группа можно считать константами. Но как быть с названиями товара? Это значение переменное и проиндексировать его в запросе PQ проблематично(
Шура, это была только смутная мысль из головы, если кто-то не подскажет компактное решение, то не хочется думать, т.к. сомнительный эффект делать это в pq - проще руками на листе excel, либо макросом мэйби.
Для вывода характеристик товара в каждой строке предлагается создать отдельную меру для каждой характеристики (название товара, цвет, размер, цена за ед.)?
Шура написал: Для вывода характеристик товара в каждой строке предлагается создать отдельную меру для каждой характеристики (название товара, цвет, размер, цена за ед.)?
я бы в PQ создал простую таблицу со списком характеристик "Цена", "Цвет", "Размер", "Всего кол-во", "Всего стоимость" и динамически добавил туда все значения из колонки Тор. точка (это будут все колонки вашего отчета). Меру, которую будете "бросать" в отчет, надо заставить анализировать поле в этой таблице в текущем контексте и с помощью SWITCH подставлять разные варианты расчета. Для цены, цвета и размера - просто значение из таблицы с данными в модели, для расчетных показателей - меры по их расчету. Будут нюансы с subtotals и totals , но это уже детали. Но может быть кто-то, кто имеет больше опыта работы с DAX, может предложить иное решение.
написал: в PQ создал простую таблицу со списком характеристик "Цена", "Цвет", "Размер", "Всего кол-во", "Всего стоимость" и динамически добавил туда все значения из колонки Тор. точка
Всю логику сразу мне сложно воспринять, поэтому последовательно прошу уточнения: "динамическое добавление в PQ" это значит что число столбцов в запросе PQ определяется исходя из фактических данных по товарам? Т.е. если в Торг. точке 5 есть товар из группы Товар 1 и Товар 2, то этот столбец попадет в эту "простую таблицу со списком характеристик"?
В принципе кол-во торговых точек меняется редко и можно их все сразу перечислить в шапке. Если в этих столбцах будут пустые ячейки это нормально.
AlienSx, спасибо большое! Моё текущее знание DAX не позволило бы самому написать такую меру.
Разбираюсь с решением, в целом логика ясна. Не могу только разобраться с её частью. Для чего предназначено выражение data[Тор. точка] = col в конце оператора SWITCH?
measure
measure := VAR col = MAX('columns'[name]) RETURN SWITCH( col; "Цена за ед." ; MAX(data[Цена за ед.]); "Размер"; MAX(data[Размер]); "Цвет"; MAX(data[Цвет]); "сумма"; SUM(data[Требуемое кол-во на торговой точке]); "стоимость"; SUMX(data; data[Требуемое кол-во на торговой точке] * data[Цена за ед.]); CALCULATE(SUM(data[Требуемое кол-во на торговой точке]); data[Тор. точка] = col) )
AlienSx, да, с CALCULATE надо было мне повнимательнее скобки посчитать, чтобы самому разобраться к чему это относится Теперь понятно.
Начал переносить запросы и меру в рабочий файл и понял, что схему данных в примере наврал. Столбец Требуемое кол-во на торговой точке в реальности не существует, а подсчет ведется по кол-ву записей одинакового товара в одной и той же Тор. точке.
Переделал исходные данные (лист Исх. данные (2)), продублировал код запросов под новую схему данных и создал новую меру (measure2) с учётом всех этих изменений. Но я её так переделал, что неверно считается столбец "стоимость" - в нём дважды перемножается "сумма" и результат потом умножается на цену за ед.
Код
"стоимость"; SUMX(data2; COUNTA (data2[Тор. точка])* data2[Цена за ед.]);
AlienSx, ожидаемый результат в новой версии. Не получалось реализовать отключение итогов с IF, сделал при помощи SWITCH, поскольку выше пришлось с ним разобраться подробнее.
Еще раз огромное спасибо за мастер-класс! Подход со расчётом всех атрибутов (столбцов) строк в рамках одной меры мне бы точно не пришел в голову, ковырялся бы с мерами для каждого атрибута.