如何在SQL中表示数据树?

时间:2010-02-01 10:12:06

标签: sql tree hierarchical-data

我正在编写一个由Tree和TreeNode组合而成的数据树结构。树将包含数据的根和顶级操作。 我正在使用UI库以窗体形式呈现树,我可以将树绑定到TreeView。

我需要在DB中保存这个树和节点。 保存树并获得以下功能的最佳方法是什么:

  1. 直观的实施。
  2. 轻松绑定。将很容易从树移动到DB结构并返回(如果有的话)
  3. 我有两个想法。第一种是将数据序列化为表格中的单行。 第二种是保存在表中,但是当移动到数据实体时,我将在更改的节点上松开表上的行状态。

    有什么想法吗?

8 个答案:

答案 0 :(得分:32)

最简单的实施是邻接列表结构:

id  parent_id  data

但是,某些数据库(尤其是MySQL)在处理此模型时存在一些问题,因为它需要能够运行MySQL缺少的递归查询。

另一个模型是嵌套集

id lft rgt data

其中lftrgt是定义层次结构的任意值(任何子项lftrgt应位于任何父项lft内,{{1} }})

这不需要递归查询,但它更慢,更难维护。

但是,在rgt中,可以使用MySQL abitilies来改进。

请参阅我的博客中的这些文章:

有更详细的解释。

答案 1 :(得分:23)

我已经为这个关于SQL-Antipatterns的slidshare添加了书签,它讨论了几种选择:http://www.slideshare.net/billkarwin/sql-antipatterns-strike-back?src=embed

那里的建议是使用一个封闭表(它在幻灯片中解释)。

以下是摘要(幻灯片77):

                  | Query Child | Query Subtree | Modify Tree | Ref. Integrity
Adjacency List    |    Easy     |     Hard      |    Easy     |      Yes
Path Enumeration  |    Easy     |     Easy      |    Hard     |      No
Nested Sets       |    Hard     |     Easy      |    Hard     |      No
Closure Table     |    Easy     |     Easy      |    Easy     |      Yes

答案 2 :(得分:8)

我很惊讶没有人提到物化路径解决方案,这可能是在标准SQL中使用树的最快方法。

在此方法中,树中的每个节点都有一个路径列,其中存储了从根到节点的完整路径。这涉及非常简单和快速的查询。

查看示例表 node

+---------+-------+
| node_id | path  |
+---------+-------+
| 0       |       |
| 1       | 1     |
| 2       | 2     |
| 3       | 3     |
| 4       | 1.4   |
| 5       | 2.5   |
| 6       | 2.6   |
| 7       | 2.6.7 |
| 8       | 2.6.8 |
| 9       | 2.6.9 |
+---------+-------+

为了获得节点 x 的子节点,您可以编写以下查询:

SELECT * FROM node WHERE path LIKE CONCAT((SELECT path FROM node WHERE node_id = x), '.%')

请注意,列路径应编入索引,以便快速执行 LIKE 子句。

答案 3 :(得分:5)

如果您正在使用PostgreSQL,您可以使用ltree,即contrib扩展中的一个包(默认情况下是),它实现了树数据结构。

来自docs

CREATE TABLE test (path ltree);
INSERT INTO test VALUES ('Top');
INSERT INTO test VALUES ('Top.Science');
INSERT INTO test VALUES ('Top.Science.Astronomy');
INSERT INTO test VALUES ('Top.Science.Astronomy.Astrophysics');
INSERT INTO test VALUES ('Top.Science.Astronomy.Cosmology');
INSERT INTO test VALUES ('Top.Hobbies');
INSERT INTO test VALUES ('Top.Hobbies.Amateurs_Astronomy');
INSERT INTO test VALUES ('Top.Collections');
INSERT INTO test VALUES ('Top.Collections.Pictures');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Stars');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Galaxies');
INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Astronauts');
CREATE INDEX path_gist_idx ON test USING GIST (path);
CREATE INDEX path_idx ON test USING BTREE (path);

您可以执行以下查询:

ltreetest=> SELECT path FROM test WHERE path <@ 'Top.Science';
                path
------------------------------------
 Top.Science
 Top.Science.Astronomy
 Top.Science.Astronomy.Astrophysics
 Top.Science.Astronomy.Cosmology
(4 rows)

答案 4 :(得分:3)

这取决于您将如何查询和更新数据。如果将所有数据存储在一行中,它基本上是一个单元,在不重写所有数据的情况下,您无法查询或部分更新。

如果要将每个元素存储为一行,则应首先阅读Managing Hierarchical Data in MySQL(特定于MySQL,但该建议也适用于许多其他数据库)。

如果您只访问整个树,则邻接列表模型使得难以在不使用递归查询的情况下检索根下的所有节点。如果添加一个链接回头部的额外列,则可以执行SELECT * WHERE head_id = @id并在一个非递归查询中获取整个树,但它会对数据库进行非规范化。

某些数据库具有自定义扩展,可以更轻松地存储和检索层次数据,例如Oracle CONNECT BY

答案 5 :(得分:2)

因为这是在Google搜索中询问“ SQL树”时的最佳答案,所以我将尝试从今天(2018年12月)的角度进行更新。

大多数答案都暗示使用邻接表既简单又缓慢,因此建议使用其他方法。

自版本8(于2018年4月发布)以来,MySQL支持recursive common table expressions (CTE)。 MySQL演出晚了一点,但这打开了一个新选项。

有一个教程here,介绍了如何使用递归查询来管理邻接表。

由于现在递归完全在数据库引擎中运行,因此它比过去(必须在脚本引擎中运行)快得多。

博客here给出了一些测量结果(都有偏差,并且针对Postgres而不是MySQL),但是它表明邻接表不必太慢。

所以我今天的结论是:

  • 如果数据库引擎支持递归,则简单的邻接表可能足够快。
  • 使用自己的数据和引擎进行基准测试。
  • 不要相信过时的建议指出“最佳”方法。

答案 6 :(得分:0)

类似于表“节点”的东西,其中每个节点行包含父ID(除了普通节点数据)。对于root,父级为NULL。

当然,这会让孩子们花费更多时间,但这样实际的数据库会非常简单。

答案 7 :(得分:0)

最好的方法,我认为确实给每个节点一个id和一个parent_id,其中父id是父节点的id。这有几个好处

  1. 如果要更新节点,只需重写该节点的数据。
  2. 当您只想查询某个节点时,您可以获得所需的信息,从而减少数据库连接的开销
  3. 许多编程语言都具有将mysql数据转换为XML或json的功能,这样可以更轻松地使用api打开您的应用程序。