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

На чтение
6 мин
Дата обновления
05.11.2026

Одна и та же DAX-мера может показывать 200 в строке таблицы и 1 000 в общем итоге. Это не обязательно ошибка: формула вычисляется в разном контексте. Чтобы понимать результат, нужно следить не только за выражением после знака равенства, но и за тем, какие строки модели доступны в момент расчёта.

Разберём авторскую учебную модель продаж в Power BI Desktop. Четыре строки ниже придуманы для упражнения; суммы не связаны с реальной компанией. Формулы используют стандартные имена DAX и запятые. Если ваша среда настроена на другой разделитель, адаптируйте ввод, сохранив смысл выражения.

Подготовить маленькую таблицу, которую можно проверить вручную

Через ввод данных создайте таблицу Sales с колонками OrderDate, Segment, Qty и Price. Дату задайте типом Date, количество — целым числом, цену — числовым типом. В примере цена относится к одной единице, скидок и возвратов нет.

OrderDateSegmentQtyPriceРасчёт строки
05.01.2026Retail2100200
10.01.2026Business3100300
05.02.2026Retail1200200
10.02.2026Business2150300

Контрольные числа: выручка всех строк 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. Такой авторский порядок позволяет увидеть момент, в котором ожидание разошлось с расчётом.

Общий итог меры вычисляется в контексте итога; он не обязан быть механической суммой видимых строк для любого выражения. В наших аддитивных продажах ручная сумма помогает проверке, но для долей и других мер сначала нужно определить смысл итогового показателя. Не создавайте десять копий основной арифметики по сегментам: базовая мера и явные изменения фильтра делают логику понятнее. При этом одинаковый результат нескольких мер ещё не доказывает корректности модели — сверяйте его с контрольными строками и ожидаемым вопросом отчёта.