【问题标题】:How can I do a breadth-first search in SQL?如何在 SQL 中进行广度优先搜索?
【发布时间】:2011-04-01 18:41:14
【问题描述】:

给定一棵存储为关系的树:

++++++++++++++++++
| 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 可以让我在一个查询中做到这一点,那 会很棒。

【问题讨论】:

  • 这棵树可以有多深?递归 CTE(SQL 2005 及更高版本)是一个不错的选择,但它们的递归深度相当有限。
  • 您可能也应该指定什么 RDBMS。
  • @Philip Kelley:“有限”是什么意思? doc 声明默认值为 100,但您可以明确指定,例如 0,这意味着无限的递归深度。
  • @phillip:我想一般来说这棵树可以是任何深度。就我而言,我怀疑它会超过十几个层次。
  • @JNK:我使用的是 PostgreSQL,但如果有通用的,我更喜欢它。

标签: sql tree


【解决方案1】:

如果您有能力更改表定义,那么使用nested set 而不是直接的父链接会使这个问题更容易解决。 Joe Celko 的SQL for Smarties 对此进行了详细介绍。

【讨论】:

  • 我现在不想弄乱我的架构,使用我在下面发布的查询我有足够的性能,但这太棒了。感谢您的参考!
【解决方案2】:
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)

【讨论】:

  • 我可以自己把递归变成循环。 ;-)
  • @jnk:是的,我正在寻找一种 SQL 技术。
【解决方案3】:

我最终得到:

# 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 什么的。

这适用于带有 PostgreSQL 8(?) 的 Trac 0.11。

【讨论】:

    【解决方案4】:

    您可以使用递归 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 是锚成员(递归的基本情况)。它将 1 放入 descendants 表中。您可以使用 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)。

    【讨论】:

    • 这太棒了。您的查询语法在 SQLite 中有效,看起来它也应该在 Postgres 和 MySQL 8+ 中有效。我只需要使用with recursive descendants。 Postgres 文档对这种模式有很好的解释:postgresql.org/docs/9.1/queries-with.html
    【解决方案5】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-16
      • 1970-01-01
      • 1970-01-01
      • 2014-05-26
      相关资源
      最近更新 更多