Введение в DAX в Power BI: как работают CALCULATE и контекст фильтра

Одна и та же DAX-мера может показывать 200 в строке таблицы и 1 000 в общем итоге. Это не обязательно ошибка: формула вычисляется в разном контексте. Чтобы понимать результат, нужно следить не только за выражением после знака равенства, но и за тем, какие строки модели доступны в момент расчёта.
Разберём авторскую учебную модель продаж в Power BI Desktop. Четыре строки ниже придуманы для упражнения; суммы не связаны с реальной компанией. Формулы используют стандартные имена DAX и запятые. Если ваша среда настроена на другой разделитель, адаптируйте ввод, сохранив смысл выражения.
Подготовить маленькую таблицу, которую можно проверить вручную
Через ввод данных создайте таблицу Sales с колонками OrderDate, Segment, Qty и Price. Дату задайте типом Date, количество — целым числом, цену — числовым типом. В примере цена относится к одной единице, скидок и возвратов нет.
| OrderDate | Segment | Qty | Price | Расчёт строки |
|---|---|---|---|---|
| 05.01.2026 | Retail | 2 | 100 | 200 |
| 10.01.2026 | Business | 3 | 100 | 300 |
| 05.02.2026 | Retail | 1 | 200 | 200 |
| 10.02.2026 | Business | 2 | 150 | 300 |
Контрольные числа: выручка всех строк 1 000, Retail — 400, Business — 600, январь — 500. Не начинайте разбор DAX на модели, где исходный результат уже невозможно объяснить без формулы.
Row context и filter context отвечают на разные вопросы
Контекст строки позволяет вычислению обращаться к значениям текущей строки. Например, создайте вычисляемую колонку Amount в Sales:
Amount = Sales[Qty] * Sales[Price]
Для первой строки используются именно 2 и 100, поэтому получается 200. Это не мера общей выручки: в каждой строке создаётся своё значение. DAX используется для выражений в модели, в том числе вычисляемых колонок и мер; перенос привычек из отдельной Excel-ячейки без учёта модели часто мешает понять результат.
Контекст фильтра определяет допустимые значения и строки для расчёта. Его формируют, например, поля визуализации, выбранные фильтры и выражения DAX. Поставьте Segment в строки табличной визуализации и добавьте меру:
Revenue = SUM(Sales[Amount])
В строке Retail мера видит соответствующий сегмент и возвращает 400. В общем итоге ограничение одной строкой сегмента отсутствует, поэтому результат 1 000. Внешне визуализация тоже имеет «строки», но это не означает, что мера автоматически работает в том же row context, что и вычисляемая колонка Amount.
SUM складывает колонку, SUMX вычисляет выражение по строкам
Колонку Amount можно не создавать, если нужна только мера. SUMX обходит таблицу и суммирует результат выражения для её строк:
Revenue X =
SUMX(Sales, Sales[Qty] * Sales[Price])
В нашем примере Revenue и Revenue X должны совпасть. SUM получает колонку готовых чисел; SUMX позволяет задать произведение Qty и Price внутри итерации. Поэтому SUM(Sales[Qty] * Sales[Price]) не является заменой: аргумент SUM — колонка, а не такое построчное выражение.
Формула SUM(Sales[Qty]) * SUM(Sales[Price]) также решает другую задачу. Для всех четырёх строк она даёт 8 × 550 = 4 400, а не 1 000. Ошибка не исправляется внешним CALCULATE: сначала выберите правильную операцию над строками. После этого фильтры определяют, на каком наборе она выполняется.
CALCULATE изменяет контекст, в котором считается выражение
Создайте меру выручки Retail:
Retail Revenue =
CALCULATE(
[Revenue X],
Sales[Segment] = "Retail"
)
CALCULATE вычисляет выражение в изменённом filter context. Если ограничения на Sales[Segment] раньше не было, фильтр добавляется. Если оно уже было, обычный аргумент-фильтр заменяет существующее ограничение на этой же колонке. При этом он не означает «удалить вообще все фильтры модели».
В таблице, где строки построены именно по Sales[Segment], Retail Revenue покажет 400 и в строке Retail, и в строке Business. Второй результат неожиданен только если ожидать пересечение: фильтр Business на этой колонке заменён на Retail. Если сегмент берётся из отдельного справочника, анализировать нужно также его фильтр и связь с Sales; пример с одной колонкой нельзя переносить на другую модель без проверки.
Для пересечения используйте KEEPFILTERS:
Retail Within Selection =
CALCULATE(
[Revenue X],
KEEPFILTERS(Sales[Segment] = "Retail")
)
Теперь выбор Business не должен превращаться в показ Retail. Ограничения на одну колонку пересекаются, и подходящих строк в этом случае нет. Посмотрите, как пустой результат отображается в вашей визуализации; прежде чем заменять его нулём, выясните, означает ли он отсутствие строк или другую проблему данных.
Снять один фильтр, сохранив остальные
Другой полезный вопрос: сколько выручки было по всем сегментам в текущем выбранном периоде? Снимем ограничение только с Segment:
Revenue All Segments =
CALCULATE(
[Revenue X],
REMOVEFILTERS(Sales[Segment])
)
В нашей простой модели эта мера не должна отменять отдельно выбранную дату. REMOVEFILTERS без указания таблицы или колонок имеет более широкий смысл; не подставляйте его вместо точечного снятия, если хотите сохранить остальные ограничения. Для справочника сегментов потребуется работать с тем источником фильтра, который действительно используется в отчёте.
У CALCULATE есть и роль перехода контекста: без аргументов фильтра он может преобразовать row context в filter context. Microsoft отдельно описывает такой случай для агрегирующего выражения внутри итератора или вычисляемой колонки. При вызове модельной меры в row context переход происходит автоматически. Это причина разбирать, где именно выполняется формула, а не добавлять CALCULATE к каждому выражению как универсальное исправление.
Мини-практика: сегмент и даты в одной модели
Создайте таблицу календаря на полный учебный год:
Date = CALENDAR(DATE(2026, 1, 1), DATE(2026, 12, 31))
В ней должна быть уникальная Date[Date] без пропусков и пустых значений. Для используемой здесь классической работы с датами отметьте её как таблицу дат. Настройте активную связь Date[Date] на стороне «один» с Sales[OrderDate] на стороне «много», с распространением фильтра от календаря к продажам. Убедитесь, что Sales[OrderDate] не содержит скрытую временную часть, мешающую совпадению ключей.
Создайте меру для января и Retail. Тестируйте её на чистой странице без дополнительных фильтров дат:
Retail January =
CALCULATE(
[Revenue X],
Sales[Segment] = "Retail",
DATESBETWEEN(
'Date'[Date],
DATE(2026, 1, 1),
DATE(2026, 1, 31)
)
)
Ожидаем 200: это первая строка учебной таблицы. DATESBETWEEN задаёт диапазон с включёнными границами по имеющейся колонке дат. Не добавляйте ту же календарную фильтрацию к текстовой колонке «месяц» и не считайте, что связь создаётся автоматически от похожих названий.
Если итог удивляет, проверить контекст до переписывания меры
Сначала выведите исходные строки и проверьте Qty, Price, даты и Segment. Затем оставьте только базовую Revenue X. После этого добавляйте по одному: сегмент, календарь, CALCULATE, KEEPFILTERS. Такой авторский порядок позволяет увидеть момент, в котором ожидание разошлось с расчётом.
Общий итог меры вычисляется в контексте итога; он не обязан быть механической суммой видимых строк для любого выражения. В наших аддитивных продажах ручная сумма помогает проверке, но для долей и других мер сначала нужно определить смысл итогового показателя. Не создавайте десять копий основной арифметики по сегментам: базовая мера и явные изменения фильтра делают логику понятнее. При этом одинаковый результат нескольких мер ещё не доказывает корректности модели — сверяйте его с контрольными строками и ожидаемым вопросом отчёта.


