PL/PGSQL - 无法将 SELECT 语句中的 array_agg 分配给变量

Posted

技术标签:

【中文标题】PL/PGSQL - 无法将 SELECT 语句中的 array_agg 分配给变量【英文标题】:PL/PGSQL - Cannot assign array_agg from SELECT statement to variable 【发布时间】:2022-01-19 11:54:59 【问题描述】:

我需要获取给定某个区域 ID 的所有子区域 ID。在表areas 中,有一个引用areas 表本身的parent_id 字段。现在可以有多个级别,例如,ID为1的区域可以是ID为2的区域的父级,而ID为2的区域可以是ID为3的区域的父级等等。所以当我调用这个函数时,我需要某个区域 id 的所有后代。

SELECT id, get_all_children(id) as children from areas where id = 1;

结果应该是

| id       | children       |
| -------- | -------------- |
| 1        | 2,3          |

这是我迄今为止尝试过的

CREATE OR REPLACE FUNCTION get_all_children(ancestors INT[])
RETURNS INT[]
LANGUAGE plpgsql
AS
$$
DECLARE
    childrenRet INT[];
BEGIN
    SELECT
        ARRAY_AGG(id::INT) INTO childrenRet
    FROM areas
    WHERE parent_id = ANY(ancestors);

    IF childrenRet = '' THEN
        RETURN childrenRet;
    END IF;
    RETURN ARRAY_CAT(get_all_children(childrenRet), childrenRet);
    
END;
$$;
SELECT id, get_all_children(id) as children from areas where id = 492;

但我得到的结果是

| id       | children       |
| -------- | -------------- |
| 492      | 3044         |

我尝试将直接子级返回为

CREATE OR REPLACE FUNCTION get_all_children(ancestors INT[])
RETURNS INT[]
LANGUAGE plpgsql
AS
$$
DECLARE
    childrenRet INT[];
BEGIN
    SELECT
        ARRAY_AGG(id::INT) INTO childrenRet
    FROM areas
    WHERE parent_id = ANY(ancestors);

    RETURN childrenRet;
    
END;
$$;
SELECT id, get_all_children(id) as children from areas where id = 492;

我仍然得到相同的结果。

现在我试过了

SELECT ARRAY_AGG(id) FROM areas WHERE parent_id = 492;

这是我得到的结果

                                    array_agg
------------------------------------------------------------------------------------
3044,3075,923,1470,774,1466,1473,1468,1467,3043,1471,1469,922,1472,3076,1474,920

我猜问题出在这里,但我不知道如何处理它。

SELECT
    ARRAY_AGG(id::INT) INTO childrenRet
FROM areas
WHERE parent_id = ANY(ancestors);

【问题讨论】:

您的标题似乎具有误导性。您似乎可以很好地分配给该变量,即没有错误。您的问题在于您的函数返回的结果,因此请重命名您的问题以反映这一点。 如果分配正确,我应该得到一个包含所有直接子元素的数组,而不是只得到第一个元素。 我已经更新了答案,调整了您的程序和结果,以及一个小提琴链接。 【参考方案1】:

您可以简单地将array_agg 操作推迟到最后一个查询表达式,在递归找到所有子代之后,如下所示:

The fiddle

-- Aggregate all of the recursively found descendants
WITH RECURSIVE cte01 (children, lev) AS (
        SELECT a1.id, 0 FROM areas AS a1 WHERE a1.parent_id = 1 UNION ALL
        SELECT a1.id, lev+1
          FROM areas AS a1
          JOIN cte01 AS c1
            ON a1.parent_id = c1.children
     )
SELECT array_agg(children ORDER BY lev, children) AS children FROM cte01
;

结果:

children
2,3,492,774,920,922,923,1466,1467,1468,1469,1470,1471,1472,1473,1474,3043,3044,3075,3076

我们也可以用函数的形式做同样的事情:

CREATE OR REPLACE FUNCTION get_all_children(start_id int)
RETURNS INT[]
LANGUAGE plpgsql
AS
$$
DECLARE
    childrenRet INT[];
BEGIN

WITH RECURSIVE cte01 (children, lev) AS (
        SELECT a1.id, 0 FROM areas AS a1 WHERE a1.parent_id = start_id UNION ALL
        SELECT a1.id, lev+1
          FROM areas AS a1
          JOIN cte01 AS c1
            ON a1.parent_id = c1.children
     )
SELECT array_agg(children ORDER BY lev, children) AS children INTO childrenRet FROM cte01
;
    RETURN childrenRet;
    
END;
$$;

Updated fiddle

例子:

SELECT get_all_children(1) as children;

结果:

children
2,3,492,774,920,922,923,1466,1467,1468,1469,1470,1471,1472,1473,1474,3043,3044,3075,3076

【讨论】:

我想到了这两种方法,但没想到将它们结合起来就能得到我想要的。谢谢你提出来。

以上是关于PL/PGSQL - 无法将 SELECT 语句中的 array_agg 分配给变量的主要内容,如果未能解决你的问题,请参考以下文章

重构 PL/pgSQL 函数以返回各种 SELECT 查询的输出

PL/pgSQL INSERT INTO 表

在 PL/pgSQL 块中运行 SELECT

PL/PGSQL触发器如何检测UPDATE上的SQL语句中是不是提供了列?

如何使用 pl/pgsql 将具有动态元素名称的 JSON 数据转换为行?

将 PL/pgSQL 函数分解成更小的部分