Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/77.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
针对树结构的优化SQL_Sql_Sql Server_Tree Structure - Fatal编程技术网

针对树结构的优化SQL

针对树结构的优化SQL,sql,sql-server,tree-structure,Sql,Sql Server,Tree Structure,如何从性能最佳的数据库中获取树结构数据?例如,假设您在数据库中有一个文件夹层次结构。其中文件夹数据库行具有ID、Name和ParentID列 您是否会使用一种特殊的算法一次获取所有数据,从而最大限度地减少数据库调用的数量,并以代码的形式对其进行处理 或者,您是否会使用对数据库进行多次调用,并直接从数据库中获取结构 可能有不同的答案,基于x个数据库行的数量、层次结构深度或其他什么 编辑:我使用Microsoft SQL Server,但其他透视图中的答案也很有趣。如果数据库中有许多树,并且只能得到

如何从性能最佳的数据库中获取树结构数据?例如,假设您在数据库中有一个文件夹层次结构。其中文件夹数据库行具有IDNameParentID

您是否会使用一种特殊的算法一次获取所有数据,从而最大限度地减少数据库调用的数量,并以代码的形式对其进行处理

或者,您是否会使用对数据库进行多次调用,并直接从数据库中获取结构

可能有不同的答案,基于x个数据库行的数量、层次结构深度或其他什么


编辑:我使用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列上的索引将加快查询速度。

针对层次结构的查询有几种常见类型。大多数其他类型的查询都是这些查询的变体

  • 从父级查找所有子级

    a、 特定深度。例如,给定我的直系父母,深度为1的所有孩子都将是我的兄弟姐妹

    b、 到树的底部

  • 从一个孩子身上,找到所有的父母

    a、 特定深度。例如,我的直系父母是深度为1的父母

    b、 达到无限的深度

  • (a)情况(特定深度)在SQL中更容易。特殊情况(深度=1)在SQL中很简单。非零深度更难。有限但非零深度可以通过有限数量的连接来完成。深度不确定(到顶部,到底部)的情况非常难

    如果你的树是巨大的,那么无论你想做什么,你都处在一个受伤的世界里

    如果你的树在一百万个节点以下,只需将其全部提取到内存中并在内存中工作即可。在OO世界中,生活要简单得多。只需提取行并在返回行时构建树即可

    如果你有一棵巨大的树,你有两个选择

    • 用于处理无限获取的递归游标。这意味着结构的维护是O(1)——只需更新几个节点,您就完成了。但是获取是O(n*log(n)),因为您必须为每个具有子节点的节点打开一个游标

    • 聪明的“堆编号”算法可以对每个节点的父级进行编码。一旦每个节点正确编号,一个简单的SQL SELECT就可以用于所有四种类型的查询。但是,树结构的更改需要对节点重新编号,这使得更改的成本与检索成本相比相当高


    很有趣,因为它展示了一些检索方法以及将沿袭存储为派生列的方法。沿袭提供了一种快捷方法来检索层次结构,而无需太多联接。

    在我所使用的产品中,我们在SQL Server中存储了一些树结构,并使用上述技术来存储节点的层次结构记录中的拱门

    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