没有内置函数如何实现累加?

How to realize cummulative sum without built-in function?

我需要实现每天的累计求和。

比如我的数据集如下:

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          |...

怎么做?

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] │
└───────┴─────────────────────────────────────────────────────────────────┘

接受的答案非常好,您确实应该使用内置的 arrayCumSum 函数来计算累积和。但是,如果最初问题的动机之一是找出如何创建 accumulate/folding 样式函数 一般 当它们不受 ClickHouse 原生支持时(例如,CumMax, CumMin 等),这是一种适用于 ClickHouse 中的任何聚合函数的方法。

实现这一点的核心逻辑是使用 arrayReduceInRanges 并使用 arrayMap 和 arrayEnumerate 生成 (1, 1), (1, 2), ... (1, n) 形式的所有元组范围。然后,无论您选择哪个函数作为 arrayReduceInRanges 的高阶聚合函数,例如'sum' 或 'max',将变成函数的累积数组形式。该逻辑如下所示:

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

从这里开始,您可以将数组中返回的值进行数组联接,或者将它们保留为数组形式以供进一步计算。

对于面包的特定应用程序,这里有一些可以使用上述核心逻辑的东西(假设你的 table 被命名为 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 子句,它按日期和买家对 table 进行排序,以便保证后续的 groupArray 调用以相同、一致的顺序构建它们的数组(ClickHouse 文档指出否则任何对 groupArray 的调用都可以以随机顺序构造元素。

它可能看起来很复杂,但是当你使用第一个核心逻辑片段分解它时,事实上这里的很多语法都是围绕数组分组和取消分组的,这样我们就可以在数组中完成我们的主要工作space,它应该有一些直观的意义。

输出将如下所示:

+-------+------------+-----------+
| buyer | date       | cum_bread |
+-------+------------+-----------+                                                                           
| b1    | 2018-01-01 |         2 |                                                                           
| b2    | 2018-01-02 |         2 |                                                                           
| b1    | 2018-01-02 |         5 |                                                                           
| b1    | 2018-01-04 |         6 |                                                                           
+-------+------------+-----------+