针对树结构的优化SQL
如何从性能最佳的数据库中获取树结构数据?例如,假设您在数据库中有一个文件夹层次结构。其中文件夹数据库行具有ID、Name和ParentID列 您是否会使用一种特殊的算法一次获取所有数据,从而最大限度地减少数据库调用的数量,并以代码的形式对其进行处理 或者,您是否会使用对数据库进行多次调用,并直接从数据库中获取结构 可能有不同的答案,基于x个数据库行的数量、层次结构深度或其他什么针对树结构的优化SQL,sql,sql-server,tree-structure,Sql,Sql Server,Tree Structure,如何从性能最佳的数据库中获取树结构数据?例如,假设您在数据库中有一个文件夹层次结构。其中文件夹数据库行具有ID、Name和ParentID列 您是否会使用一种特殊的算法一次获取所有数据,从而最大限度地减少数据库调用的数量,并以代码的形式对其进行处理 或者,您是否会使用对数据库进行多次调用,并直接从数据库中获取结构 可能有不同的答案,基于x个数据库行的数量、层次结构深度或其他什么 编辑:我使用Microsoft SQL Server,但其他透视图中的答案也很有趣。如果数据库中有许多树,并且只能得到
编辑:我使用Microsoft SQL Server,但其他透视图中的答案也很有趣。如果数据库中有许多树,并且只能得到整个树,我会为数据库中的每个节点存储树ID(或根节点ID)和父节点ID,为特定树ID获取所有节点,并在内存中进行处理 但是,如果要获取子树,则只能获取特定父节点ID的子树,因此您需要存储每个节点的所有父节点以使用上述方法,或者在下降到树中时执行多个SQL查询(希望树中没有循环!),尽管您可以重用相同的预处理语句(假设节点的类型相同,并且都存储在一个表中)以防止重新编译SQL,因此它可能不会变慢,事实上,如果将数据库优化应用于查询,则可能更可取。可能需要运行一些测试来找出原因
如果您只存储一棵树,那么您的问题将变成仅查询子树的问题之一,并应用第二个答案。我非常喜欢存储与其父ID关联的ID的简单方法:
ID ParentID
1 null
2 null
3 1
4 2
... ...
它易于维护,并且非常可扩展。谷歌搜索“物化路径”或“基因树”…在Oracle中,有SELECT…CONNECT BY语句来检索树。这实际上取决于您如何访问树 一种巧妙的方法是为每个节点提供一个字符串id,其中父节点的id是子节点的可预测子字符串。例如,父节点可以是“01”,子节点可以是“0100”、“0101”、“0102”等。这样,您可以使用以下方法从数据库中一次选择整个子树:
SELECT * FROM treedata WHERE id LIKE '0101%';
因为标准是初始子字符串,ID列上的索引将加快查询速度。针对层次结构的查询有几种常见类型。大多数其他类型的查询都是这些查询的变体
- 用于处理无限获取的递归游标。这意味着结构的维护是O(1)——只需更新几个节点,您就完成了。但是获取是O(n*log(n)),因为您必须为每个具有子节点的节点打开一个游标
- 聪明的“堆编号”算法可以对每个节点的父级进行编码。一旦每个节点正确编号,一个简单的SQL SELECT就可以用于所有四种类型的查询。但是,树结构的更改需要对节点重新编号,这使得更改的成本与检索成本相比相当高
tblTreeNode
TreeID = 1
TreeNodeID = 100
ParentTreeNodeID = 99
Hierarchy = ".33.59.99.100."
[...] (actual data payload for node)
当然,维护层次结构是一件棘手的事情,需要使用触发器。但是,在插入/删除/移动时生成层次结构绝不是递归的,因为父级或子级的层次结构拥有您需要的所有信息
您可以通过以下方式获得所有节点的子体:
SELECT * FROM tblNode WHERE Hierarchy LIKE '%.100.%'
这是插入触发器:
--Setup the top level if there is any
UPDATE T
SET T.TreeNodeHierarchy = '.' + CONVERT(nvarchar(10), T.TreeNodeID) + '.'
FROM tblTreeNode AS T
INNER JOIN inserted i ON T.TreeNodeID = i.TreeNodeID
WHERE (i.ParentTreeNodeID IS NULL) AND (i.TreeNodeHierarchy IS NULL)
WHILE EXISTS (SELECT * FROM tblTreeNode WHERE TreeNodeHierarchy IS NULL)
BEGIN
--Update those items that we have enough information to update - parent has text in Hierarchy
UPDATE CHILD
SET CHILD.TreeNodeHierarchy = PARENT.TreeNodeHierarchy + CONVERT(nvarchar(10),CHILD.TreeNodeID) + '.'
FROM tblTreeNode AS CHILD
INNER JOIN tblTreeNode AS PARENT ON CHILD.ParentTreeNodeID = PARENT.TreeNodeID
WHERE (CHILD.TreeNodeHierarchy IS NULL) AND (PARENT.TreeNodeHierarchy IS NOT NULL)
END
下面是更新触发器:
--Only want to do something if Parent IDs were changed
IF UPDATE(ParentTreeNodeID)
BEGIN
--Update the changed items to reflect their new parents
UPDATE CHILD
SET CHILD.TreeNodeHierarchy = CASE WHEN PARENT.TreeNodeID IS NULL THEN '.' + CONVERT(nvarchar,CHILD.TreeNodeID) + '.' ELSE PARENT.TreeNodeHierarchy + CONVERT(nvarchar, CHILD.TreeNodeID) + '.' END
FROM tblTreeNode AS CHILD
INNER JOIN inserted AS I ON CHILD.TreeNodeID = I.TreeNodeID
LEFT JOIN tblTreeNode AS PARENT ON CHILD.ParentTreeNodeID = PARENT.TreeNodeID
--Now update any sub items of the changed rows if any exist
IF EXISTS (
SELECT *
FROM tblTreeNode
INNER JOIN deleted ON tblTreeNode.ParentTreeNodeID = deleted.TreeNodeID
)
UPDATE CHILD
SET CHILD.TreeNodeHierarchy = NEWPARENT.TreeNodeHierarchy + RIGHT(CHILD.TreeNodeHierarchy, LEN(CHILD.TreeNodeHierarchy) - LEN(OLDPARENT.TreeNodeHierarchy))
FROM tblTreeNode AS CHILD
INNER JOIN deleted AS OLDPARENT ON CHILD.TreeNodeHierarchy LIKE (OLDPARENT.TreeNodeHierarchy + '%')
INNER JOIN tblTreeNode AS NEWPARENT ON OLDPARENT.TreeNodeID = NEWPARENT.TreeNodeID
END
还有一位是一个检查约束,用于防止树节点中出现循环引用:
ALTER TABLE [dbo].[tblTreeNode] WITH NOCHECK ADD CONSTRAINT [CK_tblTreeNode_TreeNodeHierarchy] CHECK
((charindex(('.' + convert(nvarchar(10),[TreeNodeID]) + '.'),[TreeNodeHierarchy],(charindex(('.' + convert(nvarchar(10),[TreeNodeID]) + '.'),[TreeNodeHierarchy]) + 1)) = 0))
我还建议使用触发器来防止每个树有一个以上的根节点(空父节点),并防止相关节点属于不同的树ID(但这些比上面提到的要简单一些)
您需要检查您的特定案例,以查看此解决方案的性能是否可以接受。希望这能有所帮助!Celko就此(2000年)写道:
还有人问:
最后,您可以查看rails“acts_as_tree”(读重)和“acts_as_嵌套_set”(写重)插件。我没有一个比较它们的好链接。在RDMS中存储树的所有方法中,最常见的是邻接列表和嵌套集。嵌套集针对读取进行了优化,可以在单个查询中检索整个树。邻接列表针对写入进行了优化,可以添加
ID | ParentCommentID
ID | ParentCommentID | TopCommentID