Привет, Хабр! Язык запросов DAX популярен и эффективен для построения дашбордов в Business Intelligence, и за счет свой функциональной природы DAX в чем-то ближе к реляционной алгебре, по сравнению с SQL. Особенности DAX удобно рассмотреть на основе примеров DAX-запросов, переведенных на реляционную алгебру. В частности, использование ALL в итераторе SUMX в рамках наиболее популярной DAX функции SUMMARIZECOLUMNS позволяет рассмотреть некоторые нюансы DAX. Если интересно описание ALL в DAX с точки зрения реляционной алгебры — добро пожаловать под кат! :)

Схема данных и реляционные отношения

Чтобы не начинать построение реляционного выражения для DAX с нуля, ранее разобран следующий DAX, в котором не использован ALL:

EVALUATESUMMARIZECOLUMNS (
  'Product'[Product Name],
  'Customer'[Customer Name],
  "Total Quantity", SUMX ( 'Sales', 'Sales'[Quantity] )
)

Сейчас же добавляется ALL в итератор SUMX, т.е. рассматривается DAX вида:

EVALUATESUMMARIZECOLUMNS (
  'Product'[Product Name],
  'Customer'[Customer Name],
  "Total Quantity", SUMX ( ALL ( 'Sales' ), 'Sales'[Quantity] )
)

Напомним схему данных, которая использовалась ранее и будет использоваться сейчас. Была рассмотрена схема звезда с таблицей фактов с продажами Sales, строка которой содержит ключ продукта productkey, ключ клиента customerkey и проданное количество quantity, а также таблицей Product (поля productkey и productname) и таблицей Customer (поля customerkey и customername).

Используемая схема данных для DAX (dax.do)
Используемая схема данных для DAX (dax.do)

Можно напомнить, что было выделено отношение продажи S, элемент si множества S представляет собой кортеж из ключа продукта salesproductkeyi, ключа клиента salescustomerkeyi и проданного количества quantityi:

si ∈ S, si = (salesproductkeyi, salescustomerkeyi, quantityi)

Также выделено отношение продукты P, элемент которого является кортежем pi с ключом productkeyi и именем продукта productnamei:

pi ∈ P, pi = (productkeyi, productnamei)

Наконец, ранее выделено отношение клиенты C, элемент которого ci является кортежем из ключа customekeyi и имени клиента customernamei:

ci ∈ C, сi = (customerkeyi, customernamei)

В итоге из предыдущей статьи следующий DAX:

SUMMARIZECOLUMNS (
    'Product'[Product Name],
    'Customer'[Customer Name],
    "Total Quantity", SUMX ( 'Sales', 'Sales'[Quantity] )
)

соответствует реляционному выражению:

productname, customername F SUM quantitysalesproductkey = productkey AND salescustomerkey = customerkey (S×P×C))

или, с использованием JOIN ⋈, можно записать

productname, customername F SUM quantity (S ⋈ salesproductkey = productkey P ⋈ salescustomerkey = customerkey C)

ALL в DAX

Из описания ALL в DAX Guide видно, что он служит для сброса всех фильтров:

Returns all the rows in a table, or all the values in a column, ignoring any filters that might have been applied.

Рассмотрим добавление ALL в итератор SUMX для отмены фильтр-контекста строки для таблицы фактов Sales, объединенной со справочниками продуктов Product и клиентов Customer:

EVALUATE
SUMMARIZECOLUMNS (
    'Product'[Product Name],
    'Customer'[Customer Name],
    "Total Quantity", SUMX ( ALL ( 'Sales' ), 'Sales'[Quantity] )

)

В данном случае благодаря ALL игнорируется фильтр по 'Product'[Product Name] и 'Customer'[Customer Name] из SUMMARIZECOLUMNS, и в SUMX производится суммирование 'Sales'[Quantity] по всем строкам Sales.

Конечно, такой случай всегда выводит в Total Quantity одно и то же значение, но тем не менее, кейс выгдялит интересным.

ALL в SUMX (dax.do)
ALL в SUMX (dax.do)

Реляционное представление ALL в DAX

Как мы видим, с появлением ALL нам уже не нужно считать сумму 'Sales'[Quantity] с группировкой по продуктам и клиентам вида productname, customername F SUM quantity — теперь эти три поля не будут вместе в одной операции группировки по productname, customername и суммирования quantity, поскольку нужна сумма по всем строкам из Sales, объединенной со справочниками.

Обозначим объединение таблицы фактов со справочниками через

SalesStar = σsalesproductkey = productkey AND salescustomerkey = customerkey (S×P×C)

Или через операцию JOIN ⋈ получим:

SalesStar = S ⋈ salesproductkey = productkey P ⋈ salescustomerkey = customerkey C

Для подсчета ALL в итераторе SUMX нам нужно сохранить связи между таблицами при суммировании в SalesStar, в итоге выполняется агрегация (суммирование) без группировки и подсчитывается необходимая сумма по 'Sales'[Quantity]:

F SUM quantitysalesproductkey = productkey AND salescustomerkey = customerkey (S×P×C)) = F SUM quantity (SalesStar )

Далее используем операцию переименования единственного атрибута в F SUM quantity (SalesStar), это отношение с одним кортежем и одним атрибутом, т.е. по сути отношение, содержащее единственный скалярный результат.

Операция переименования ρ позволяет переименовать отношение и/или его атрибуты непосредственно в выражении реляционной алгебры:

suppose we have a relation R of degree n , R(A1, A2, . . . , An) we can rename the relation, the attributes or both:

• ρS(B1,B2,...,Bn)(R) 

• ρS(R)

• ρ(B1,B2,...,Bn)(R)

Таким образом, с операцией переименования атрибута, получим новое отношение Q:

Q = ρ(sumquantityallsales)(F SUM quantity (SalesStar))

В Q содержится результат F SUM quantity (SalesStar), т.е. единственный кортеж с единственным атрибутом, причем имя атрибута переименовано из SUM quantity в sumquantityallsales, и представляет собой необходимую сумму 'Sales'[Quantity] по всем записям. Видно, что при добавлении ALL теперь F SUM quantity подсчитано без группировки.

В итоге реляционное выражение для исходного DAX с ALL будет следующим:

π productname, customername, sumquantityallsales salesproductkey = productkey AND salescustomerkey = customerkey (S×P×C×Q))

Также можно переписать через JOIN ⋈:

π productname, customername, sumquantityallsales (S ⋈ salesproductkey = productkey P ⋈ salescustomerkey = customerkey C×Q)

Далее упрощаем за счет использования SalesStar и получаем

SalesStar = S ⋈ salesproductkey = productkey P ⋈ salescustomerkey = customerkey C

Q = ρ(sumquantityallsales)(F SUM quantity (SalesStar))

π productname, customername, sumquantityallsales (SalesStar × Q)

С точки зрения производительности можно присоединить скаляр sumquantityallsales позже, после группировки по productname, customername, поэтому можно также записать:

SalesStar = S ⋈ salesproductkey = productkey P ⋈ salescustomerkey = customerkey C

Q = ρ(sumquantityallsales)(F SUM quantity (SalesStar))

π productname, customername, sumquantityallsales (Q × π productname, customername (SalesStar))

Причем видно, что Q — это отношение с одним кортежем с одним атрибутом, т.е. Q — это отношение, которое хранит скалярный результат суммы quantity из Sales по всем записям из объединенного отношения таблицы фактов Sales, справочника продуктов Product и справочника клиентов Customer, что и требовалось в "Total Quantity" из SUMAMRIZECOLUMNS с добавленным ALL.

Надеюсь, пример описания DAX с использованием ALL через реляционную алгебру был интересен. Желаю успешных и быстрых дашбордов! :)

Комментарии (0)