К содержимому
L-SYSTEM

DAX II: CALCULATE и переход контекста

Превью-юнит: этот юнит открыт и без записи на курс. Остальные — после записи.

Занятие — на вашем языке: его ведёт ассистент. Материал: английский · Экзамен: английский, без перевода Как подключить ассистента

Цель экзамена (Model the data, 25–30 %): Use the CALCULATE function. Связанные: Create single aggregation measures и Identify use cases for calculated columns (u10), Implement time intelligence measures (u12), Create calculation groups (u13).

1. Боль: мера, которая выглядит как SQL и лжёт

Руководитель консалтингового направления Aldervane хочет одну матрицу: часы по клиентам, оплачиваемые часы, долю клиента во всех часах и часы по клиентам из Великобритании. Томаш, старший разработчик, который десять лет пишет на T-SQL, пишет меры так, как написал бы WHERE:

Billable Hours = CALCULATE ( [Hours], Timesheet[Billable] = "Y" )
UK Hours       = CALCULATE ( [Hours], Client[Country] = "UK" )

На его первой странице каждое число выглядит правильным. Потом коллега перетаскивает Billable на строки, а Country — на столбцы, и матрица начинает показывать 28 оплачиваемых часов в строке неоплачиваемых и 26 британских часов в столбце Ireland. Ничего не сломано. Томаш прочитал CALCULATE как «добавь условие». На самом деле это значит «вычисли это выражение в изменённом контексте фильтра», и изменение обычно заменяет фильтры, а не добавляется к ним.

Этот юнит — об этом единственном различии. Как только вы можете назвать контекст фильтра ячейки, каждый вопрос о CALCULATE на экзамене превращается в арифметику.

2. Модель, которую мы считаем в уме

client Client ClientName (key) Country ts Timesheet ClientName Billable (Y/N) Hours client->ts 1 → *  (single direction)
ИллюстрацияThe unit's mini model — Client filters Timesheet through a one-to-many relationship.
Описание

Two boxes joined by an arrow from left to right. The left box is the dimension table Client with the columns ClientName, which is the key, and Country. The right box is the fact table Timesheet with the columns ClientName, Billable with values Y or N, and Hours. The arrow is labelled one to many, single direction: a filter on Client flows to Timesheet, never the other way. All examples in this unit use this model.

Client

ClientName Country
Contoso Energy UK
Fabrikam Logistics Ireland
Woodgrove Bank UK

Timesheet

ClientName Billable Hours
Contoso Energy Y 10
Contoso Energy N 2
Fabrikam Logistics Y 6
Fabrikam Logistics Y 4
Woodgrove Bank Y 8
Woodgrove Bank N 6

Базовая мера — Hours = SUM ( Timesheet[Hours] ). Итоги, которые стоит держать в голове: Contoso 12, Fabrikam 10, Woodgrove 14, все клиенты 36; оплачиваемые часы 28; клиенты из Великобритании 26.

3. Контекст фильтра одним предложением (из u10)

Каждая ячейка визуального элемента вычисляется отдельно. Её контекст фильтра — это набор фильтров из заголовка строки, заголовка столбца, срезов, панели фильтров и RLS. В матрице с ClientName на строках ячейка Contoso имеет фильтр Client[ClientName] = "Contoso Energy", и этот фильтр проходит через связь к Timesheet. [Hours] там равно 12. У строки итога нет фильтра по ClientName, поэтому она показывает 36.

Привычка для экзамена: прежде чем что-то считать, запишите контекст фильтра ячейки. Затем примените к нему то, что делает CALCULATE.

4. CALCULATE задаёт фильтры — и заменяет их

CALCULATE ( <expression>, <filter1>, <filter2>, … )

Аргумент-фильтр вроде Timesheet[Billable] = "Y" — сокращённая запись таблицы с одним столбцом и допустимыми значениями:

-- what the engine actually uses
FILTER ( ALL ( Timesheet[Billable] ), Timesheet[Billable] = "Y" )

Обратите внимание на ALL. Сокращённая запись игнорирует любой фильтр, уже существующий на Timesheet[Billable], и ставит на его место свой собственный. Фильтры на других столбцах остаются как есть.

Isotype picture — the same coins seen through two frames, VISUAL and CALCULATE, each letting different coins through.
ИллюстрацияIsotype picture — the same coins seen through two frames, VISUAL and CALCULATE, each letting different coins through.
Описание

Two square charcoal frames on beige paper, joined by a thick arrow pointing from left to right. The left frame, labelled VISUAL, holds rows of faded coin outlines; only the top row of four slate-blue coins is solid — the selection the visual's filter lets through. The right frame, labelled CALCULATE, holds the same faded coins, but now only the bottom row of four ochre coins is solid. The idea: CALCULATE does not narrow the visual's selection further — it swaps the filter on that column for its own, so a different set of rows is counted. The picture does not show filters on other columns, which CALCULATE leaves in place.

4.1 Когда замена — именно то, что нужно

Матрица с ClientName на строках:

ClientName Hours Billable Hours
Contoso Energy 12 10
Fabrikam Logistics 10 10
Woodgrove Bank 14 8
Итог 36 28

Фильтр строки стоит на ClientName, а CALCULATE фильтрует Billable. Это разные столбцы, поэтому два фильтра объединяются, и числа получаются такими, каких ожидал Томаш.

4.2 Когда это застаёт врасплох

Поставьте на строки вместо этого Timesheet[Billable]:

Billable Hours Billable Hours
N 8 28
Y 28 28
Итог 36 28

В строке N фильтр ячейки — Billable = "N". CALCULATE заменяет его на Billable = "Y", поэтому ячейка показывает 28. Ловушка экзамена: любой вопрос, где на оси стоит тот же столбец, который фильтрует мера, проверяет, знаете ли вы это.

То же происходит с UK Hours = CALCULATE ( [Hours], Client[Country] = "UK" ):

Ось на строках Строка UK Hours Почему
ClientName Contoso Energy 12 фильтр ClientName остаётся, добавлено Country = UK: Contoso — в UK
ClientName Fabrikam Logistics (пусто) ClientName = Fabrikam и Country = UK: таких строк нет
ClientName Woodgrove Bank 14 оба фильтра выполняются
Country Ireland 26 Country = Ireland заменён на Country = UK
Country UK 26 заменён тем же значением

Строка Fabrikam пуста, а строка Ireland показывает 26, хотя Fabrikam — ирландский клиент. Вся разница в том, какой столбец фильтрует визуальный элемент.

4.3 Пересечение вместо замены: KEEPFILTERS

Если требование — «часы по UK, но с учётом того, что пользователь уже выбрал», оберните фильтр:

UK Hours (keep) = CALCULATE ( [Hours], KEEPFILTERS ( Client[Country] = "UK" ) )

KEEPFILTERS пересекает новый фильтр с существующим на том же столбце. С Country на строках: Ireland → пусто, UK → 26.

Табличный фильтр тоже даёт пересечение, потому что перебирает только уже видимые строки:

UK Hours (table) = CALCULATE ( [Hours], FILTER ( Client, Client[Country] = "UK" ) )

Это работает, но медленнее и менее точно, чем KEEPFILTERS: фильтруется каждый столбец Client, а не только Country. Используйте FILTER, когда условию нужно то, чего не выразить простым предикатом, — чаще всего меру (раздел 6).

5. Снятие фильтров: доля от итога

Hours All Clients = CALCULATE ( [Hours], REMOVEFILTERS ( Client ) )
% of All Clients  = DIVIDE ( [Hours], [Hours All Clients] )
ClientName Hours Hours All Clients % of All Clients
Contoso Energy 12 36 33.3 %
Fabrikam Logistics 10 36 27.8 %
Woodgrove Bank 14 36 38.9 %

Функции, которые снимают фильтры, и когда выбирать каждую:

Функция (как аргумент CALCULATE) Снимает Оставляет Типичное применение
REMOVEFILTERS ( Client ) фильтры на каждом столбце Client всё остальное доля от всей фирмы
ALL ( Client ) то же, что REMOVEFILTERS, когда используется как модификатор всё остальное старый код; ALL также может возвращать таблицу, REMOVEFILTERS — нет
REMOVEFILTERS ( Client[ClientName] ) фильтр только на одном столбце другие столбцы Client, например Country доля внутри страны
ALLEXCEPT ( Client, Client[Country] ) все фильтры Client, кроме Country Country то же самое, записанное наоборот
ALLSELECTED ( Client ) фильтры, которые применяет сам визуальный элемент фильтры от срезов и внешнего отчёта «доля от того, на что смотрит пользователь»

Ловушка экзамена: «Доли должны по-прежнему давать в сумме 100 %, когда пользователь выбирает UK в срезе» означает ALLSELECTED. С REMOVEFILTERS британские клиенты дали бы в сумме лишь 72 % (26 / 36), потому что знаменатель игнорировал бы срез.

6. Фильтрация по мере

Булев аргумент-фильтр может сравнивать столбцы одной таблицы с константами, но не может ссылаться на меру. «Оплачиваемые часы клиентов, у которых всего больше 10 часов» требуют таблицы клиентов для перебора:

Billable Hours Big Clients =
CALCULATE (
    [Billable Hours],
    FILTER ( VALUES ( Client[ClientName] ), [Hours] > 10 )
)

VALUES ( Client[ClientName] ) — маленькая таблица: по одной строке на каждого видимого клиента. Для каждой строки FILTER вычисляет [Hours] — это мера, и поэтому она вызывает переход контекста (следующий раздел). Перебирайте самую маленькую таблицу, которая отвечает на вопрос, — столбец измерения, никогда не таблицу фактов.

7. Порядок вычисления CALCULATE

a 1. Evaluate filter arguments in the ORIGINAL filter context b 2. Context transition (if there is a row context) a->b c 3. Apply modifiers REMOVEFILTERS, ALL, USERELATIONSHIP, CROSSFILTER b->c d 4. Apply filter arguments replace filters on the same columns (KEEPFILTERS: intersect instead) c->d e 5. Evaluate the expression in the new filter context d->e
ИллюстрацияThe five steps CALCULATE takes, from the original filter context to the result.
Описание

Five boxes stacked top to bottom and joined by arrows. One: the filter arguments are evaluated in the original filter context. Two: if there is a row context, context transition turns the current row into filters. Three: modifiers such as REMOVEFILTERS, ALL, USERELATIONSHIP and CROSSFILTER are applied. Four: the filter arguments are applied, replacing existing filters on the same columns, or intersecting with them when wrapped in KEEPFILTERS. Five: the expression is evaluated in the resulting filter context. The order explains why a filter argument wins over a filter that context transition created on the same column.

На экзамене порядок нужен редко. Он нужен, когда два шага касаются одного и того же столбца, например аргумент-фильтр на столбце, который также отфильтровал переход контекста: шаг 4 идёт после шага 2, поэтому побеждает аргумент-фильтр.

8. Переход контекста

Isotype picture — one highlighted ROW passes through a funnel and becomes a FILTER that lets only its own coins through.
ИллюстрацияIsotype picture — one highlighted ROW passes through a funnel and becomes a FILTER that lets only its own coins through.
Описание

On the left, a column of five person pictograms stands for the rows of a table, labelled ROW; the middle person is brick red and framed. An arrow carries this one row into a black funnel. From the narrow end of the funnel an arrow enters a frame labelled FILTER, which holds nine coins in three rows: only the middle row of three brick-red coins is solid, the rest are faded. The idea: context transition turns the row being iterated into a filter, so the calculation sees only the fact rows that belong to that row. The picture does not show how many columns of the row become filters — in DAX, all of them do.

Контекст строки существует в вычисляемых столбцах и в итераторах (SUMX, AVERAGEX, FILTER…). Он означает «я нахожусь на этой строке», но ничего не фильтрует. CALCULATE его преобразует: каждый столбец текущей строки становится фильтром. Это преобразование и есть переход контекста.

Три вычисляемых столбца на Client:

Вычисляемый столбец Contoso Fabrikam Woodgrove
= SUM ( Timesheet[Hours] ) 36 36 36
= CALCULATE ( SUM ( Timesheet[Hours] ) ) 12 10 14
= [Hours] 12 10 14
  • У первого столбца нет контекста фильтра, поэтому SUM видит весь Timesheet. Это классическая ошибка SQL-мышления: разработчик ожидает коррелированный подзапрос, а получает общий итог.
  • Второй столбец использует CALCULATE без аргументов-фильтров. Его единственная задача здесь — переход контекста.
  • Третий ссылается на меру. Ссылка на меру всегда несёт неявный CALCULATE, поэтому тоже вызывает переход контекста.

То же правило действует внутри мер, которые перебирают строки:

Avg Hours per Client (right) = AVERAGEX ( Client, [Hours] )                 -- total row: 12
Avg Hours per Client (wrong) = AVERAGEX ( Client, SUM ( Timesheet[Hours] ) ) -- total row: 36

В неправильной версии каждая итерация суммирует все 36 часов (перехода нет), и среднее из 36, 36 и 36 равно 36.

Ловушка экзамена: «вычисляемый столбец возвращает одно и то же значение в каждой строке» указывает на отсутствующий переход контекста. Исправление — CALCULATE или ссылка на меру.

9. Цена

  • FILTER по таблице фактов работает медленно. CALCULATE ( [Hours], FILTER ( Timesheet, Timesheet[Billable] = "Y" ) ) перебирает каждую видимую строку табеля и может быть на порядки медленнее предиката по столбцу. На таблице фактов в 10 миллионов строк вы увидите это в Performance Analyzer (u14).
  • Переход контекста внутри больших итераторов дорог. SUMX ( Timesheet, [Some Measure] ) выполняет переход для каждой строки фактов. Перебирайте вместо этого измерение или VALUES столбца.
  • Переход фильтрует каждый столбец строки. Если в таблице есть дублирующиеся строки, переход фильтрует все одинаковые строки сразу. Перебор таблицы без ключа даёт молча неверные результаты.
  • Читаемость. Каждый заменённый фильтр невидим в отчёте. Пользователь, который видит 28 в строке «N», не догадается почему. Называйте такие меры честно (Billable Hours (ignores Billable filter)) или используйте KEEPFILTERS, если должен побеждать фильтр визуального элемента.
Интуиция SQL Что на самом деле делает DAX
WHERE Billable = 'Y' добавляет условие фильтр CALCULATE заменяет фильтр на этом столбце
SUM(Hours) OVER () для общего итога CALCULATE ( [Hours], REMOVEFILTERS ( … ) )
коррелированный подзапрос на каждую строку вычисляемому столбцу нужен переход контекста, чтобы увидеть «свои» строки
HAVING SUM(Hours) > 10 FILTER ( VALUES ( … ), [Hours] > 10 ) внутри CALCULATE

10. Лабораторная — CALCULATE на датасете Aldervane

Вам понадобятся: Power BI Desktop (бесплатный, Windows) и публичный датасет aldervane-pbi-dataset (файлы CSV плюс README со связями). Время: около 40 минут. Стоимость: нет.

  1. Get data → Text/CSV для DimClient, DimEngagement и FactTimesheet. В Model view создайте DimClient[ClientKey] 1 → * DimEngagement[ClientKey] и DimEngagement[EngagementKey] 1 → * FactTimesheet[EngagementKey], обе однонаправленные.
  2. Создайте меры:
    Hours            = SUM ( FactTimesheet[Hours] )
    Billable Hours   = CALCULATE ( [Hours], FactTimesheet[Billable] = "Y" )
    UK Hours         = CALCULATE ( [Hours], DimClient[Country] = "UK" )
    UK Hours (keep)  = CALCULATE ( [Hours], KEEPFILTERS ( DimClient[Country] = "UK" ) )
    Hours All Clients = CALCULATE ( [Hours], REMOVEFILTERS ( DimClient ) )
    % of All Clients = DIVIDE ( [Hours], [Hours All Clients] )
    
  3. Откройте DAX query view и выполните:
    EVALUATE
    SUMMARIZECOLUMNS (
        DimClient[Country],
        "Hours", [Hours],
        "UK Hours", [UK Hours],
        "UK Hours (keep)", [UK Hours (keep)]
    )
    
    Ожидается: Ireland 9 664 / 18 907 / пусто; Poland 6 186 / 18 907 / пусто; UK 18 907 / 18 907 / 18 907. Объясните каждое число вслух, прежде чем смотреть.
  4. Постройте матрицу с DimClient[ClientName] на строках и [Hours], [% of All Clients]. Contoso Energy должен показать 19.5 %. Добавьте срез по Country, выберите UK и посмотрите, как доли перестают давать в сумме 100 %. Поменяйте знаменатель на ALLSELECTED ( DimClient ) и проверьте снова.
  5. На DimEngagement добавьте два вычисляемых столбца: Hours (bare) = SUM ( FactTimesheet[Hours] ) и Hours (CT) = [Hours]. Для ENG-4401 ожидайте 34 757 и 1 567.5.
  6. Общий итог — 34 757 часов как загружено: эта лабораторная загружает CSV заново, поэтому намеренная запись на −3 часа всё ещё внутри. Очистка в u04 её удаляет (после неё 34 760 часов); числа лабораторной выше — как загружено.

Нет машины с Windows или Desktop заблокирован? Пройдите шаги 2–5 на бумаге, используя ожидаемые числа выше. Любое практическое задание этого юнита можно решить без Power BI.

11. Ловушки экзамена — итог

  • Аргумент-фильтр заменяет фильтр на том же столбце; другие столбцы сохраняют свои фильтры.
  • Тот же столбец на оси и в аргументе-фильтре → каждая строка показывает одно и то же число.
  • «С учётом выбора пользователя» → KEEPFILTERS (или табличный фильтр).
  • «Доля от всего» → REMOVEFILTERS / ALL; «доля от выбранного» → ALLSELECTED; «доля внутри страны» → снять только фильтр ClientName / ALLEXCEPT.
  • Булев фильтр не может использовать меру → FILTER ( VALUES ( … ), [Measure] > x ).
  • Вычисляемый столбец показывает общий итог в каждой строке → отсутствует переход контекста; используйте CALCULATE или ссылку на меру.
  • Ссылка на меру = неявный CALCULATE.

12. Запомните картинки

Перескажите каждую картинку одним предложением. Две рамки: CALCULATE подменяет фильтр на столбце, а не сужает его. Воронка: текущая строка становится фильтром. Если вы можете сказать оба, не глядя, у вас в руках суть этой цели экзамена.


Источники: Microsoft Learn — "CALCULATE function (DAX)", "KEEPFILTERS function (DAX)", "ALLSELECTED function (DAX)", "Modify DAX filter context in semantic models" (training module, unit "Perform context transition"), "DAX query view". Exam PL-300 study guide, skills measured as of April 20, 2026.

Задания

Проверка здесь ничего не меняет: ответы начнут засчитываться после записи на курс.

  1. Мини-модель из этого юнита. Billable Hours = CALCULATE ( [Hours], Timesheet[Billable] = "Y" ). В матрице с Client[ClientName] на строках — что возвращает Billable Hours в строке Contoso Energy?

  2. Та же мера: Billable Hours = CALCULATE ( [Hours], Timesheet[Billable] = "Y" ). Теперь на строках матрицы стоит Timesheet[Billable]. Что показывает Billable Hours в строке N?

  3. UK Hours = CALCULATE ( [Hours], Client[Country] = "UK" ). На строках матрицы — Client[Country]. Что показывает UK Hours в строке Ireland?

  4. Та же мера UK Hours, но теперь на строках — Client[ClientName]. Что показывает UK Hours в строке Fabrikam Logistics?

  5. Допишите меру так, чтобы при Country на строках строка Ireland была пустой, а строка UK показывала 26: UK Hours = CALCULATE ( [Hours], ( Client[Country] = "UK" ) )

  6. Hours All Clients = CALCULATE ( [Hours], REMOVEFILTERS ( Client ) ). Матрица с Client[ClientName] на строках. Что она показывает в строке Woodgrove Bank?

  7. В отчёте Aldervane есть срез по Country. Столбец % of Hours в таблице клиентов должен давать в сумме 100 % при любых странах, которые выберет пользователь. Какая функция должна стоять в знаменателе: CALCULATE ( [Hours], ____ ( Client ) )?

  8. Да или нет? CALCULATE ( [Hours], Client[Country] = "UK" ) добавляет Country = UK к любому фильтру, который у визуального элемента уже есть на Client[Country].

  9. Да или нет? В вычисляемом столбце на Client выражение SUM ( Timesheet[Hours] ) возвращает часы клиента текущей строки.

  10. Да или нет? Запись = [Hours] (ссылка на меру) в вычисляемом столбце вызывает переход контекста.

  11. Avg = AVERAGEX ( Client, SUM ( Timesheet[Hours] ) ). Что возвращает Avg в строке итога матрицы по ClientName?

  12. Что делает каждое выражение, использованное как аргумент CALCULATE, с существующим фильтром на Client?

    1. REMOVEFILTERS ( Client )
    2. ALL ( Client[Country] )
    3. Client[Country] = "UK"
    4. KEEPFILTERS ( Client[Country] = "UK" )
    5. FILTER ( Client, Client[Country] = "UK" )
  13. Нужны оплачиваемые часы только по клиентам, у которых итоговое [Hours] больше 10. Какой аргумент-фильтр работает?

  14. На картинке про CALCULATE рамка визуального элемента пропускает синие монеты, а рамка CALCULATE пропускает только охряные. Что это показывает о CALCULATE?