当我提到嵌套集模型时,我指的是here.
所描述的内容我需要在用户定义的层次结构中构建一个用于存储“类别”(我想不出更好的词)的新系统。由于嵌套集模型针对读取而不是写入进行了优化,因此我决定使用它。不幸的是,在我研究和测试嵌套集时,我遇到了如何显示带有排序节点的分层树的问题。例如,如果我有层次结构:
root
finances
budgeting
fy08
projects
research
fabrication
release
trash
我希望对其进行排序,使其显示为:
root
finances
budgeting
fy08
projects
fabrication
release
research
trash
请注意,制作在研究之前出现。
无论如何,经过长时间的搜索后,我看到了诸如“将树存储在一个多维数组中并对其进行排序”和“求助树并序列化为嵌套集模型”的答案(我正在解释... )。无论哪种方式,第一个解决方案是RAM和CPU的可怕浪费,这两者都是非常有限的资源......第二个解决方案看起来像是很多痛苦的代码。
无论如何,我能够弄清楚如何(使用嵌套集模型):
所以我认为#5和#6可用于进行我想要的排序,它也可以用于按排序顺序重建树。
然而,现在我已经看过所有这些我学会了做的事情,我看到#3,#5和#6可以一起用来执行排序插入。如果我做了排序插入,它总是被排序。但是,如果我改变排序标准或者我想要一个不同的排序顺序,我就会回到原点。
这可能只是嵌套集模型的限制吗?它的使用是否会抑制输出的查询排序?
答案 0 :(得分:4)
我认为这确实是嵌套集模型的限制。您无法轻松地对各自父节点中的子节点进行排序,因为结果集的排序对于重构树结构至关重要。
我认为这可能是在插入,更新或删除节点时保持树排序的最佳方法。这甚至使查询非常快,这是此数据结构的主要目标之一。如果为所有操作实现存储过程,则它非常易于使用。
您还可以反转预排序树的排序顺序。您只需使用ORDER BY node.rgt DESC
代替ORDER BY node.lft ASC
。
如果您确实需要支持其他排序条件,则可以通过向每个节点添加第二个lft
和rgt
索引来实现它,并在每个插入/更新时按照其他条件对其进行排序/删除。
答案 1 :(得分:4)
我经常使用嵌套集,我经常遇到同样的问题。我所做的以及我建议的是不对数据库中的项目进行排序。而是在用户界面中对它们进行排序。从数据库中提取所有节点后,无论如何,您可能必须将它们转换为某种分层数据结构。在该结构中,对包含节点子节点的所有数组进行排序。
例如,如果您的前端是Flex应用程序,并且节点的子节点存储在ICollectionView中,则可以使用sort属性使它们以您希望的方式显示。
另一个例子,如果您的前端是PHP脚本的输出,您可以让每个节点的子节点都在一个数组中,并使用PHP的数组排序函数来执行排序。
当然,这只有在你不需要对实际的数据库条目进行排序时才有效,但是你呢?
答案 2 :(得分:2)
我刚刚编写了以下内容,它可以帮我整理整个嵌套集树。
排序(理想情况下)需要一个列出树中每个节点的当前级别的视图和一个交换两个节点的过程 - 两者都包含在下面,兄弟交换代码来自Joe Celkos的Tree&我强烈推荐使用嵌套集的任何人的层次结构书。
可以在'INSERT INTO @t'语句中更改排序,这里是'Name'上的简单字母数字排序
这可能是一种不好的方式,特别是使用光标设置基于代码,但正如我所说它对我有用,希望它有所帮助。
<强>更新强>
下面的代码现在显示不使用cusor的版本。我看到速度改进了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
答案 3 :(得分:1)
是的,它是嵌套集模型的限制,因为嵌套集是层次结构的预先排序表示。这种预先排序是读取如此快速的原因。 邻接模型也在您链接的页面上进行了描述,它提供了最灵活的排序和过滤,但对性能有显着影响。
我在嵌套集中插入和移动的首选方法是在邻接模型中处理受影响的分支:获取新兄弟的列表;在列表中找到新节点的正确位置;并构造所需的更新语句(这是你必须要小心的位置)。至于更改您的订购标准:这是一次性批处理作业,因此您可以负担得起一些RAM和CPU,最灵活的答案是将嵌套集表示分解为邻接表示并重建嵌套集基于新标准的邻接。
答案 4 :(得分:0)
我相信,在您的情况下,您想要交换的节点没有任何后代,您可以简单地交换lft和rgt值。考虑这棵树:
A
/ \
B C
/ \
D E
这可能会变成这组嵌套集:
1 A 10
2 B 3
4 C 9
5 D 6
7 E 8
现在考虑要交换D和E.以下嵌套集有效,D和E交换:
1 A 10
2 B 3
4 C 9
7 D 8
5 E 6
当然,交换具有子树的节点不能以这种方式完成,因为您还需要更新子节点的lft和rgt值。
答案 5 :(得分:0)
您可以在渲染时对它们进行排序。我在这里解释了渲染How to render all records from a nested set into a real html tree
答案 6 :(得分:0)
从我班级的方法中查看我的简单解决方案。 $ this-&gt; table-&gt; order是Nette框架代码,用于从DB获取数据。
$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);
答案 7 :(得分:-1)
排序嵌套集没有限制,并不难。只需按左边的凉亭(锚点,无论如何)进行排序,就完成了。如果每个节点都有LEVEL,您还可以根据级别拉出正确的缩进。