类别:

聚合函数 (通用)

ACCUMULATE

返回由四个用户定义的 SQL Lambda 函数(initialize、accumulate、combine 和 terminate)计算出的自定义聚合值。ACCUMULATE 遵循 MapReduce 聚合模型,并以与内置聚合函数相同的方式与 GROUP BY、HAVING 和子查询集成。

对于原型设计以及没有内置聚合函数支持的一次性聚合,ACCUMULATE 特别有用。对于性能敏感型工作负载,建议尽可能优先使用内置聚合函数,或考虑是否可以通过内置聚合函数与连接的组合来实现相同结果。使用 OBJECT、ARRAY 或 VARIANT 状态类型的 ACCUMULATE 会产生额外的性能开销。

语法

ACCUMULATE(
    <input_expr>,
    <initialize_lambda>,
    <accumulate_lambda>,
    <combine_lambda>,
    <terminate_lambda>
)

实参

input_expr

对每个非 NULL 输入行求值的表达式。求得的结果值将作为输入值传递给各个 Lambda 函数。

initialize_lambda

签名为 (value) -> <state_expr> 的 Lambda。对每个非 NULL 输入行调用一次,根据该行的值生成初始局部状态。

accumulate_lambda

签名为 (state, value) -> <state_expr> 的 Lambda。将新的输入值合并到现有的局部状态中,并返回更新后的状态。

combine_lambda

签名为 (state1, state2) -> <state_expr> 的 Lambda。合并由并行工作线程生成的两个局部状态。必须满足结合律。

terminate_lambda

签名为 (state) -> <output_expr> 的 Lambda。将最终合并的状态转换为返回给查询的结果值。

Lambda 实参名称是任意的。类型注释是可选的;有关详细信息,请参阅 类型推断

返回

返回由 terminate_lambda 生成的值。数据类型与 terminate_lambda 的返回类型相匹配。如果所有输入行是 NULL 或输入集为空,则返回 NULL。

使用说明

  • 在调用任何 Lambda 之前,系统会静默跳过 NULL 输入行,这与标准 SQL 聚合行为一致。如果所有输入行是 NULL 或输入集是空,则结果为 NULL。

  • ACCUMULATE 没有持久形式。不存在 CREATE AGGREGATE FUNCTION 或等效的 DDL。要重用聚合,请将其封装在视图、CTE 或存储过程中。

  • Lambda 只能引用其声明的参数。不允许引用外部查询中的列。要包含外部查询值,请将其投射到输入表达式中,或在 CTE 中预先计算。

    以下示例会导致编译错误,因为 column2 不是 Lambda 参数:

    -- ERROR: column2 is a column reference, not a lambda parameter.
    SELECT ACCUMULATE(
        column1,
        (v INT) -> v + column2,
        ...
    ) FROM t;
    
  • Lambda 内部不允许使用以下函数类:

    函数类

    示例

    聚合函数

    SUM、 AVG、 COUNT

    窗口函数

    ROW_NUMBER() OVER (...)

    非确定性函数

    RANDOM(), UUID_STRING()

类型推断

Lambda 实参类型是可选的。如果省略,系统会从输入表达式推断类型,并沿 Lambda 链进行传递。

  • 显式类型不会在不同的 Lambda 之间进行强制转换。如果在任何 Lambda 实参上标注了类型,该标注即为该位置状态类型的权威定义。如果四个 Lambda 之间的注释不一致且无法调和(例如 initialize 返回 ARRAY,但 accumulate 将状态声明为 INT),则编译将失败。

  • 您可以省略所有类型注释,或仅为部分实参添加注释;编译器会自动推断并扩展类型。只要显式注释一致,就允许混合使用带注释和未注释的实参。

  • 状态类型(在 initialize、accumulate 和 combine 之间流转的类型)与输出类型(terminate 的返回类型)会被独立跟踪,且二者可能不一致。

  • 输入表达式会被隐式转换为 initialize Lambda 的 value 实参所期望的类型。例如,如果输入列为 INT 且 value 声明为 STRING,则会自动执行类型转换。

示例

计算列的总和,模拟现有 SUM(c1) 聚合函数的等效行为:

SELECT
    ACCUMULATE(
        c1,
        (v INT)                      -> v,
        (state INT, v INT)           -> state + v,
        (state1 INT, state2 INT)     -> state1 + state2,
        (state INT)                  -> state
    ) AS total
FROM t;

计算组中所有值的乘积。

SELECT
    ACCUMULATE(
        c1,
        (v INT)                      -> v,
        (state INT, v INT)           -> state * v,
        (state1 INT, state2 INT)     -> state1 * state2,
        (state INT)                  -> state
    ) AS total
FROM t;

使用 ARRAY 跟踪运行总和与计数来计算平均值:

SELECT ACCUMULATE(
    c1,
    (v INT)                        -> [v, 1],
    (state ARRAY, v INT)           -> [state[0] + v, state[1] + 1],
    (state1 ARRAY, state2 ARRAY)   -> [state1[0] + state2[0], state1[1] + state2[1]],
    (state ARRAY)                  -> state[0] / state[1]
) AS mean
FROM t;

使用结构化 OBJECT 作为状态来计算平均值:

SELECT ACCUMULATE(
    c1,
    (v INT) -> {'sum': v, 'count': 1}::OBJECT(sum INT, count INT),
    (state OBJECT(sum INT, count INT), v) ->
        {'sum': state:sum + v, 'count': state:count + 1}::OBJECT(sum INT, count INT),
    (state1, state2) ->
        {'sum': state1:sum + state2:sum, 'count': state1:count + state2:count}::OBJECT(sum INT, count INT),
    (state) -> state:sum / state:count
) AS mean
FROM t;

查找列中的最短字符串:

SELECT ACCUMULATE(
    c1,
    (v STRING) -> v,
    (state STRING, v STRING) ->
        CASE WHEN LENGTH(v) < LENGTH(state) THEN v ELSE state END,
    (state1 STRING, state2 STRING) ->
        CASE WHEN LENGTH(state1) <= LENGTH(state2) THEN state1 ELSE state2 END,
    (state STRING) -> state
) AS shortest
FROM t;

将 ACCUMULATE 与 GROUP BY 结合使用:

SELECT
    category,
    ACCUMULATE(
        amount,
        (v INT)                      -> v,
        (state INT, v INT)           -> state + v,
        (state1 INT, state2 INT)     -> state1 + state2,
        (state INT)                  -> state
    ) AS category_total
FROM orders
GROUP BY category;

在 Lambda 内部调用 UDF:

SELECT ACCUMULATE(
    c1,
    (v NUMBER)       -> v,
    (state, v)       -> my_sum_udf(state, v),
    (state1, state2) -> my_sum_udf(state1, state2),
    (state)          -> state
) AS total
FROM t;