DAX
December 14, 2022

#PowerBI: контекст и константы

Разобраться в работе контекста в языке #DAX бывает не просто. Иногда кажется, что вот - уловил уже суть, но вдруг получаешь результат, который не можешь объяснить.

В прошлой статье о контекстах я попытался предложить упрощенный вариант понимания контекста фильтра. В ней я разбирал работу контекста на простом примере, не вдаваясь в подробности и теорию.

Сегодня же, хочу заглянуть чуть глубже и на примере одной визуализации рассказать о том, что может влиять на формирование контекста вычисления, какие функции используются для формирования визуального элемента и как на это могут повлиять константы, связи и наполнение визуальных элементов.

За основу возьмём всю ту же модель данных:

Схема модели

Единственное, по какой-то причине в таблице фактов добавлен столбец Category, который соответственно содержит названия категорий товаров:

Столбец категорий в таблице Sales

Хотя у нас есть информация о категориях товаров в связанной таблице Products:

Столбец категорий в таблице Products

Также в модели создана мера, которая считает количество единиц проданного товара:

Мера количества продаж

На листе отчёта создадим визуальный элемент Таблица и добавим в него столбец Category из таблицы Sales:

Столбец категорий из таблицы Sales в визуализации

Для того, чтобы отобразить визуальный элемент, движку PowerBI необходимо вычислить таблицу, которая получается в результате группировки. Чтобы посмотреть как это происходит, воспользуемся функционалом анализатора производительности (вкладка "Представление" - "Анализатор производительности" - "Таблица" - "Запрос DAX" - "Копировать запрос")

Использование Анализатора производительности

Далее скопируем запрос и вставим его в DAX Studio:

Запрос таблицы в визуализации
Я убрал лишние строки, где результат запроса ограничивается на 500 строк, т.к. это никак не влияет на результат

Из запроса видно, что для отображения в таблице, с помощью табличной функции DISTINCT формируется таблица категорий товаров на основании столбца Category таблицы Sales. Никаких фильтров у нас нет, поэтому в визуальном элементе отображаются все категории, по которым были продажи.

Но заказчик хочет видеть информацию только по категории Bikes, поэтому добавим в панель фильтров соответствующие условие:

Таблица и фильтр по справочнику

Только вот в фильтр мы добавили столбец CategoryName из таблицы Products, но в визуализации отображается ожидаемый результат в виде одной категории.

Посмотрим на текст запроса:

Запрос с фильтром по таблице

Для отображения таблицы по прежнему используется функция DISTINCT, но на этот раз она обернута в функцию CALCULATETABLE, с помощью которой добавляется контекст вычисление с фильтром по таблице Products, который передаётся по связи между таблицами.

А теперь добавим в визуализацию меру:

Таблица с мерой

И сразу посмотрим на код запроса:

Запрос с мерой

Результат по прежнему тот, который мы ожидаем - в таблице отображены продажи по одной категории товаров. Но вот функция, которая формирует таблицу для отображения изменилась. Вместо DISTINCT теперь используется функция SUMMARIZECOLUMNS.

Но, прежде чем обратиться к описанию данной функции, создадим новую меру, практически идентичную текущей, за исключением того, что в неё добавлена константа в виде 0:

Мера с константой
Такой приём используется довольно часто, чтобы вместо пустоты(BLANK) показывать 0.

Скопируем визуальный элемент и заменим в нём меру, чтобы было удобнее сравнивать:

Таблица с новой мерой +0

Значение по категории Bikes не изменилось, но появились две категории с нулевым значением, хотя вы по прежнему могли ожидать получить одну категорию, которая выбрана в панели фильтров, ведь таблицы связаны между собой и направление фильтра по связи от Products в Sales. Посмотрим запрос:

Запрос с использованием меры с константой

Запрос идентичен запросу с изначальной мерой. Почему же там показывается одна категория, а здесь три?

Вот здесь стоит обратиться к теории и описанию функции SUMMARIZECOLUMNS. До примечаний редко кто доходит или внимательно их читает, но здесь кроется вся суть:

Filters in SUMMARIZECOLUMNS only apply to group-by columns from the same table and to measures.

Или по-русски: Фильтры в SUMMARIZECOLUMNS применяются ТОЛЬКО к столбцам группировки из ТОЙ ЖЕ таблицы и К МЕРАМ.

А у нас столбец группировки и столбец фильтра находятся в РАЗНЫХ таблицах, поэтому происходит следующее:

They do not apply to group-by columns from other tables directly, but indirectly through the implied non-empty filter from measures.

То есть, в этом случае к столбцам группировки фильтры из других таблиц применяются не напрямую, а косвенно через подразумеваемый "непустой" фильтр из мер. А в нашей новой мере появилась константа 0, то есть значение, уже вычисленное, на которое никакие фильтры не могут повлиять, поэтому мы как раз и получили "непустое" значение для каждой строки.

Обратите внимание, что так будет работать только сложение и вычитание. Умножение или деление, допустим мы захотим умножить на какой-то коэффициент, по прежнему будут возвращать пустоту:

BLANK()+0 = 0

BLANK()-0 = 0

BLANK() * 0 = BLANK()

DIVIDE(0,BLANK()) = BLANK()

И в конце немного теории и дополнительное пояснение почему же оно так работает из книги "Подробное руководство по DAX":

Контекст строки осуществляет итерации по таблице, он не фильтрует. Речь идет о построчном сканировании таблицы и последовательном выполнении той или иной операции. Обычно в отчетах нам нужны какие-то агрегации вроде суммы или среднего значения. Во время прохода по таблице контекст строки перебирает строки конкретной таблицы, предоставляя доступ к информации по всем столбцам, но только из этой таблицы. В других таблицах – даже связанных с нашей – контекст строки в этот момент не создан. Иными словами, контекст строки сам по себе автоматически не взаимодействует с существующими в модели связями.

Именно поэтому, если мы хотим получить ожидаемый результат, в нашем визуальном элементе должен находиться столбец из той же таблицы, по которой мы осуществляем фильтрацию или по другому итерацию, просматривая столбец, чтобы выбрать нужные значения:

Таблица со столбцом из справочника

Ссылка на файл с моделью.