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

时间:2011-04-01 18:41:15

标签: sql tree

给定存储为关系的树:

++++++++++++++++++
| 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允许我在一个查询中执行此操作,那么 会很棒的。

5 个答案:

答案 0 :(得分:2)

如果您能够更改表定义,那么使用nested set而不是直接父链接可以更容易地解决此问题。 Joe Celko的SQL for Smarties详细介绍了这一点。

答案 1 :(得分:1)

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)

OR

执行两个功能,例如:

有孩子(p)

获取子数组(p)

答案 2 :(得分:1)

我最终得到了:

# 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)

虽然我觉得这个功能的名字有点弱。我可能会将其更改为_tree或其他内容。

这适用于Trac 0.11和PostgreSQL 8(?)。

答案 3 :(得分:0)

您可以使用递归WITH。我是用Oracle 12c做的。语法可能与另一个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:

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是锚成员(递归的基本情况)。它在descendants表中放置1。您可以使用3或7,就像在您的示例中一样。然后是select child from descendants inner join parent_child on parent = node递归成员的递归案例。这意味着我们在后代(即1)中获取新节点并获取所有子节点(即2和3)并将它们添加到descendants表中。所以,现在我们在表格中有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)

答案 4 :(得分:0)

这是一个详细的示例:Hierarchical Span of Control report in SQL, without Oracle CONNECT BY syntax?。要获取后代(或在我的示例中为间接报告),首先需要将树展平到路径中。