是否可以进行递归 SQL 查询?
-
09-06-2019 - |
题
我有一个与此类似的表:
CREATE TABLE example (
id integer primary key,
name char(200),
parentid integer,
value integer);
我可以使用parentid 字段将数据排列成树结构。
现在这是我无法解决的一点。给定一个parentid,是否可以编写一条SQL语句来累加该parentid下的所有值字段并向下递归树的分支?
更新: 我使用的是 posgreSQL,所以我无法使用精美的 MS-SQL 功能。无论如何,我希望将其视为一个通用的 SQL 问题。
顺便说一句,我很高兴在提问后 15 分钟内得到了 6 个答案!堆栈溢出!
解决方案
有几种方法可以在 PostgreSQL 中满足您的需要。
如果您可以安装模块,请查看 tablefunc contrib。它有一个 connectby() 函数来处理遍历树。 http://www.postgresql.org/docs/8.3/interactive/tablefunc.html
另请查看 ltree contrib,您可以调整表以使用它: http://www.postgresql.org/docs/8.3/interactive/ltree.html
或者您可以使用 PL/PGSQL 函数自行遍历树。
像这样的东西:
create or replace function example_subtree (integer)
returns setof example as
'declare results record;
child record;
begin
select into results * from example where parent_id = $1;
if found then
return next results;
for child in select id from example
where parent_id = $1
loop
for temp in select * from example_subtree(child.id)
loop
return next temp;
end loop;
end loop;
end if;
return null;
end;' language 'plpgsql';
select sum(value) as value_sum
from example_subtree(1234);
其他提示
以下是使用公用表表达式的示例脚本:
with recursive sumthis(id, val) as (
select id, value
from example
where id = :selectedid
union all
select C.id, C.value
from sumthis P
inner join example C on P.id = C.parentid
)
select sum(val) from sumthis
上面的脚本创建了一个名为的“虚拟”表 sumthis
有列的 id
和 val
. 。它被定义为两个选择合并的结果 union all
.
第一的 select
得到根(where id = :selectedid
).
第二 select
迭代地跟踪先前结果的子级,直到没有任何内容可返回。
然后可以像普通表一样处理最终结果。在这种情况下,val 列被求和。
从 8.4 版本开始,PostgreSQL 递归查询支持 对于使用 SQL 标准的公用表表达式 WITH
句法。
如果您想要一个适用于任何 ANSI 的便携式解决方案 SQL-92 RDBMS,您需要向表中添加新列。
乔·塞尔科 (Joe Celko) 是该书的原作者 嵌套集 在 SQL 中存储层次结构的方法。你可以谷歌 “嵌套集”层次结构 以了解更多有关背景的信息。
或者你可以将parentid重命名为 左侧 并添加一个 右ID.
这是我尝试总结嵌套集的尝试,但由于我不是 Joe Celko,所以它会严重不足:SQL 是一种基于集合的语言,邻接模型(存储父 ID)不是层次结构的基于集合的表示。因此,不存在纯粹的基于集合的方法来查询邻接模式。
然而, 近年来,大多数主要平台都推出了扩展来处理这个精确的问题。因此,如果有人回复 Postgres 特定的解决方案,请务必使用它。
进行递归查询的标准方法 SQL
是递归的 CTE
. PostgreSQL
支持他们,因为 8.4
.
在早期版本中,您可以编写递归集合返回函数:
CREATE FUNCTION fn_hierarchy (parent INT)
RETURNS SETOF example
AS
$$
SELECT example
FROM example
WHERE id = $1
UNION ALL
SELECT fn_hierarchy(id)
FROM example
WHERE parentid = $1
$$
LANGUAGE 'sql';
SELECT *
FROM fn_hierarchy(1)
参见这篇文章:
如果您使用 SQL Server 2005,那么有一种非常酷的方法可以使用公用表表达式来执行此操作。
它消除了创建临时表的所有繁琐工作,并且基本上允许您仅使用WITH和UNION来完成这一切。
这是一个很好的教程:
http://searchwindevelopment.techtarget.com/tip/0,289483,sid8_gci1278207,00.html
以下代码可以编译并测试正常。
create or replace function subtree (bigint) returns setof example as $$ declare results record; entry record; recs record; begin select into results * from example where parent = $1; if found then for entry in select child from example where parent = $1 and child parent loop for recs in select * from subtree(entry.child) loop return next recs; end loop; end loop; end if; return next results; end; $$ language 'plpgsql';
在我的例子中需要条件“child <>parent”,因为节点指向它们自己。
玩得开心 :)
Oracle 有“START WITH”和“CONNECT BY”
select
lpad(' ',2*(level-1)) || to_char(child) s
from
test_connect_by
start with parent is null
connect by prior child = parent;
顺便说一句,尽管这个问题已经得到很好的回答,但应该指出的是,如果我们将其视为:
通用 SQL 问题
那么 SQL 实现相当简单,因为 SQL'99 允许规范中的线性递归(尽管我相信没有 RDBMS 完全实现该标准)通过 WITH RECURSIVE
陈述。所以从理论角度来看我们现在就可以做到这一点。
这些示例对我来说都不起作用,所以我像这样修复了它:
declare results record; entry record; recs record; begin for results in select * from project where pid = $1 loop return next results; for recs in select * from project_subtree(results.id) loop return next recs; end loop; end loop; return; end;
这是 SQL Server 吗?您不能编写一个 TSQL 存储过程来循环并将结果合并在一起吗?
我也很感兴趣是否有一种仅 SQL 的方法可以做到这一点。从我在地理数据库课上的记忆来看,应该有。
我认为在 SQL 2008 中更容易 层次结构ID
如果您需要存储任意图形,而不仅仅是层次结构,您可以将 Postgres 推到一边并尝试图形数据库,例如 快板:
图形数据库中的所有内容都存储为三元组(源节点、边、目标节点),它为操作图形结构并使用类似 SQL 的语言查询它提供了一流的支持。
它与 Hibernate 或 Django ORM 之类的东西集成得不好,但如果您认真对待图形结构(不仅仅是像 Nested Set 模型给您的层次结构),请检查一下。
我还相信 Oracle 最终在其最新产品中添加了对真实图的支持,但我很惊讶它花了这么长时间,很多问题都可以从这个模型中受益。