18

当我提到嵌套集模型时,我指的是这里描述的内容。

我需要构建一个新系统,用于在用户定义的层次结构中存储“类别”(我想不出更好的词来形容它)。由于嵌套集模型针对读取而不是写入进行了优化,因此我决定使用它。不幸的是,在我对嵌套集的研究和测试过程中,我遇到了如何显示带有排序节点的层次树的问题。例如,如果我有层次结构:

root
    finances
        budgeting
            fy08
    projects
        research
        fabrication
        release
    trash

我希望对其进行排序,使其显示为:

root
    finances
        budgeting
            fy08
    projects
        fabrication
        release
        research
    trash

请注意,制造出现在研究之前。

无论如何,经过长时间的搜索,我看到了诸如“将树存储在多维数组中并对其进行排序”和“将树重新排序并序列化回您的嵌套集合模型”之类的答案(我在解释......)。无论哪种方式,第一个解决方案都是对 RAM 和 CPU 的可怕浪费,它们都是非常有限的资源……第二个解决方案看起来像是很多痛苦的代码。

无论如何,我能够弄清楚如何(使用嵌套集模型):

  1. 在 SQL 中启动新树
  2. 插入一个节点作为树中另一个节点的子节点
  3. 在树中的兄弟节点之后插入一个节点
  4. 从 SQL 中拉出具有层次结构的整个树
  5. 从层次结构中的特定节点(包括根节点)拉取子树,有或没有深度限制
  6. 查找树中任何节点的父节点

所以我想#5 和#6 可以用来做我想要的排序,它也可以用来按排序顺序重建树。

然而,现在我已经查看了所有这些我学会做的事情,我发现#3、#5 和#6 可以一起使用来执行排序插入。如果我做了排序插入,它总是被排序。但是,如果我改变了排序标准或者我想要一个不同的排序顺序,我就会回到原点。

这可能只是嵌套集模型的限制吗?它的使用是否会抑制输出的查询排序?

4

8 回答 8

5

我认为这确实是嵌套集模型的一个限制。您不能轻易地在它们各自的父节点中对子节点进行排序,因为结果集的排序对于重构树结构至关重要。

我认为这可能是在插入、更新或删除节点时保持树排序的最佳方法。这甚至使查询变得非常快,这是这种数据结构的主要目标之一。如果你对所有操作都实现了存储过程,那么它非常容易使用。

您还可以反转预排序树的排序顺序。你只需要使用ORDER BY node.rgt DESC而不是ORDER BY node.lft ASC.

如果您确实需要支持另一个排序标准,您可以通过向每个节点添加第二个lftrgt索引来实现它,并在每次插入/更新/删除时保持按其他标准排序。

于 2008-10-14T20:47:23.737 回答
4

我经常使用嵌套集,并且经常遇到同样的问题。我所做的以及我会推荐的只是不对数据库中的项目进行排序。而是在用户界面中对它们进行排序。从数据库中提取所有节点后,无论如何,您可能必须将它们转换为某种分层数据结构。在该结构中,对包含该节点子节点的所有数组进行排序。

例如,如果您的前端是 Flex 应用程序,并且节点的子节点存储在 ICollectionView 中,您可以使用 sort 属性让它们以您想要的方式显示。

另一个例子,如果您的前端是 PHP 脚本的一些输出,您可以将每个节点的子节点放在一个数组中,并使用 PHP 的数组排序函数来执行排序。

当然,这仅在您不需要对实际的数据库条目进行排序时才有效,但是您呢?

于 2009-01-20T16:22:39.213 回答
2

我刚刚写完以下内容,这对我来说可以对整个嵌套集合树进行排序。

排序(理想情况下)需要一个列出树中每个节点的当前级别的视图和交换两个节点的过程 - 两者都包含在下面,兄弟交换代码来自我强烈推荐的 Joe Celkos 的 Tree & Hierarchies 书任何使用嵌套集的人。

可以在“INSERT INTO @t”语句中更改排序,这里是对“名称”的简单字母数字排序

这可能是一种糟糕的方法,尤其是使用基于集合的代码的光标,但正如我所说,它对我有用,希望它有所帮助。

更新:

下面的代码现在显示不使用游标的版本。我看到大约 10 倍的速度提升

CREATE VIEW dbo.tree_view

AS

SELECT t2.NodeID,t2.lft,t2.rgt ,t2.Name, COUNT(t1.NodeID) AS level  
FROM dbo.tree t1,dbo.tree t2
WHERE t2.lft BETWEEN t1.lft AND t1.rgt
GROUP BY t2.NodeID,t2.lft,t2.rgt,t2.Name

GO

----------------------------------------------

  DECLARE @CurrentNodeID int
DECLARE @CurrentActualOrder int
DECLARE @CurrentRequiredOrder int
DECLARE @DestinationNodeID int
DECLARE @i0 int
DECLARE @i1 int
DECLARE @i2 int
DECLARE @i3 int

DECLARE @t TABLE (TopLft int,NodeID int NOT NULL,lft int NOT NULL,rgt int NOT NULL,Name varchar(50),RequiredOrder int NOT NULL,ActualOrder int NOT NULL)


INSERT INTO @t (toplft,NodeID,lft,rgt,Name,RequiredOrder,ActualOrder)
    SELECT tv2.lft,tv1.NodeID,tv1.lft,tv1.rgt,tv1.Name,ROW_NUMBER() OVER(PARTITION BY tv2.lft ORDER BY tv1.ColumnToSort),ROW_NUMBER() OVER(PARTITION BY tv2.lft ORDER BY tv1.lft ASC)
    FROM dbo.tree_view tv1 
    LEFT OUTER JOIN dbo.tree_view tv2 ON tv1.lft > tv2.lft and tv1.lft < tv2.rgt and tv1.level = tv2.level+1
    WHERE tv2.rgt > tv2.lft+1

    DELETE FROM @t where ActualOrder = RequiredOrder


WHILE EXISTS(SELECT * FROM @t WHERE ActualOrder <> RequiredOrder)
BEGIN


    SELECT Top 1 @CurrentNodeID = NodeID,@CurrentActualOrder = ActualOrder,@CurrentRequiredOrder = RequiredOrder
    FROM @t 
    WHERE ActualOrder <> RequiredOrder
    ORDER BY toplft,requiredorder

    SELECT @DestinationNodeID = NodeID
    FROM @t WHERE ActualOrder = @CurrentRequiredOrder AND TopLft = (SELECT TopLft FROM @t WHERE NodeID = @CurrentNodeID) 

    SELECT @i0 = CASE WHEN c.lft < d.lft THEN c.lft ELSE d.lft END,
            @i1 =  CASE WHEN c.lft < d.lft THEN c.rgt ELSE d.rgt END,
            @i2 =  CASE WHEN c.lft < d.lft THEN d.lft ELSE c.lft END,
            @i3 =  CASE WHEN c.lft < d.lft THEN d.rgt ELSE c.rgt END
    FROM dbo.tree c
    CROSS JOIN dbo.tree d
    WHERE c.NodeID = @CurrentNodeID AND d.NodeID = @DestinationNodeID

    UPDATE dbo.tree
    SET lft = CASE  WHEN lft BETWEEN @i0 AND @i1 THEN @i3 + lft - @i1
                    WHEN lft BETWEEN @i2 AND @i3 THEN @i0 + lft - @i2
            ELSE @i0 + @i3 + lft - @i1 - @i2
            END,
        rgt = CASE  WHEN rgt BETWEEN @i0 AND @i1 THEN @i3 + rgt - @i1
                    WHEN rgt BETWEEN @i2 AND @i3 THEN @i0 + rgt - @i2
            ELSE @i0 + @i3 + rgt - @i1 - @i2
            END
    WHERE lft BETWEEN @i0 AND @i3 
    AND @i0 < @i1
    AND @i1 < @i2
    AND @i2 < @i3

    UPDATE @t SET actualorder = @CurrentRequiredOrder where NodeID = @CurrentNodeID
    UPDATE @t SET actualorder = @CurrentActualOrder where NodeID = @DestinationNodeID

    DELETE FROM @t where ActualOrder = RequiredOrder

END
于 2009-01-19T12:21:33.287 回答
1

是的,这是嵌套集模型的限制,因为嵌套集是层次结构的预排序表示。这种预购是阅读速度如此之快的原因。邻接模型(也在您链接到的页面上进行了描述)提供了最灵活的排序和过滤,但对性能有显着影响。

我在嵌套集中插入和移动的首选方法是像在邻接模型中一样处理受影响的分支:获取新兄弟的列表;在列表中找到新节点的正确位置;并构造所需的更新语句(这是您真正需要小心的地方)。至于改变你的订购标准:这是一个一次性的工作,所以你可以负担得起一些 RAM 和 CPU,最灵活的答案是将嵌套集表示分解为邻接表示并重建嵌套集基于新标准的邻接。

于 2008-10-15T11:55:45.353 回答
0

I believe that, in your case, where the nodes you want to swap don't have any descendants, you can simply swap the lft and rgt values around. Consider this tree:

   A
 /   \
B     C
     / \
    D   E

This could turn into this group of nested sets:

1 A 10 
2 B 3  
4 C 9
5 D 6
7 E 8

Now consider you want to swap D and E. The following nested sets are valid and D and E are swapped:

1 A 10
2 B 3 
4 C 9 
7 D 8
5 E 6 

Swapping nodes that have subtrees cannot be done this way, of course, because you would need to update the childrens' lft and rgt values as well.

于 2010-02-04T11:28:18.590 回答
0

从我的课堂方法中查看我的简单解决方案。$this->table->order 是从 DB 获取数据的 Nette 框架代码。

$tree = Array();
$parents = Array();
$nodes = $this->table->order('depth ASC, parent_id ASC, name ASC');
$i = 0;
$depth = 0;
$parent_id = 0;

foreach($nodes as $node) {
    if($depth < $node->depth || $parent_id < $node->parent_id) {
        $i = $parents["{$node->parent_id}"] + 1;
    }
    $tree[$i] = $node;
    $parents["{$node->id}"] = $i;
    $depth = $node->depth;
    $parent_id = $node->parent_id;
    $i += (($node->rgt - $node->lft - 1) / 2) + 1;
}
ksort($tree);
于 2013-12-08T00:27:11.667 回答
0

您可以在渲染时对其进行排序。我在这里解释了渲染如何将嵌套集中的所有记录渲染到真正的 html 树中

于 2010-11-10T10:18:29.223 回答
-1

对嵌套集进行排序没有限制,也不难。只需按左边的凉亭(锚,随便)排序,就完成了。如果每个节点都有一个级别,您还可以根据级别拉出正确的缩进。

于 2011-01-20T19:42:59.603 回答