在PostgreSQL中递归聚合父项

时间:2018-03-20 18:22:15

标签: postgresql recursion parent-child aggregate-functions

在子父表中,我需要汇总每个孩子的所有父母。我可以很容易地在CTE查询中获得每个父项的子项,但无法计算如何反转它(sqfiddle here)。鉴于此:

CREATE TABLE rel(
  child integer,
  parent integer
);

INSERT INTO rel(child, parent)
VALUES
(1,NULL),
(2,1),
(3,1),
(4,3),
(5,2),
(6,4),
(7,2),
(8,7),
(9,8);

将返回父项数组的查询(顺序并不重要):

1, {NULL}
2, {1}
3, {1}
4, {3,1}
5, {2,1}
6, {4,3,1}
7, {2,1}
8, {7,2,1}
9, {8,7,2,1}

2 个答案:

答案 0 :(得分:3)

即使有一个可接受的答案,我想展示如何用更简单的方式在纯SQL中解决问题,使用递归CTE:

WITH RECURSIVE t(child, parentlist) AS (
  SELECT child , ARRAY[]::INTEGER[] FROM rel WHERE parent IS NULL
  UNION
  SELECT rel.child, rel.parent || t.parentlist 
    FROM rel 
    JOIN t ON rel.parent = t.child
) SELECT * FROM t;


 child | parentlist 
-------+------------
     1 | {}
     2 | {1}
     3 | {1}
     4 | {3,1}
     5 | {2,1}
     7 | {2,1}
     6 | {4,3,1}
     8 | {7,2,1}
     9 | {8,7,2,1}
(9 rows)

如果您坚持为父母列表空白的孩子设置单身{NULL},请说

SELECT child,
       CASE WHEN CARDINALITY(parentlist) = 0 
            THEN ARRAY[NULL]::INTEGER[]
            ELSE parentlist
       END
  FROM t;

而不是SELECT * FROM t,但坦率地说,我不明白为什么你应该这样做。

最后一句话:我不知道有任何有效的方法可以用纯SQL或过程语言来处理关系数据库。关键是JOIN本来就很昂贵,如果你有非常大的表,你的查询会花费很多时间。您可以通过索引缓解问题,但解决此类问题的最佳方法是使用图形软件而不是RDBMS。

答案 1 :(得分:2)

为此你*可以创建一个PL。我做了类似的事情,这是我的PL处理任何父子结构,它返回一个表,但对于你的情况我改变了一点:

DROP FUNCTION IF EXISTS ancestors(text,integer,integer);
CREATE OR REPLACE FUNCTION ancestors(
    table_name text,
    son_id integer,-- the id of the son you want its ancestors
    ancestors integer)-- how many ancestors you want. 0 for every ancestor.
RETURNS integer[]
AS $$
DECLARE
    ancestors_list integer[];
    father_id integer:=0;
    query text;
    row integer:=0;
BEGIN
    LOOP
        query:='SELECT child, parent FROM '||quote_ident(table_name) || ' WHERE child='||son_id;
        EXECUTE query INTO son_id,father_id;
        RAISE NOTICE 'son:% | father: %',son_id,father_id;
        IF son_id IS NOT NULL
        THEN
            ancestors_list:=array_append(ancestors_list,father_id);
            son_id:=father_id;
        ELSE
            ancestors:=0;
            father_id:=0;
        END IF;
        IF ancestors=0
        THEN
            EXIT WHEN father_id IS NULL;
        ELSE
            row:=row+1;
            EXIT WHEN ancestors<=row;
        END IF;
    END LOOP;
    RETURN ancestors_list;
END;
$$ LANGUAGE plpgsql;

创建PL后,要获得wat,您只需查询:

SELECT *,ancestors('rel',child,0) from rel

返回:

child | parent | ancestors
------+--------+-----------------
1     | NULL   | {NULL}
2     | 1      | {1,NULL}
3     | 1      | {1,NULL}
4     | 3      | {3,1,NULL}
5     | 2      | {2,1,NULL}
6     | 4      | {4,3,1,NULL}
7     | 2      | {2,1,NULL}
8     | 7      | {7,2,1,NULL}
9     | 8      | {8,7,2,1,NULL}

如果您不想出现NULL,只需更新PL;)