DAX
July 20, 2022

CALCULATE, CALCULATE, CALCULATE или контекст вычисления

Основа основ работы с языком DAX - это понимание работы контекстов вычисления. И, когда только начинаешь работать в PowerBI или PowerPivot, понимание концепции работы контекстов вызывает затруднения. Каждый раз, получая не тот результат, который ожидал, начинающий разработчик впадает в уныние, поэтому хочу предложить вам своё понимание определения контекста в картинках=)

Я сознательно хочу упростить определение контекстов, не вдаваясь в витиеватые формулировки - всё это можно легко "нагуглить" или прочитать в книгах. Также я не буду разбирать тонкости построения моделей и связей, хотя они могут влиять на создание контекста вычисления - просто возьмём за аксиому, что модель имеет схему "звезда", все связи однонаправленные и один ко многим.

Возьмём простую модель о продажах:

Схема модели данных

Создадим простую меру и добавим её в визуализацию "Матрица":

Базовая мера Sum Orders Qty

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

Таблица из одного столбца для суммирования

Добавим на страницу срез по полу(Gender) покупателей (Customers) - отберём только женщин:

Визуализация с фильтром в срезе

Это вычисление уже можно представить как отфильтрованную таблицу из двух столбцов:

"Виртуальная" таблица с фильтром по Gender

Чтобы узнать: Сколько покупают женщины по понедельникам? - добавим в панель фильтров соответствующий фильтр:

Фильтр на странице в боковой панели

Наша "виртуальная" таблица снова расширилась. В ней появился новый столбец - NameDayWeek, отфильтрованный по понедельнику:

"Виртуальная" таблица по Gender и NameDayWeek

А товары из каких категорий женщины покупают по понедельникам?

Добавляем измерение в строки

Названия категорий (CategoryName) мы добавили в строки матрицы - это НЕ является фильтром для ДАННОГО ВИЗУАЛЬНОГО элемента (если выбрать строку в этом визуальном элементе, то значение строки измерения будет фильтром для других визуальных элементов), НО является фильтром для меры.

Каждая ячейка визуального элемента вычисляется НЕЗАВИСИМО от других в СВОЁМ контексте вычисления.

В соответствии с выше описанным правилом для ячейки со значением 4271 фильтром является CategoryName="Accessories", а в "виртуальную" таблицу добавляется ещё один столбец:

"Виртуальная" таблица по Gender, NameDayWeek, CategoryName

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

Добавляем в столбцы матрицы измерение Calendar[Year]
"Виртуальная" таблица отфильтрована по году

Таким образом можно сформулировать, что перед вычислением меры, сначала вычисляются все внешние фильтры, которые складываются в единый фильтр, как логическое "И": И женщины, И понедельник, И Accessories, И 2017 - после чего происходит агрегирование оставшихся значений. Если все выше полученные фильтры интерпретировать в код, то мы получили бы следующую меру:

Интерпретация контекста в виде меры

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

Создадим новую меру и поместим её в такую же матрицу:

Визуализация меры с внутренним фильтром

В нижней матрице теперь отображаются продажи женщинам по понедельникам только товаров красного цвета. А код интерпретации выглядит следующим образом:

Интерпретация контекста в виде меры с внутренним фильтром

В интерпретации кода теперь присутствуют две функции CALCULATE. Это ещё одна аксиома: в мере ВСЕГДА по умолчанию есть функция CALCULATE. Также видно, что в нижней матрице исчезла категория Clothing, в которой не было продаж товаров красного цвета.

Добавим в обе матрицы в строки измерение ProductColor:

Матрицы по категориям и цветам

В матрице справа в строках с названиями цветов все значения одинаковые в рамках года, плюс добавились строки с цветами, по которым не было продаж. Это произошло потому, что в мере [SOQ Red] мы принудительно изменили контекст вычисления. Краткая запись ("синтаксический сахар") - 'Products'[ProductColor]="Red", на самом деле интерпретируется как FILTER(ALL('Products'[ProductColor]), 'Products'[ProductColor]="Red"). То есть, после того, как были вычислены все внешние фильтры, во внутреннем мы говорим: верни ВСЕ цвета - ALL('Products'[ProductColor]) и выбери из них красный - 'Products'[ProductColor]="Red". И как раз здесь многие начинающие запинаются, полагаясь на интуицию и применяя функцию FILTER следующим образом:

"Странно" работающая мера

Забывая или не зная, что к внутренним аргументам фильтра также сначала применяются все внешние фильтры, а только потом внутренние. Например: к таблице Products применяются внешние фильтры по CategoryName="Accessories" и ProductColor="Black", а потом в полученной таблице мы просим найти красный цвет, но, естественно, нельзя найти красный там, где только чёрный. Если же применить функцию ALL ко всей таблице Products, то мы потеряем фильтр по CategoryName и также получим неверный результат.

Предлагаю вам в следующий раз, когда запутаетесь в контекстах вычисления, также, как и я, попробовать представить "виртуальную" таблицу, в которую вы по шагам добавляете по одному столбцу(фильтру), выбирая в них нужные значения, соблюдая порядок вычисления - от внешних к внутренним. Успехов!

P.S. Пока я писал эту статья Марко Руссо и Альберто Феррари выпустили отличное видео про контексты, со схожей, как мне кажется, концепцией объяснения: