PAYATHON 2026

PostgreSQL 如何将两列按行配对后作为数组参数传给函数?

数据小许

可以在 SQL 查询中直接使用两个带 ORDER BY 的 array_agg 分别生成参数数组,不必修改函数或声明变量。两个数组必须采用相同的排序规则,才能保持元素的对应关系。

使用 array_agg 构造两个参数数组

假设 c1、c2 是普通标量列,且聚合后的数组类型符合函数 f 的参数类型,可以这样写:

SELECT f(
    a => array_agg(t.c1 ORDER BY t.c1, t.c2),
    b => array_agg(t.c2 ORDER BY t.c1, t.c2)
)
FROM t;

这条查询聚合同一批输入行:参数 a 收集 c1,参数 b 收集 c2,两者都按 c1, c2 排序。聚合函数可以直接用于普通 SELECT,不必放在函数内部。

如果原来的 CTE 还负责筛选或其他处理,可以保留它,只替换最后的查询:

WITH ts AS (
    SELECT t.c1, t.c2
    FROM t
)
SELECT f(
    a => array_agg(ts.c1 ORDER BY ts.c1, ts.c2),
    b => array_agg(ts.c2 ORDER BY ts.c1, ts.c2)
)
FROM ts;

原来的写法是否有问题?

原来用两个 ARRAY(SELECT ...) 的写法通常也能满足需求。对于常见标量类型,如果排序并列的行具有相同的 c1、c2 值,这些行的先后顺序不会影响最终的配对结果。

改用 array_agg 的主要好处是,可以在同一条聚合查询中构造两个数组。但不能据此认定性能一定更好,实际差异需要结合数据量和执行计划判断。

两个聚合内的排序表达式、排序方向以及 NULLS FIRST、NULLS LAST 设置应保持一致。直接在 array_agg 内部写 ORDER BY 更明确,不要仅依赖上游查询的排序来约束聚合结果。

如果数据类型或排序规则可能将不同的实际值视为排序相等,而这些差异会影响配对,就需要在两处排序中加入相同的唯一排序键。是否存在这样的键,要根据实际表结构确定。

保留空结果集时的行为

两种写法处理空结果集的方式不同:

  • ARRAY(SELECT ...) 在子查询没有行时返回空数组。
  • array_agg(...) 在没有输入行时返回 NULL。

如果希望改写后仍向函数传入空数组,可以使用 COALESCE:

SELECT f(
    a => COALESCE(
        array_agg(t.c1 ORDER BY t.c1, t.c2),
        '{}'
    ),
    b => COALESCE(
        array_agg(t.c2 ORDER BY t.c1, t.c2),
        '{}'
    )
)
FROM t;

这里的 '{}' 会根据对应 array_agg 的返回类型解析为空数组。如果列类型或函数重载导致类型不匹配,需要按函数的实际参数类型添加显式转换。

NULL 值会占据数组位置

对于普通标量输入,array_agg 会保留列中的 NULL。例如,某行的 c1 为 NULL 时,它在数组中仍占一个位置,另一数组中的对应元素也会保留。

不要为两个聚合分别添加不同的过滤条件。例如,只在参数 a 中排除 c1 IS NULL 的行,可能改变两个数组的长度或位置关系。需要过滤行时,应先筛选共同的输入行,再构造两个数组。

备注:内容仅供参考。