我在表格中有一个树形结构,它使用物化路径让我能够快速找到孩子。但是,我还需要对结果进行深度优先排序,正如人们对线程论坛回复所期望的那样。
id | parent_id | matpath | created
----+-----------+---------+----------------------------
2 | 1 | 1 | 2010-05-08 15:18:37.987544
3 | 1 | 1 | 2010-05-08 17:38:14.125377
4 | 1 | 1 | 2010-05-08 17:38:57.26743
5 | 1 | 1 | 2010-05-08 17:43:28.211708
7 | 1 | 1 | 2010-05-08 18:18:11.849735
6 | 2 | 1.2 | 2010-05-08 17:50:43.288759
9 | 5 | 1.5 | 2010-05-09 14:02:43.818646
8 | 6 | 1.2.6 | 2010-05-09 14:01:17.632695
所以最终的结果应该按照这样排序:
id | parent_id | matpath | created
----+-----------+---------+----------------------------
2 | 1 | 1 | 2010-05-08 15:18:37.987544
6 | 2 | 1.2 | 2010-05-08 17:50:43.288759
8 | 6 | 1.2.6 | 2010-05-09 14:01:17.632695
3 | 1 | 1 | 2010-05-08 17:38:14.125377
4 | 1 | 1 | 2010-05-08 17:38:57.26743
5 | 1 | 1 | 2010-05-08 17:43:28.211708
9 | 5 | 1.5 | 2010-05-09 14:02:43.818646
7 | 1 | 1 | 2010-05-08 18:18:11.849735
我将如何解决这个问题?我可以在直接SQL(这是PostgreSQL 8.4)中执行此操作,还是应将其他信息添加到此表中?
更新:尝试更好地解释排序条件。
想象一下,id'1'是论坛的根帖,所有带有“1”的“matpath”的帖子都是该帖子的子句。因此,ids 2到5是对1的直接回复,并获得'1'的路径。但是,id 6是回复2,而不是直接为1,因此它得到1.2的matpath。这意味着对于具有正确嵌套的线程论坛,表中显示所有id,论坛的结构将如下所示,因此订购要求:
* id 1 (root post)
* id 2
* id 6
* id 8
* id 3
* id 4
* id 5
* id 9
* id 7
答案 0 :(得分:14)
我相信你的物化路径不对。
你可以用什么逻辑对这样的事情进行排序
1
1.2
1
1.5
为什么第二个1不与第一个一起?
如果你有
1
1.2
2
2.5
这将是微不足道的。
编辑: 我查看了您的示例,并且您没有存储行的物化路径,但是您正在存储父行的物化路径。 这是行的物化路径实际应该是什么样子。如果你将它存储为:
,如果你不会有超过9个分支,那么直接在matpath上进行排序就可以了 id | parent_id | matpath | created
----+-----------+-----------+----------------------------
2 | 1 | 1.2 | 2010-05-08 15:18:37.987544
6 | 2 | 1.2.6 | 2010-05-08 17:50:43.288759
8 | 6 | 1.2.6.8 | 2010-05-09 14:01:17.632695
3 | 1 | 1.3 | 2010-05-08 17:38:14.125377
4 | 1 | 1.4 | 2010-05-08 17:38:57.26743
5 | 1 | 1.5 | 2010-05-08 17:43:28.211708
9 | 5 | 1.5.9 | 2010-05-09 14:02:43.818646
7 | 1 | 1.7 | 2010-05-08 18:18:11.849735
否则(> 9)你必须将matpath
变成类似
001.002.006
001.002.006.008
最多可支持999个分支。
请注意
0001.0002.0006
也会为您提供一个比接受的答案更短的字段答案 1 :(得分:8)
我通常会为此创建一个额外的列,称为SortPath
。它将包含您需要排序的数据,连接在一起。该列的类型为varchar
,并按字符串排序。像这样:
id | parent_id | matpath | created | sortpath
---+-----------+---------+-----------------------------+--------------------------------------------------------------------------------------
2 | 1 | 1 | 2010-05-08 15:18:37.987544 | 2010-05-08 15:18:37.987544-2
6 | 2 | 1.2 | 2010-05-08 17:50:43.288759 | 2010-05-08 15:18:37.987544-2.2010-05-08 17:50:43.288759-6
8 | 6 | 1.2.6 | 2010-05-09 14:01:17.632695 | 2010-05-08 15:18:37.987544-2.2010-05-08 17:50:43.288759-6.2010-05-09 14:01:17.632695-8
3 | 1 | 1 | 2010-05-08 17:38:14.125377 | 2010-05-08 17:38:14.125377-3
4 | 1 | 1 | 2010-05-08 17:38:57.26743 | 2010-05-08 17:38:57.267430-4
5 | 1 | 1 | 2010-05-08 17:43:28.211708 | 2010-05-08 17:43:28.211708-5
9 | 5 | 1.5 | 2010-05-09 14:02:43.818646 | 2010-05-08 17:43:28.211708-5.2010-05-09 14:02:43.818646-9
7 | 1 | 1 | 2010-05-08 18:18:11.849735 | 2010-05-08 18:18:11.849735-7
这里需要注意几点:
sortpath
将被排序为字符串,因此重要的是所有日期都具有相同的长度才能正确排序。例如,观察2010-05-08 17:38:57.26743
列中sortpath
添加额外零的方式。sortpath
中显示当前节点的日期,但它不在matpath
中。我更愿意在两者中看到它。sortcolumn
的开头。这样,如果你想一次查询多个论坛(你可能不会),那么它仍然可以正确排序。答案 2 :(得分:6)
我不确定我理解为什么接受的解决方案根本没有任何意义。它可以工作,但它甚至比@Unreason的解决方案更简单,更低效(更多磁盘空间,更多索引等)(只是在物化路径中填充ID)。
OP面临的整个场景似乎源于这样一个事实,正如@Unreason正确指出的那样,物化路径(MP)的实现是不正确的。 OP已将MP提供给父节点,而不是当前节点。在公认的解决方案中,SortPath
列通过提供当前节点的物化路径来纠正这一点(这次使用日期 - 为什么? - 而不是主键)。
供参考,请考虑以下excerpt:
物化路径
在此方法中,每条记录都存储到根的整个路径。在我们的 在前面的例子中,我们假设KING是一个根节点。然后, 记录ename ='SCOTT'通过路径连接到根目录 SCOTT-> JONES-> KING。现代数据库允许表示列表 节点作为单个值,但由于物化路径已经存在 很久以前就发明了这种惯例 与某个分隔符连接的节点串;最经常 '。'要么 “/'.
答案 3 :(得分:6)
虽然@Unreason关于填充工作的答案,但我想提供另一种解决方案,我相信这是我自己对这个问题的发明。
您正在寻找创建字节流的函数,f(x)=b_1b_2..b_i
(抱歉没有MatJax在SO上)b_i
是一个字节。我们知道两个字节流的比较与第一个不同的字节相同。我们想要f(x)<f(y) iff x<y
。
用0填充相同的长度绝对可以轻松获得此目标。你拿两个数字,看看第一个非零字节就在那里。
Steven Wittens(acko.net)在八年前向Drupal核心引入了一个不同的技巧:将字符串前面的位数作为另一个数字。因此,数字97685成为字符5 9 7 6 8 5
。这也有效:首先查看长度字节,如果它们不相同那么较大的肯定会更大。除此之外,你知道两个数字是相等的长度。他还使用了基数为36的数字,其中0-9a-z是数字,就像每个字母的十六进制一样。这个编码需要两个字节用于前36个节点,三个用于下一个1260 ...
请注意,填充和此缓存变量长度编码都不需要物化路径的分隔符,但为了便于阅读,它们通常都包含在内。
numconv支持base85编码,但需要区分大小写的排序规则。如果你删除小写字母仍有94个ASCII字符,你仍然有base68。
但是如果你使用'二进制'字段,那么你可以做base256:而不是一个狡猾的编码只是把数字写成一系列字节,然后用字节流的长度作为单个字节作为整数。这将允许您编码任何小于2 ^ 2048左右的树。对于前256个节点,您使用两个字节,对于下一个65280,您正在查看三个字节。这已经非常有效了。
我提名utf8encode(x)
功能。考虑一下!您需要下降到bitsorting而不是bytesorting,但这不会改变结果:找到最左边的零位。如果另一个字符串在那里有一个1,那么它将是一个更长的UTF-8编码,因此肯定会更大。如果他们在同一个地方有第一个零,那么我们有相同长度的位串,这对我们来说比较好。
这很好,但分离器呢。 UTF-8算法在将其视为纯粹创建比特流的算法时可以处理31位数 - 因此它适用于包含少于20亿个节点的树。您的物化路径将是UTF-8编码数字的比特流,比较好:丢弃最左边相同的UTF-8编码数字,我们回到上一段。 Q.E.D。
因为我们不需要分隔符或前缀字节,所以我们可以将前128个节点编码为单个字节,然后将下一个1920编码为两个字节,最多为65535,即三个字节。对于四个字节,base256将获胜。对于非常大的树,您可以将UTF-8作为0-2147483647的编码器处理为字节流。所以你可以用它作为base2147483647编码:D
总结一下:UTF-8最适合小树,并且比二十亿节点以下的base256差一些。除此之外,直到2 ^ 2048左右,前缀为长度为base256。除了前缀长度为-2147483647之外,除了那之外什么都没有。
答案 4 :(得分:3)
我无法想到在直接SQL中执行此操作的简单方法。而不是matpath,我将在这里使用node_path。 node_path是matpath ||'。'|| id
id | parent_id | node_path | created
----+-----------+---------+----------------------------
2 | 1 | 1.2 | 2010-05-08 15:18:37.987544
3 | 1 | 1.3 | 2010-05-08 17:38:14.125377
4 | 1 | 1.4 | 2010-05-08 17:38:57.26743
5 | 1 | 1.5 | 2010-05-08 17:43:28.211708
7 | 1 | 1.7 | 2010-05-08 18:18:11.849735
6 | 2 | 1.2.6 | 2010-05-08 17:50:43.288759
9 | 5 | 1.5.9 | 2010-05-09 14:02:43.818646
8 | 6 | 1.2.6.8 | 2010-05-09 14:01:17.632695
现在您要根据node_path对树进行排序,排序字段由您运行排序的次数定义。
在split_part(node_path,'。',recursion_depth)上的plpgsql排序中的自定义递归函数将起作用。您必须检查split_part中的NULL值(并忽略这些值)。