До вмісту
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?