搜索物化路径树的最右侧节点

时间:2015-04-23 18:53:16

标签: sql postgresql tree materialized-path-pattern django-treebeard

是否可以按物化路径树path文本字段进行排序,以便找到树的最右侧节点?例如,考虑这个使用django-treebeard' s MP_Node的python函数:

def get_rightmost_node():
    """Returns the rightmost node in the current tree.

    :rtype: MyNode
    """
    # MyNode is a subclass of django-treebeard's MP_Node.
    return MyNode.objects.order_by('-path').first()

从我的所有测试中,它似乎都回归了我的期望,但我不知道如何提出数学来证明它。而且我还没有找到关于在物化路径树上执行此操作的任何信息。

Treebeard的实现在路径中没有分隔符,所以路径也是如此 如下所示:000100010001000100010012

4 个答案:

答案 0 :(得分:4)

简短回答:不。

Here is a SQLFiddle展示了我在评论中描述的问题。

对于这个简单的设置:

id, path
1,  '1'
2,  '1\2'
3,  '1\3'
4,  '1\4'
5,  '1\5'
6,  '1\6'
7,  '1\7'
8,  '1\8'
9,  '1\9'
10, '1\10'

尝试使用简单排序获取最右边的叶子(id = 10)将失败:

SELECT TOP 1
  id,
  path
FROM hierarchy
ORDER BY path DESC

返回:

id, path
9,  1\9

由于path是基于文字的列,1\10将在 1\9之后以降序排序(请参阅第二个查询的结果小提琴)。

即使您开始追踪深度和路径长度,这通常很便宜且易于跟上,但完全有可能获得这样的路径:

path       depth  length
12\3\11\2  4      9
5\17\10\1  4      9

仍然无法正确排序。

即使您使用的是字母而不是数字,这只会将问题视角推向第26个孩子而不是第10个孩子:

SQLFiddle using letters

我对物化路径操作并不像嵌套集和邻接列表那样熟悉,并且没有使用django的经验,所以如果有一些我不知道的方法,我会推迟别人,但你几乎要当然必须在path列上执行某种解析才能始终获得正确的叶子。

编辑 - 解决了排序是否是一个有效的解决方案的问题,经过一些讨论和对问题的思考后,这里有一些关于其他潜在解决方案的补充说明:

- 当节点可以有两个以上的子节点时,“最右边”是一个模糊的术语(即,树不是二叉树)。如果一个节点有10个子节点,它们位于父节点的左侧,哪些节点位于右侧?您必须先定义此条件,然后才能定义问题的解决方案。

- 为您的问题空间正确定义“最右侧”,了解最右侧的节点不一定位于树的最低层:

        1
       / \
    1\1   1\2 <= This is the rightmost node
    /
  1\1\1 <= This is the lowest node

- 定义“最右边”,可以使用一个简单的循环以编程方式找到最右边的节点:

//in pseudocode
function GetRightmostNode(Node startNode)
{
  Node currentNode = startNode;

  while(currentNode.RightChildren != null)
  {
    currentNode = maximum of currentNode.RightChildren;
  }

  return currentNode;
}

此循环将查找当前节点右侧当前节点的子节点。如果它们存在,它会选择最右边的孩子并重复。一旦它到达一个右边没有子节点的节点,它就会返回当前节点,因为它找到了以startNode为根的树(或子树)最右边的节点。

答案 1 :(得分:3)

  

是否可以按物化路径树的路径文本字段进行排序,以便找到树的最右侧节点?

没有。例如,如果节点路径存储为'/1/3/6/2',请考虑:

/1
/1/3
/1/3/6/2
/1/3/6/5
/1/3/6/21
/1/40

请参阅保罗的答案,理由将上述内容排除在外。

尽管如此,所有希望都没有失去。如果你正在搜索&#34;最右边的节点&#34;,我假设你是指树中最深的节点,你可以简单地计算分隔符。例如:

select length(regexp_replace('/1/3/6/2', '[^/]+', '', 'g')) as depth;

如果您正在寻找最大值,请使用以下内容:

order by length(regexp_replace(path, '[^/]+', '', 'g')) desc

...或等效的python代码。索引选项包括索引相同的表达式,或将结果存储在单独的深度字段中并对其进行索引。

如果您仍然对ID的实际值感兴趣,则上面的数字通常对应于ID,因此请进一步使用该列。如果它们不同,则使用不同的正则表达式提取最右边的数字,并将其转换为整数,以便自然地对它们进行排序(1,11,2),而不是按字典顺序排列(1,11,2): / p>

select regexp_replace('/1/3/6/2', '^.+/', '')::int as value;

答案 2 :(得分:0)

编辑:Paul Griffin正确地指出我的答案是不可靠的,因为它假设节点将处于某个值之下。这是一个更好的尝试,在Denis de Bernardy的深度函数中加入两个旋转。

使用两个排序条件,一个用于深度,然后再一个用于转换为整数的最左侧节点的值:

SELECT path, 
       length(regexp_replace(path, '[^/]+', '', 'g')) as depth,
       regexp_replace(path, '^.*/', '')::int as last       
FROM test 
ORDER BY depth DESC, last DESC;

这会将最高值的最深节点放在顶部。

SQLFiddle

答案 3 :(得分:-1)

您可以使用@Paul解释的方法进行一些修改。您可以在每个数字前面附加0,并且每条路径的长度都可以统一。

可以将节点分配为路径

id |  path
-----------------
1  |  '01'
2  |  '01\01'
3  |  '01\02'
4  |  '01\03'
5  |  '01\04'
6  |  '01\04\01'
7  |  '01\04\02'
8  |  '01\04\03'
9  |  '01\05\01'
10 |  '01\05\02'
11 |  '01\05\03'
12 |  '01\05\04'

如果具有最大子节点数的节点的子节点数小于100,则可以使用上面的示例。

如果它介于100和1000之间,那么您可以像0那样添加额外的001\003\002\005

然后,您可以获得最正确的节点12 as,

SELECT TOP 1 id
FROM tree
ORDER BY path DESC

你可以在这里找到演示。 Demo