【问题标题】:Is it possible to query a tree structure table in MySQL in a single query, to any depth?是否可以在单个查询中查询 MySQL 中的树结构表到任何深度?
【发布时间】:2010-09-15 06:01:15
【问题描述】:

我认为答案是否定的,但我希望有人能够深入了解如何在 SQL (MySQL) 中将树结构爬取到任何深度,但只需一次查询

更具体地说,给定一个树结构表(id、data、data、parent_id)和表中的一行,是否有可能获得所有个后代(child/grandchild/etc),或者就此而言,所有祖先(父母/祖父母/等)都不知道它会下降或上升多远,使用单个查询?

或者正在使用某种递归需求,我不断深入查询直到没有新结果?

具体来说,我使用的是 Ruby 和 Rails,但我猜这不是很相关。

【问题讨论】:

标签: mysql sql database-design data-structures hierarchical-data


【解决方案1】:

是的,这是可能的,这就是所谓的修改前序树遍历,这里有最好的描述

Joe Celko's Trees and Hierarchies in SQL for Smarties

此处提供了一个工作示例(使用 PHP)

http://www.sitepoint.com/article/hierarchical-data-database/2/

【讨论】:

  • 好吧...我现在得去拿那本书。我已经多次使用这种结构,每次都有不同的信息来源......
  • 说真的 - 为 Smarties 获取 Celko 的 SQL。这是 SQL 的圣经
  • 这方面有一整本书?!不能用两三页概括吗? (就像 SitePoint 上的那篇文章!)
  • sitePoint 的文章太棒了!
  • 我曾多次遇到过这个想法,但据我所知,该系统虽然看起来非常聪明,但放弃了许多使 RDBMS 快速和安全的因素。如果你有一棵大树并且需要在最旧的分支之一中插入一些东西,你将不得不更新表中的几乎每条记录,如果你删除的话也是如此。当涉及到这个问题时,我想我会建议孩子们走更多的路。但我不是专家。
【解决方案2】:

这里有几个资源:

基本上,您需要在存储过程或查询中执行某种游标或构建邻接表。我会避免在数据库之外进行递归:取决于你的树有多深,这可能会变得非常慢/粗略。

【讨论】:

  • 它讨论嵌套集的第二个链接非常好 - 我从来没有想过这样做!
  • 该链接还提到了 Joe Celko 的书。
【解决方案3】:

当您提出的主要问题是“我的孩子都是什么”和“我的父母都是什么”时,Daniel Beardsley 的回答根本不是一个糟糕的解决方案。

作为对 Alex Weinstein 的回应,与 Celko 技术相比,这种方法实际上导致对父移动节点的更新更少。在 Celko 的技术中,如果最左边的 2 级节点移动到最右边的 1 级节点之下,那么树中几乎每个节点都需要更新,而不仅仅是节点的子节点。

不过,我想说的是,Daniel 可能以错误的方式将路径存储回根目录。

我会存储它们以便查询

SELECT FROM table WHERE ancestors LIKE "1,2,6%"

这意味着 mysql 可以使用 'ancestors' 列上的索引,而它不能使用前导 %。

【讨论】:

  • 在列中存储逗号分隔的值有几个完整性和性能问题。真的很糟糕。
  • 好的,所以你现在支持你的论点。只是说'这不是一个好主意,没有一些推理支持它对任何人都没有用。您给出的示例是有人使用逗号分隔的列表来存储一对多关系。这显然是错误的。然而,在这里我们将其用作缓存以提高查询性能。这是一个完全不同的用例。我们不建议替换表中的 parent_id 列,只是将缓存的路径存储到 root 中,我们不必递归查询结构。
  • 显然我并没有说这不是一个好主意。我说“有几个完整性和性能问题。”
  • 我不想卷入一场激烈的战争,但你不能只说“这有问题”,而不说这些问题是什么。它不会在讨论中添加任何内容。
【解决方案4】:

我之前遇到过这个问题并且有一个古怪的想法。您可以在每条记录中存储一个 字段,该字段是其直接祖先 id 的串联字符串,一直返回到根目录。

想象一下你有这样的记录(缩进意味着层次结构,数字是 id,祖先。

  • 1,“1”
    • 2, "2,1"
      • 5, "5,2,1"
      • 6, "6,2,1"
        • 7, "7,6,2,1"
        • 11, "11,6,2,1"
    • 3, "3,1"
      • 8, "8,3,1"
      • 9, "9,3,1"
      • 10, "10,3,1"

然后选择id:6的后代,就这样做

SELECT FROM table WHERE ancestors LIKE "%6,2,1"

保持祖先列是最新的可能比它的价值更麻烦,但它在任何数据库中都是可行的解决方案。

【讨论】:

  • 维护这将是地狱。想象一下如果节点 3 发生变化。您需要对其所有子项进行更新。
  • 假设你颠倒了路径的顺序,使得根祖先在左边(例如 id 5 和 6,祖先='1,2'),最直接的父在正确(正如@Kray 在他的回答中所建议的那样),可以通过设置祖先 = REPLACE(祖先,旧路径,新路径)的单个查询来实现更新子节点。如果您的层次结构不是很大或不经常更改,我认为这不是一个坏方法。在这种结构中使用 LIKE "1,2%" 或 FIND_IN_SET,可以大大简化基于祖先的查询(针对后代或祖先)。
【解决方案5】:

Celko 的技术(嵌套集)非常好。我还使用了一个包含“祖先”、“后代”和“距离”字段的邻接表(例如,直系子女/父母的距离为 1,孙子女/祖父母的距离为 2,等等)。

这需要维护,但对于插入来说相当容易:您使用事务,然后将直接链接(父、子、距离 = 1)放入表中,然后 INSERT IGNORE a SELECTion of existing parent&children by添加距离(我可以在有机会时调出 SQL),它希望在 3 个字段中的每一个上都有一个索引以提高性能。这种方法变得丑陋的地方是删除......你基本上必须标记所有受到影响的项目,然后重建它们。但这样做的一个优点是它可以处理任意无环图,而嵌套集模型只能处理直层次结构(例如,除根之外的每个项目都有一个且只有一个父级)。

【讨论】:

    【解决方案6】:

    SQL 不是图灵完备的语言,这意味着您将无法执行这种循环。你可以用 SQL 和树结构做一些非常聪明的事情,但我想不出一种方法来描述在任意深度的层次结构中“在其层次结构中”具有特定 id 的行。

    您最好的选择是遵循@Dan 的建议,即使用其他更强大的语言在树中工作。您实际上可以使用循环以通用语言生成查询字符串,其中查询只是一些复杂的连接(或子查询)系列,它反映了您正在寻找的层次结构的深度。这将比循环和多个查询更有效。

    【讨论】:

      【解决方案7】:

      这绝对可以做到,而且对于 SQL 来说并没有那么复杂。我已经回答了这个问题,并在此处提供了一个使用 mysql 程序代码的工作示例:

      MySQL: How to find leaves in specific node

      Booth:如果您满意,您应该将其中一个答案标记为已接受。

      【讨论】:

        【解决方案8】:

        我使用了https://stackoverflow.com/questions/27013093/recursive-query-emulation-in-mysql 中描述的“With Emulator”例程(由https://stackoverflow.com/users/1726419/yossico 提供)。到目前为止,我已经获得了非常好的结果(性能方面),但我没有大量数据或大量后代可供搜索/搜索。

        【讨论】:

          【解决方案9】:

          您几乎肯定会为此使用一些递归。如果你正在这样做,那么将整个树而不是它的一部分获得固定深度将是微不足道的(实际上更容易)。

          在非常粗略的伪代码中,您会需要以下内容:

          getChildren(parent){
              children = query(SELECT * FROM table WHERE parent_id = parent.id)
              return children
          }
          
          printTree(root){
              print root
              children = getChildren(root)
              for child in children {
                  printTree(child)
              }
          }
          

          虽然在实践中你很少想做这样的事情。这将是相当低效的,因为它对表中的每一行都发出一个请求,所以它只对小表或嵌套不太深的树才有意义。老实说,无论哪种情况,您都可能希望限制深度。

          但是,鉴于这类数据结构的流行,很可能有一些 MySQL 东西可以帮助您解决这个问题,特别是减少您需要进行的查询数量。

          编辑:考虑到这一点,进行所有这些查询几乎没有意义。如果您仍然要读取整个表格,那么您可以将整个表格放入 RAM - 假设它足够小!

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2022-01-11
            • 2019-09-19
            • 1970-01-01
            • 2016-01-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多