Как реализовать накопительную сумму без встроенной функции?

Мне нужно проводить кумулятивное суммирование за каждый день.

Например, мой набор данных выглядит следующим образом:

buyer | bread | date      |
---------------------------
b1    |   2   | 2018-01-01|
b1    |   3   | 2018-01-02|
b1    |   1   | 2018-01-04|
b2    |   2   | 2018-01-02|

Мне нужно получить отбор следующим образом:

buyer | cum_sum_on_01_01 | cum_sum_on_01_02 | cum_sum_on_01_03 | cum_sum_on_01_04 | cum_sum_on_01_05 |...
----------------------------------------------------------------------------------------------------------
b1    |        2         |        5         |         5        |      6           |       6          |...
b2    |        0         |        2         |         2        |      2           |       2          |...

Как это сделать?


person Jens    schedule 24.12.2018    source источник
comment
Добро пожаловать в Stack Overflow! См. Как сделать Я делаю X? Ожидание от Stack Overflow таково, что пользователь, задающий вопрос, не только исследует свой вопрос, но и делится своими исследованиями, попытками кода и результатами. Это демонстрирует, что вы потратили время, чтобы попытаться помочь себе, избавляет нас от повторения очевидных ответов и, прежде всего, помогает вам получить более конкретный и актуальный ответ! См. Также: Как задать вопрос   -  person DebanjanB    schedule 24.12.2018


Ответы (2)


В чем смысл without built-in function? Единственный способ получить кумулятивные суммы в ClickHouse на данный момент - arrayCumSum. Итак, ответ - построить массив кандидатов и передать его arrayCumSum. Вот шаги:

шаг 1: создание массива хлеба для каждого покупателя

SELECT
    buyer,
    groupArray(bread) AS breads
FROM
(
    SELECT
        buyer,
        sum(bread) AS bread,
        date
    FROM bbd
    ALL RIGHT JOIN
    (
        WITH
            toDate('2018-01-01') AS min_date,
            toDate('2018-01-31') AS max_date
        SELECT
            arrayJoin(buyers) AS buyer,
            arrayJoin(arrayMap(i -> (min_date + toIntervalDay(i)), range(toUInt64((max_date - min_date) + 1)))) AS date
        FROM
        (
            SELECT groupUniqArray(buyer) AS buyers
            FROM bbd
        )
    ) USING (buyer, date)
    GROUP BY
        buyer,
        date
    ORDER BY
        buyer ASC,
        date ASC
)
GROUP BY buyer

┌─buyer─┬─breads──────────────────────────────────────────────────────────┐
│ b1    │ [2,3,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] │
│ b2    │ [0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] │
└───────┴─────────────────────────────────────────────────────────────────┘

Шаг 2: примените arrayCumSum для каждого покупателя

заменить groupArray(bread) AS breads на arrayCumSum(groupArray(bread)) AS breads

┌─buyer─┬─breads──────────────────────────────────────────────────────────┐
│ b1    │ [2,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6] │
│ b2    │ [0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2] │
└───────┴─────────────────────────────────────────────────────────────────┘
person Amos    schedule 24.12.2018

Принятый ответ превосходен, и вам действительно следует использовать встроенную функцию arrayCumSum для вычисления совокупных сумм. Однако, если одна из мотиваций исходного вопроса заключалась в том, чтобы выяснить, как создать функции стиля накопления / складывания в целом, когда они изначально не поддерживаются ClickHouse (например, CumMax, CumMin и т. Д.) , вот подход, который будет работать с любой агрегатной функцией в ClickHouse.

Основная логика для достижения этого - использовать arrayReduceInRanges и сгенерировать все диапазоны кортежей формы (1, 1), (1, 2), ... (1, n) с помощью arrayMap и arrayEnumerate. Затем, какую бы функцию вы ни выбрали в качестве агрегатной функции высшего порядка для arrayReduceInRanges, например «сумма» или «макс» будет преобразована в кумулятивную форму функции на основе массива. Вот как выглядит эта логика:

WITH arr as (SELECT groupArray(some_col) AS arr_some_col FROM some_table)
SELECT
    arrayReduceInRanges(
        'sum'
        arrayMap(x -> (1, x), arrayEnumerate(arr_some_col))
        arr_some_col
    )
FROM arr

Отсюда вы можете arrayJoin значений обратно из массива или сохранить их в форме массива для дальнейших вычислений.

Для вашего конкретного приложения с хлебом вот что-то, что будет работать с использованием вышеуказанной базовой логики (при условии, что ваша таблица называется bread_data):


WITH ordered AS (SELECT * FROM bread_data ORDER BY date, buyer),
agg AS (
    SELECT
        buyer,
        untuple(
            arrayJoin(
                arrayZip(
                    groupArray(date),
                    arrayReduceInRanges(
                        -- 'sum' or any ClickHouse aggregate function.
                        'sum',
                        arrayMap(x -> (1, x), arrayEnumerate(groupArray(bread))),
                        groupArray(bread)
                    )
                )
            )
        )
    FROM ordered
    GROUP BY buyer
)
SELECT buyer, _ut_1 AS date, _ut_2 as cum_bread
FROM agg
ORDER BY date

Обратите внимание на первое предложение WITH, которое упорядочивает таблицу по дате и покупателю, чтобы последующие вызовы groupArray гарантированно построили свои массивы в том же согласованном порядке (в документации ClickHouse отмечается, что в противном случае любой вызов groupArray может создавать элементы в случайном порядке. ).

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

Результат будет выглядеть так:

+-------+------------+-----------+
| buyer | date       | cum_bread |
+-------+------------+-----------+                                                                           
| b1    | 2018-01-01 |         2 |                                                                           
| b2    | 2018-01-02 |         2 |                                                                           
| b1    | 2018-01-02 |         5 |                                                                           
| b1    | 2018-01-04 |         6 |                                                                           
+-------+------------+-----------+ 
person geeves    schedule 18.12.2020