如何在SQL中执行广度优先搜索?

如何在SQL中执行广度优先搜索?,sql,tree,Sql,Tree,给定存储为关系的树: ++++++++++++++++++ | Parent | Child | ++++++++++++++++++ | 1 | 2 | ++++++++++++++++++ | 1 | 3 | ++++++++++++++++++ | 3 | 4 | ++++++++++++++++++ | 3 | 5 | ++++++++++++++++++ | 2 | 6 | +++++++++++

给定存储为关系的树:

++++++++++++++++++
| Parent | Child |
++++++++++++++++++
|   1    |   2   |
++++++++++++++++++
|   1    |   3   |
++++++++++++++++++
|   3    |   4   |
++++++++++++++++++
|   3    |   5   |
++++++++++++++++++
|   2    |   6   |
++++++++++++++++++
|   7    |   8   |
++++++++++++++++++
|   7    |   9   |
++++++++++++++++++
如何获取给定节点的所有后代?比如说, 1,我想要(1,2,3,4,5,6),3我想要(3,4,5),7我想要 想要(7,8,9)

我是从一个脚本(Python,但这并不重要)来做这件事的,所以我 您可以执行以下操作:

children(p):
   nodes = SELECT child FROM relation WHERE parent=p
   for each node in nodes
        nodes += children(node)
   return nodes

nodes = children(root)
但如果有一些时髦的SQL让我可以在一个查询中实现这一点,那么 那太棒了

children(p):
   nodes = SELECT child FROM relation WHERE parent=p
   for each node in nodes
        sql = SELECT child FROM relation WHERE parent=node
.
.
.
        nodes += children(node)
   return nodes

nodes = children(root)

执行两个功能,例如:

有孩子(p)

获取子数组(p)

我最终得到了:

# Parents is a list of strings
def _children(parents):
    if len(parents) == 0:
        return []

    db = self.env.get_db_cnx()
    cursor = db.cursor()
    cursor.execute("SELECT t.id "
                   "FROM ticket AS t "
                   "LEFT OUTER JOIN ticket_custom AS p ON "
                   "    (t.id=p.ticket AND p.name='%s') "
                   "WHERE p.value IN (%s)" % 
                   (self.fields['parent'],
                    "'" + "','".join(parents) + "'"))
    children = ['%s'%row[0] for row in cursor] 
    return parents + _children(children)
虽然我认为函数名有点弱。我可以把它改成树之类的


这在带有PostgreSQL 8(?)的Trac 0.11中有效。

如果您能够更改表定义,那么使用一个而不是直接的父链接可以使此问题更容易解决。Joe Celko的文章对此进行了详细介绍。

您可以使用递归函数。我是用oracle12c做的。语法可能与其他DBMS略有不同。我构建了表,并用以下脚本填充了表中的数据:

create table parent_child (
  parent integer,
  child integer,
  constraint parent_child_pk primary key (parent, child)
);

insert all
  into parent_child values (1, 2)
  into parent_child values (1, 3)
  into parent_child values (3, 4)
  into parent_child values (3, 5)
  into parent_child values (2, 6)
  into parent_child values (7, 8)
  into parent_child values (7, 9)
  select 1 from dual;

commit;
然后,我使用递归函数:

with descendants (node) as (
  select 1 from dual -- root
  union all
  select child from descendants inner join parent_child on parent = node
)
select node from descendants order by node;
select 1 from dual
是锚定成员(递归的基本情况)。它将1放入
子体
表中。您可以使用3或7代替,就像您的示例中一样。然后是递归案例,其中包含
从子代中选择子代内部连接父节点上的父节点
递归成员。这意味着我们在后代中获取新节点(即1)并获取其所有子节点(即2和3),然后将它们添加到
后代
表中。现在我们有1,2和3在表中。我们再次开始使用2和3作为新节点,并得到它们的子节点4和5。下一步,我们有完整的结果,这可能是你想要的

如果我们需要一个字符串,我们可以使用
listagg
,但是我们需要一种排序方法,或者至少需要一种识别根节点的方法。请注意,将表中的元组展平为一个大字符串不是一个好主意

with descendants (node, is_root) as (
  select 1, 'Y' from dual -- root
  union all
  select child, 'N' from descendants inner join parent_child on parent = node
)
select '(' || listagg(node, ', ') within group (order by case is_root when 'Y' then 0 else 1 end, node) || ')' list
from descendants;

它给出了
(1,2,3,4,5,6)

下面是一个详细的示例:。要获得子体(或在我的示例中,间接报告),首先需要将树展平成一条路径。

树可以有多深?递归CTE(SQL 2005及以上)是一个不错的选择,但它们的递归深度相当有限。您可能也应该指定什么是RDBMS。@Philip Kelley:“有限”是什么意思?默认值为100,但您可以显式指定,例如,0,这意味着递归的深度不受限制。@phillip:我想通常情况下,树可以是任何深度。在我的情况下,我怀疑它的深度会超过12层。@JNK:我在使用PostgreSQL,但如果有通用的东西,我更喜欢它。我自己可以将递归转换为循环。;-)@jnk:是的,我正在寻找一种SQL技术。我现在不想弄乱我的模式,我在下面发布的查询中有足够的性能,但这很好。谢谢你的参考!