Как стать автором
Поиск
Написать публикацию
Обновить

Комментарии 4

Интересный подход, спасибо за разбор! Но возникает закономерный вопрос — зачем реализовывать такую логику на C#, если даже базовые SQL-движки справляются с задачами декартова произведения и агрегаций на порядок быстрее?

Например, аналогичная операция в SQL Server (декартово произведение 1 млн × 1 млн и выборка первых 1 млн строк):

WITH T1 AS (
    SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS a
    FROM sys.all_objects AS a
    CROSS JOIN sys.all_objects AS b
),
T2 AS (
    SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS b
    FROM sys.all_objects AS a
    CROSS JOIN sys.all_objects AS b
)
SELECT TOP (1000000) *
FROM T1
CROSS JOIN T2;

У меня этот запрос выполняется за ~4 секунды.

А вот эквивалент для ClickHouse (1 млн × 1 млн, первые 1 млн. строк):

SELECT *
FROM
(
    SELECT number AS a
    FROM numbers(1000000)
) AS t1
JOIN
(
    SELECT number AS b
    FROM numbers(1000000)
) AS t2
ON 1 = 1
LIMIT 1000000;

На ClickHouse — ~0.12 сек.

Понятно, что у DaxSharp немного иная цель — эмулировать семантику DAX и SUMMARIZECOLUMNS в .NET-приложениях. Но если говорить исключительно о производительности и объёмах, SQL-инструменты всё ещё выглядят значительно предпочтительнее.

Спасибо за вопрос и комментарий, да, действительно, этот пакет в первую очередь для DAX-подобной семантики в C#.

Кейс для 100 млн записей в таблице фактов и декартова произведения 1 млн × 1 млн — это больше egde кейс для проверки корректности имплементации («ничего не зависает») и производительность пакета на миллионах записей ниже, чем в движках СУБД. Единственное, похоже, что сопоставимый SQL для ClickHouse (без деталей плана выполнения запроса) может выглядеть так (в общем случае поля t1.a и t2.a могут быть и неключевыми в таблицах t1 и t2, поэтому добавляется группировка):

SELECT *
FROM
    (
        (SELECT number % 1000000 AS a
        FROM numbers(100000000)
        GROUP BY a)
        ) AS t1
        JOIN
    (
        (SELECT number % 1000000 AS a
         FROM numbers(100000000)
         GROUP BY a)
        ) AS t2
    ON 1 = 1
LIMIT 1000;

Такой запрос выполняется в ClickHouse уже примерно 2.2 секунды и пока не содержит агрегацию SUM, логику IF и других функций (ISBLANK), т.е. полный вариант запроса будет ещё дольше выполняться. Также если вместо numbers(100000000) для сравнения взять numbers(1000000), то время выполнения запроса будет около половины секунды, тоже можно сказать, что измеряется в секундах, а не миллисекундах.

С другой стороны, за счет отсутствия запроса к СУБД пакет DaxSharp быстрее для меньшего количества записей (в «таблице фактов» Sales и соответствующей переменной sales), но с ограничениями, т.е. если есть возможность пренебречь временем загрузки всей таблицы sales в .NET приложение, загруженная таблица sales содержит актуальные данные и т.д.

Например, DaxSharp быстрее для запросов с 0 записей в sales (т.к. нет запроса к СУБД), для 1, 10, 100 записей, для нескольких тысяч и, возможно, десятков, сотен тысяч записей в sales, в итоге производительность зависит от запроса, данных и т.д.

Также пакет DaxSharp может использоваться без СУБД, например, с данными из файла или из API.

Потому что это GroupBy с вызовом агрегирующей функции.

И EntityFramework транслирует все В

SELECT SUM(NUM), GroupLabel1, GroupLabel2
FROM (какая-то таблица или SELECT)
GROUP BY GroupLabel1, GroupLabel2

Или в EF:

data
.Where(i=>i=2)
.GroupBy(i=> new (GroupLabel1, GroupLabel2))
.Select(i=> new (i.key.GroupLabel1, i.Key.GroupLabel2, i.Sum(j=>j.NUM)));

Естественно, можно все переписать, но надо работать с выражениями.

Как я понял - в статье про то, что нужен именно синтаксис как в DAX.

Хорошее замечание, что стоить иметь в виду EF, тоже рассматриваю его как направление для улучшений пакета.

Как я понял - в статье про то, что нужен именно синтаксис как в DAX.

И также логика SUMMARIZECOLUMNS в DAX Power BI немного сложнее, чем группировка с агрегацией, если кратко, то для примера с группировкой по Product[ProductId] и Customer[CustomerId] даже используются 3 группировки: «исходная» (параметр метода) x => new {x.productId, x.customerId} и на её основе в DaxSharp получаются две другие группировки при помощи рефлексии x => x.productId и x => x.customerId — эти группировки для декартова произведения.

На основе data делаются расчеты выражений (с фильтрацией строк и групп) с исходной группировкой по x => new {x.productId, x.customerId} и пишутся в Dictionary с ключом productId, x.customerId, дальше на основе группировок из рефлексии x => x.productId и x => x.customerId делается перебор для декартова произведения при помощи вложенных итераторов, достаются и возвращаются результаты из подсчитанного Dictionary, реализуется логика первых N значений и т.д.

Т.е. в итоге наверно даже получается не столько похожий на DAX Power BI синтаксис (хотя синтаксис похож), но возможность получить результаты по DAX SUMMARIZECOLUMNS логике декартового произведения, но на C#.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации