ALLSELECTED мы уже встречали: «доля в выборке» считается через неё. Функция удобная и интуитивная — ровно до момента, когда её затягивает внутрь итератора. Тогда она начинает вести себя непредсказуемо, и баг почти не отлаживается. Разбираем по статье SQLBI о best practices.
Что она делает
ALLSELECTED восстанавливает контекст фильтра «как его выбрал пользователь» — без фильтра текущей строки визуала, но с учётом срезов. Отсюда её каноничное применение — доля от итога видимого:
Доля категории =
DIVIDE (
[Выручка],
CALCULATE ( [Выручка], ALLSELECTED ( Товары[Категория] ) )
)
В строке «Молочка» числитель — выручка молочки, знаменатель — выручка по всем видимым категориям. Если пользователь срезом оставил три категории, доли посчитаются внутри этих трёх и дадут в сумме 100%. Это и есть безопасный сценарий.
Почему она коварна: shadow filter context
Под капотом ALLSELECTED работает не как простое «убрать фильтр визуала». Она опирается на теневой контекст фильтра (shadow filter context) — скрытый контекст, который создают итераторы. В разных ситуациях механизм разный:
- в
SUMMARIZECOLUMNS(обычный визуал) — убирает фильтр текущей группы, восстанавливая выбор; - внутри итератора (
SUMX,ADDCOLUMNS) — активирует теневой контекст итерации.
Из-за этого ALLSELECTED в простом визуале и ALLSELECTED внутри итератора — по сути разные вещи, хотя пишутся одинаково.
Если мера с ALLSELECTED вызвана изнутри итератора, включается механизм теневого контекста — и результат становится нелогичным. В разборе SQLBI значение строки оказывалось больше итога — логически невозможное, верный признак, что контекст «поехал».
Пример бага
Допустим, хотим «заметные товары» — те, что дают ≥0,5% выручки. Если порог считать через меру с ALLSELECTED внутри SUMX:
Заметные товары (НЕВЕРНО) =
SUMX (
Товары,
IF ( DIVIDE ( [Выручка], [Выручка по выбору] ) >= 0.005, [Выручка] )
)
-- [Выручка по выбору] содержит ALLSELECTED → вызов из итератора → теневой контекст → мусор
[Выручка по выбору] с ALLSELECTED, вызванная из SUMX, ведёт себя не так, как в визуале. Цифры неверные, и понять почему — почти невозможно, особенно если ALLSELECTED спрятан в цепочке мер.
Как правильно
Правило одно: ALLSELECTED не должен сработать в момент начала итерации. Вычислите его значение до итератора и положите в переменную:
Заметные товары (ВЕРНО) =
VAR _Total = CALCULATE ( [Выручка], ALLSELECTED ( Товары ) )
RETURN
SUMX (
Товары,
IF ( DIVIDE ( [Выручка], _Total ) >= 0.005, [Выручка] )
)
ALLSELECTED отработал один раз снаружи, в нормальном контексте; в SUMX идёт уже готовое число _Total. Теневой контекст не включается — результат корректный.
- Используйте
ALLSELECTEDтолько в мерах, которые кладёте прямо в визуал (доля от видимого). 2. Не вызывайте меры сALLSELECTEDиз других мер и из итераторов. 3. Если значение нужно в итераторе — вынесите его вVARдоSUMX.
ALLSELECTED или REMOVEFILTERS
Из-за коварства ALLSELECTED его область применения стоит держать узкой:
- нужна доля от выбранного пользователем (итог визуала) →
ALLSELECTED, только в верхнеуровневой мере; - нужен итог по всем данным независимо от среза →
REMOVEFILTERS/ALL— они проще и предсказуемее; - сложная логика с порогами и ранжированием внутри итераторов → считайте базу через
ALL/REMOVEFILTERSили выноситеALLSELECTEDвVAR.
ALLSELECTED не «плохая» — она незаменима для процента от видимого. Опасна не функция, а её использование в глубине цепочек мер и итераторов. Держите её на поверхности — и проблем не будет.
Сделайте Доля = DIVIDE ( [Выручка], CALCULATE ( [Выручка], ALL ( Регионы ) ) ) и положите по округам. Чему равна сумма столбца «Доля»? А если заменить ALL на ALLSELECTED и оставить в срезе 3 округа?
Показать ответ
С ALL знаменатель — всегда все 8 округов; сумма видимых долей = 100 % (доля ЦФО ≈ 25,7 %). С ALLSELECTED и срезом из 3 округов знаменатель = сумма этих трёх, доли пересчитываются «от выбранного» — их сумма снова 100 %, но уже среди выбранных. В этом и разница: ALL игнорирует срез, ALLSELECTED его уважает.
Что дальше
С тонкостями фильтр-контекста (ALL, ALLSELECTED, ALLCROSSFILTERED) разобрались. Дальше — field parameters, what-if и DAX-паттерны. Курс пополняется.