CALCULATE, CALCULATE, CALCULATE или контекст вычисления
Основа основ работы с языком DAX - это понимание работы контекстов вычисления. И, когда только начинаешь работать в PowerBI или PowerPivot, понимание концепции работы контекстов вызывает затруднения. Каждый раз, получая не тот результат, который ожидал, начинающий разработчик впадает в уныние, поэтому хочу предложить вам своё понимание определения контекста в картинках=)
Я сознательно хочу упростить определение контекстов, не вдаваясь в витиеватые формулировки - всё это можно легко "нагуглить" или прочитать в книгах. Также я не буду разбирать тонкости построения моделей и связей, хотя они могут влиять на создание контекста вычисления - просто возьмём за аксиому, что модель имеет схему "звезда", все связи однонаправленные и один ко многим.
Возьмём простую модель о продажах:
Создадим простую меру и добавим её в визуализацию "Матрица":
Как видно на скриншоте, сейчас на меру не действуют никакие фильтры. Данное вычисление можно представить, как таблицу из одного столбца OrderQuantity с множеством значений, которые просто суммируются.
Добавим на страницу срез по полу(Gender) покупателей (Customers) - отберём только женщин:
Это вычисление уже можно представить как отфильтрованную таблицу из двух столбцов:
Чтобы узнать: Сколько покупают женщины по понедельникам? - добавим в панель фильтров соответствующий фильтр:
Наша "виртуальная" таблица снова расширилась. В ней появился новый столбец - NameDayWeek, отфильтрованный по понедельнику:
А товары из каких категорий женщины покупают по понедельникам?
Названия категорий (CategoryName) мы добавили в строки матрицы - это НЕ является фильтром для ДАННОГО ВИЗУАЛЬНОГО элемента (если выбрать строку в этом визуальном элементе, то значение строки измерения будет фильтром для других визуальных элементов), НО является фильтром для меры.
Каждая ячейка визуального элемента вычисляется НЕЗАВИСИМО от других в СВОЁМ контексте вычисления.
В соответствии с выше описанным правилом для ячейки со значением 4271 фильтром является CategoryName="Accessories", а в "виртуальную" таблицу добавляется ещё один столбец:
Точно также фильтром для меры будут и значения измерений, которые находятся в столбцах визуального элемента:
Таким образом можно сформулировать, что перед вычислением меры, сначала вычисляются все внешние фильтры, которые складываются в единый фильтр, как логическое "И": И женщины, И понедельник, И 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. Пока я писал эту статья Марко Руссо и Альберто Феррари выпустили отличное видео про контексты, со схожей, как мне кажется, концепцией объяснения: