我在PostgreSQL中具有以下递归函数。但我想知道它是如何工作的
有人可以帮我解释一下吗?
CREATE OR REPLACE FUNCTION build_hierarchey(location_id int) RETURNS SETOF jsonb
AS $BODY$
BEGIN
RETURN QUERY
SELECT
CASE WHEN COUNT(x) > 0
THEN ((to_jsonb(t) || jsonb_build_object('Children', jsonb_agg(f.x)))
ELSE to_jsonb(t)
END
FROM "Locations" t
LEFT JOIN build_hierarchey(t."Id") AS f(x) ON true
WHERE t."ParentLocationId" = location_id OR (location_id IS null AND t."ParentLocationId" IS null)
GROUP BY t."Id", t."Name";
END;
$BODY$ LANGUAGE 'plpgsql'
这就是我的用法。
从build_hierarchey(null :: int)中选择jsonb_agg(build_hierarchey)
结果为json(hirarchey)
[
{
"Id": 11,
"Name": "Zone-C",
"Children": [
{
"Id": 23,
"Name": "01-C",
"CategoryId": null
},
{
"Id": 20,
"Name": "01-A",
"CategoryId": null
}
],
"CategoryId": null
},
{
"Id": 19,
"Name": "Zone-K",
"CategoryId": null
},
{
"Id": 1,
"Name": "ccc",
"Children": [
{
"Id": 3,
"Name": "01-A",
"CategoryId": null
},
{
"Id": 5,
"Name": "01-C",
"Children": [
{
"Id": 8,
"Name": "01-C-03",
"CategoryId": null
},
{
"Id": 7,
"Name": "01-C-02",
"CategoryId": null
},
{
"Id": 6,
"Name": "01-C-01",
"CategoryId": null
}
],
"CategoryId": null
},
{
"Id": 4,
"Name": "01-B",
"CategoryId": null
}
],
"CategoryId": null
},
{
"Id": 18,
"Name": "Zone-J",
"CategoryId": null
},
{
"Id": 2,
"Name": "Zone-B",
"Children": [
{
"Id": 10,
"Name": "02-A",
"CategoryId": null
},
{
"Id": 9,
"Name": "01-A",
"CategoryId": null
}
],
"CategoryId": null
},
{
"Id": 16,
"Name": "Zone-H",
"CategoryId": null
},
{
"Id": 15,
"Name": "Zone-G",
"CategoryId": null
},
{
"Id": 14,
"Name": "Zone-F",
"CategoryId": null
},
{
"Id": 17,
"Name": "Zone-I",
"CategoryId": null
},
{
"Id": 22,
"Name": "Zone-AA",
"CategoryId": null
}
]
这是“位置”表。 (自我引用)
从“位置”中选择“ ID”,“名称”,“ ParentLocationId”
ID Name ParentLocationId
1 "ccc"
2 "Zone-B"
3 "01-A" 1
4 "01-B" 1
5 "01-C" 1
6 "01-C-01" 5
7 "01-C-02" 5
8 "01-C-03" 5
9 "01-A" 2
10 "02-A" 2
11 "Zone-C"
14 "Zone-F"
15 "Zone-G"
答案 0 :(得分:1)
与许多递归函数一样,我们可以从概念上将其分解为几个步骤。
首先,我们有一个基本情况-在给定null
作为输入的情况下,查询将选择所有locations
而没有父项:
SELECT
...
FROM "Locations" t
WHERE location_id IS null AND t."ParentLocationId" IS null
接下来,我们遇到重复的情况-给定ID作为输入时,查询会选择所有以该ID作为其父对象的locations
:
SELECT
...
FROM "Locations" t
WHERE t."ParentLocationId" = location_id
然后,我们添加递归-当返回上述任一列表时,也请再次调用该函数,并找到每个ID:
SELECT
...
FROM "Locations" t
LEFT JOIN build_hierarchey(t."Id") AS f(x) ON true
存在ON true
子句是因为必须有一些条件,但是要连接的实际行由传递给函数的t."Id"
确定。它必须是LEFT JOIN
,因为该函数有时将不生成任何行。例如带有示例数据build_hierarchey(3)
的行将不返回任何行,但是我们不希望它从build_hierarchey(1)
的结果中排除位置3。
现在,我们需要将当前查询(t
)的结果与递归函数调用(f
)的结果相结合:
SELECT
CASE WHEN COUNT(x) > 0
THEN ((to_jsonb(t) || jsonb_build_object('Children', jsonb_agg(f.x)))
ELSE to_jsonb(t)
END
FROM ... as t
LEFT JOIN ... as f(x)
GROUP BY t."Id", t."Name";
同样,这必须处理函数不返回任何行的情况。其余的只是格式化:将每个结果集展平为JSON对象,然后将它们组合为所需的结构。
现在,您的问题:
执行顺序(先由每个根节点进行深层次化,或者先列出所有根节点,然后再进行下一级。)
在SQL中,谈论执行顺序很少有用,因为DBMS不会一次生成一行并吐出结果,而是试图高效地生成所请求的数据尽可能。据我所知,DBMS可以自由选择是否将所有级别的所有行都加载到内存中,并在每个行上运行该函数。还是在遇到每行时是否先深度优先运行该功能。
通常来说,唯一可以保证的是结果将按照查询所请求的顺序显示。在这种情况下,没有ORDER BY
子句,因此每个级别的结果的实际顺序也是完全不确定的-DBMS将选择运行查询时最方便的顺序。 / p>
如何从此功能中断。
实际上不能保证退出此功能;其退出条件取决于您的数据不包含周期的假设,也就是说,它最终将到达没有“子项”(即,一个从未出现在ParentLocationId
列中的ID)的节点。
为演示周期,请考虑以下数据:
ID Name ParentLocationId
1 "A" 3
2 "B" 1
3 "C" 2
对build_hierarchey(1)
的调用将如下进行:
这可能在应用程序的其他位置被阻止,但是我们还有一个额外的保护:通过调用build_hierarchey(null)
,我们永远不会遇到这样的循环。我们可以证明如下:
null
作为其父节点的节点不属于周期null
的父级,因为该父级不能成为循环的一部分null
的节点开始(build_hierarchey(null)
就是这样),即使表中存在一个节点也永远不会达到周期