【问题标题】:Searching for the right-most node of a materialized path tree搜索物化路径树的最右边节点
【发布时间】:2015-07-02 03:27:13
【问题描述】:

是否可以通过物化路径树的path 文本字段进行排序以找到树的最右侧节点?例如,考虑这个使用 django-treebeard 的 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 等。

【问题讨论】:

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


【解决方案1】:

简短回答:不。

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 为根的树(或子树)的最右边节点。

【讨论】:

  • 如果我不使用分隔符会有很大的不同吗?
【解决方案2】:

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

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

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

请参阅 Paul 的回答,了解上述排序不起作用的原因。

尽管如此,所有的希望都不会消失。如果您正在搜索“最右边的节点”,我假设您的意思是树中最深的节点,您可以简单地计算分隔符。例如:

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):

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

【讨论】:

  • 这将使您获得最深的级别,但无法可靠地获得该级别上最右边的叶子。您仍然需要解析 /1/3/6/5/1/3/6/21 才能发现哪个数字更大。一个简单的排序会将21 放在5 之前
  • @PaulGriffin:据我了解,OP 会以任何顺序想要两者,或者如果有任何深度为 5 或更多的东西,则需要其他东西。如果他想对它们进行排序,则 5 和 21 将对应于 ID,他可以使用该列对结果集进行排序。 (如果不是,regexp_replace(path, '^.+/', '')::int 并收工。)
  • 就此而言,最右边的节点可能不在最深处。考虑:/1, /1/1, /1/1/1, /1/2。尽管/1/1/1 是最深的节点,但/1/2 是最右边的节点。
  • @PaulGriffin:再次,据我所知,问题不是“排序最右边的节点”,而是“我可以使用排序来找到最右边的节点”。答案是否定的,OP 需要一种方法来实际找到最右边的节点。但我当然可能理解错了这个问题。
  • 对不起,如果我听起来不愉快,这个问题对我来说非常有趣,我很喜欢这里的讨论。我了解 OP 问题的答案是“不,您无法排序以获得最右边的节点”,并且您正在尝试提供一种潜在的方法来实际获得最右边的节点。我要指出的是,最右边的节点不一定在树的最深层次上,总是返回最深层次上的节点的方法并不总是返回有效的结果。
【解决方案3】:

编辑: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

【讨论】:

    【解决方案4】:

    您可以使用@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 之间,那么您可以添加一个额外的 0001\003\002\005 一样明智。

    那么就可以得到最右边的节点12为,

    SELECT TOP 1 id
    FROM tree
    ORDER BY path DESC
    

    您可以在此处找到演示。 Demo

    【讨论】:

    • 这不是敲你的答案,更像是对后代的警告......如果你确定你的数据永远不会超过设定的界限,这些解决方案是可行。但是,您最好对依赖于数据的解决方案保持警惕。过去,我经常被更改规格或数据集所困扰,这些规格或数据集增长了几个数量级,超出了预期的大小,并开始破坏这样的解决方案。那时的修复/重构/维护几乎是不可能的,并且比您刚开始编写更通用的解决方案要花费更多的时间和金钱。
    • 我同意你的看法@PaulGriffin。如果级别超出限制,重构将是一项真正艰巨的任务。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-19
    • 1970-01-01
    • 1970-01-01
    • 2016-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多