【问题标题】:How to find distinct root-nodes of newest nodes in trees (hold in closure table)?如何找到树中最新节点的不同根节点(保存在闭包表中)?
【发布时间】:2011-02-10 14:52:03
【问题描述】:

我尝试将消息树存储为 MySQL 上的闭包表。主要是从 Bill Karwin 的演讲 Models for hierarchical data 中了解到这种方法。问题是:我想找到不同的 3 个根节点(= 没有祖先的节点),它们的树中有最新的节点。注意!即使某个根节点的子树中有 10 个最新节点,它也只会计算一次。

所有节点都有其修改时间,为简单起见,我们可以说节点 id 也代表它们最后一次修改的时间(但我们不能在查询中使用 id 作为时间),第一个是第一个,最后一个是第 17 个。

1
 2
  8
   15
   16
  17
 7
3
 4
  5
 6
9
 12
10
 14
11
 13

在闭包表中,我有 3 列(祖先、后代、深度),因此树的呈现方式如下:

| ancestor | descendant | depth |
+----------+------------+-------+
|        1 |          1 |     0 | 
|        1 |          2 |     1 | 
|        1 |          7 |     1 | 
|        1 |          8 |     2 | 
|        1 |         15 |     3 | 
|        1 |         16 |     3 | 
|        1 |         17 |     2 | 
|        2 |          2 |     0 | 
|        2 |          8 |     1 | 
|        2 |         15 |     2 | 
|        2 |         16 |     2 | 
|        2 |         17 |     1 | 
|        3 |          3 |     0 | 
|        3 |          4 |     1 | 
|        3 |          5 |     2 | 
|        3 |          6 |     1 | 
|        4 |          4 |     0 | 
|        4 |          5 |     1 | 
|        5 |          5 |     0 | 
|        6 |          6 |     0 | 
|        7 |          7 |     0 | 
|        8 |          8 |     0 | 
|        8 |         15 |     1 | 
|        8 |         16 |     1 | 
|        9 |          9 |     0 | 
|        9 |         12 |     1 | 
|       10 |         10 |     0 | 
|       10 |         14 |     1 | 
|       11 |         11 |     0 | 
|       11 |         13 |     1 | 
|       12 |         12 |     0 | 
|       13 |         13 |     0 | 
|       14 |         14 |     0 | 
|       15 |         15 |     0 | 
|       16 |         16 |     0 | 
|       17 |         17 |     0 | 

我可以得到这样的最新子树:

SELECT c.ancestor, MAX(time) AS t 
FROM closure c 
    JOIN nodes n ON (c.descendant = n.node AND c.ancestor <> n.node) 
GROUP BY c.ancestor ORDER BY t desc;

但是我怎样才能获得具有最新帖子的不同 3 个 root 节点(在这种情况下为 1、10 和 11)?一个查询是否可能(并且合理)?


编辑:我输入sample tables to pastebin

【问题讨论】:

  • 你能给出节点表的示例数据吗?

标签: sql tree hierarchical-data


【解决方案1】:

这个线程相当老了,但我在想出一个解决方案之前偶然发现了它,除了标准的祖先和后代之外不需要额外的列,你甚至不需要时间,因为 OP 自己说明了问题:你想要没有其他人的后代的祖先。下面是最终查询,下面是测试数据,大家自己试试吧。

select a.node_name, a.node_id
from test.hier a left outer join 
             (select coo.descendant /* coo = CHILD OF OTHER */
              from test.closure_tree coo right outer join test.closure_tree ro
                    on coo.ancestor <> ro.descendant /* ignore its self reference */
                    and coo.descendant = ro.descendant /* belongs to another node besides itself */)lo 
on a.node_id = lo.descendant
where lo.descendant is null /* wasn't found to be a child of another node besides itself */
group by a.node_name, a.node_id

加载此测试层次结构的测试数据脚本:

--create table test.hier (
--  node_name varchar(10), 
--  node_id int identity (1,1) primary key
--)     

--insert into test.hier (node_name)
--values ('ROOT1')
--insert into test.hier (node_name)
--values ('ROOT2')
--insert into test.hier (node_name)
--values ('ROOT3')
--insert into test.hier (node_name)
--values ('ChildOf1')
--insert into test.hier (node_name)
--values ('ChildOf1')
--insert into test.hier (node_name)
--values ('ChildOf1')
--insert into test.hier (node_name)
--values ('ChildOf1')
--insert into test.hier (node_name)
--values ('ChildOf1')
--insert into test.hier (node_name)
--values ('ChildOf2')
--insert into test.hier (node_name)
--values ('ChildOf2')
--insert into test.hier (node_name)
--values ('ChildOf3')
--insert into test.hier (node_name)
--values ('ChildOf3')
--insert into test.hier (node_name)
--values ('ChildOf3')
--insert into test.hier (node_name)
--values ('ChildOf3')
--insert into test.hier (node_name)
--values ('LeafOf3')
--insert into test.hier (node_name)
--values ('LeafOf3')
--insert into test.hier (node_name)
--values ('LeafOf3')
--insert into test.hier (node_name)
--values ('LeafOf3')
--insert into test.hier (node_name)
--values ('LeafOf1')
--insert into test.hier (node_name)
--values ('LeafOf2')

--create table test.closure_tree (
--  ancestor int, 
--  descendant int, 
--  PRIMARY KEY (ancestor, descendant), 
--  constraint fk_test_a FOREIGN KEY (ancestor) references test.hier (node_id), 
--  constraint fk_test_d FOREIGN KEY (descendant) references test.hier (node_id)
--)

-- SELF REFERENCES 
--insert into test.closure_tree (ancestor, descendant)
--select node_id as a, node_id as d
--from test.hier

--insert into test.closure_tree (ancestor, descendant)
--select a.node_id, b.node_id 
--from test.hier a join test.hier b
--      on a.node_name = 'ROOT1' 
--      and b.node_name = 'ChildOf1'

--insert into test.closure_tree (ancestor, descendant)
--select a.node_id, b.node_id 
--from test.hier a join test.hier b
--      on a.node_name = 'ROOT2' 
--      and b.node_name = 'ChildOf2'

--insert into test.closure_tree (ancestor, descendant)
--select a.node_id, b.node_id 
--from test.hier a join test.hier b
--      on a.node_name = 'ROOT3' 
--      and b.node_name = 'ChildOf3'

--insert into test.closure_tree (ancestor, descendant)
--select a.node_id, b.node_id 
--from test.hier a join test.hier b
--      on a.node_name = 'ChildOf3' 
--      and b.node_name = 'LeafOf3'

--insert into test.closure_tree (ancestor, descendant)
--select a.node_id, b.node_id 
--from test.hier a join test.hier b
--      on a.node_name = 'ROOT3' 
--      and b.node_name = 'LeafOf3'

--insert into test.closure_tree (ancestor, descendant)
--select a.node_id, b.node_id 
--from test.hier a join test.hier b
--      on a.node_name = 'ROOT1' 
--      and b.node_name = 'LeafOf1'

--insert into test.closure_tree (ancestor, descendant)
--select a.node_id, b.node_id 
--from test.hier a join test.hier b
--      on a.node_name = 'ChildOf1' 
--      and b.node_name = 'LeafOf1'

--insert into test.closure_tree (ancestor, descendant)
--select a.node_id, b.node_id 
--from test.hier a join test.hier b
--      on a.node_name = 'ChildOf2' 
--      and b.node_name = 'LeafOf2'

--insert into test.closure_tree (ancestor, descendant)
--select a.node_id, b.node_id 
--from test.hier a join test.hier b
--      on a.node_name = 'Root2' 
--      and b.node_name = 'LeafOf2'


---- Test read of hierarchy with weird ordering for human readability
--select a.node_name, b.node_name as descendant_node_name 
--from test.hier a join test.closure_tree c
--  on a.node_id = c.ancestor
--  join test.hier b
--  on c.descendant = b.node_id
--order by right(a.node_name, 1), left(a.node_name, 1) desc

【讨论】:

  • 为我工作!谢谢
【解决方案2】:

我找到了解决办法。 “有点”,因为我不得不在节点表中使用附加列:root。它表示节点是否是根节点。使用这个额外的位,我可以编写这样的查询:

SELECT c.ancestor, MAX(n.time) AS t FROM closure c
    JOIN nodes n ON (c.descendant = n.node AND c.ancestor <> n.node)
    JOIN nodes n2 ON (c.ancestor = n2.node AND n2.root = 1) 
    GROUP BY c.ancestor ORDER BY t desc LIMIT 3;

在我看来,它的表现相当不错。它也可以扩展。我生成了具有 100000 个节点的树,大约需要 1 秒才能获得结果(最大树深度为 18)。

我附上了用于内容生成的 perl 脚本(和表模式),所以也许有些人可以调整这个查询以更好地执行。

#!/usr/bin/perl --

use strict;
use warnings;
use Data::Random qw(:all);
my ($maxnode, $node) = ();

my $dbh = !DATABASE INIT!

foreach ( 1 .. $ARGV[0] ) {
    $node = ($_ == 1) ? 0 : int( rand(4) );

    if (!$node) {
        $maxnode = &RootNode(1);
    }
    else {
        $maxnode = &Node($maxnode);
    }
}


##
## 
sub Node {
my $parent = int( rand($_[0]) ) + 1;

my $id = &RootNode(0, $parent);

my $insert = qq|INSERT INTO test.closure (ancestor, descendant, depth) 
        SELECT ancestor, $id, depth + 1 
        FROM test.closure WHERE descendant = ?|;
$dbh->do($insert, undef, $parent);
return $id;

}
##


##
## 
sub RootNode {
my $min_datetime = $_[0] 
        ? '2008-9-21 4:0:0' 
        :  $dbh->selectrow_array( "SELECT time 
                FROM test.nodes WHERE node = ?", undef, $_[1] );
my $label = join( "", rand_chars( set => 'alpha', min => 5, max => 20 ) );
my $time = rand_datetime( min => $min_datetime, max => 'now' );

my $insert = qq|INSERT INTO test.nodes (label, time, root) VALUES (?, ?, ?)|;
$dbh->do($insert, undef, $label, $time, $_[0]);
my ($id) = $dbh->selectrow_array("SELECT LAST_INSERT_ID()");

$insert = qq|INSERT INTO test.closure (ancestor, descendant, depth) 
        VALUES (?, ?, 0)|;
$dbh->do($insert, undef, $id, $id);

return $id;
}
##

__DATA__
USE test

DROP TABLE IF EXISTS `closure`;
DROP TABLE IF EXISTS `nodes`;

CREATE TABLE `nodes` (
`node` int(11) NOT NULL auto_increment,
`label` varchar(20) NOT NULL,
`time` datetime default NULL,
`root` tinyint(1) unsigned default NULL,
PRIMARY KEY  (`node`)
) ENGINE=InnoDB;

CREATE TABLE `closure` (
`ancestor` int(11) NOT NULL,
`descendant` int(11) NOT NULL,
`depth` tinyint(3) unsigned default NULL,
PRIMARY KEY  (`ancestor`,`descendant`),
KEY `descendant` (`descendant`),
CONSTRAINT `closure_ibfk_1` FOREIGN KEY (`ancestor`) REFERENCES `nodes` (`node`),
CONSTRAINT `closure_ibfk_2` FOREIGN KEY (`descendant`) REFERENCES `nodes` (`node`)
) ENGINE=InnoDB;

【讨论】:

    【解决方案3】:

    您可以创建一个顶级元素,仅供参考,其所有后代都将成为根节点。

    • 顶部
        • sub1
        • sub2
        • sub3
      • root2
      • root3
      • root4

    【讨论】:

    • 谢谢,这是个好主意
    【解决方案4】:

    我尝试将其模拟到数据库中,并生成此查询以查找具有最新帖子的最后 3 个根节点。我不确定我是否理解了您的所有请求,但如果我不理解,请告诉我,我会尽快为您提供帮助。

    我的查询如下:


    SELECT TOP 3 QRY_GROUP_ALL_OF_THEM.MínDeancestor, Max(QRY_GROUP_ALL_OF_THEM.descendant) AS MáxDedescendant
    FROM (  SELECT Min(closure.ancestor) AS MínDeancestor, [QRY_LAST_INSERTIONS].[descendant]
        FROM closure,   (SELECT DISTINCT closure.descendant 
            FROM closure 
            GROUP BY closure.descendant, closure.depth, closure.ancestor, closure.descendant 
            HAVING  (((closure.descendant>12 And closure.descendant<>[closure].[ancestor]) AND (closure.depth<>0)) 
                OR ((closure.descendant<>[closure].[ancestor]) AND (closure.depth<>0)))
            ) AS QRY_LAST_INSERTIONS
        GROUP BY closure.descendant, [QRY_LAST_INSERTIONS].[descendant]
        HAVING (((closure.descendant)=[QRY_LAST_INSERTIONS].[descendant]))
    ) AS QRY_GROUP_ALL_OF_THEM
    GROUP BY QRY_GROUP_ALL_OF_THEM.MínDeancestor
    ORDER BY Max(QRY_GROUP_ALL_OF_THEM.descendant) DESC;
    

    如您所见,同一个查询中有三个查询。 如果它对你有用,请告诉我,我明天会解释它是如何工作的。

    最好的问候, 乔迪·马斯

    【讨论】:

    • 感谢 JJNGUY 的编辑:我无法正确格式化查询。 :)
    • 谢谢你,乔迪!我尝试了您的查询,但以错误结尾:...正确的语法在 '3 QRY_GROUP_ALL 附近使用...我将数据转储到 pastebin,也许您可​​以尝试使用 my tables 。要了解您的复杂查询,我需要新鲜的早晨;)
    • 你好@wk 我想问题可能是因为某些别名中的引号......我会更正它,我会尽快发布。
    【解决方案5】:

    在这里,您有相同的代码,但别名中没有引号,请检查它并告诉我它是否适用于您。我在Microsoft SQL Server下尝试过,因为我的笔记本电脑上没有MySQL Server,但是如果它不起作用,告诉我,我会安装并尝试它。

    查询:

    SELECT TOP 3 QRY_GROUP_ALL_OF_THEM.MinAncestor, Max(QRY_GROUP_ALL_OF_THEM.descendant) AS MaxDescendant
    FROM (  
    SELECT Min(closure.ancestor) AS MinAncestor, [QRY_LAST_INSERTIONS].[descendant]     
    FROM closure, (
        SELECT DISTINCT closure.descendant          
        FROM closure          
        GROUP BY closure.descendant, closure.depth, closure.ancestor, closure.descendant          
        HAVING  (((closure.descendant>12 And closure.descendant<>[closure].[ancestor]) 
            AND (closure.depth<>0))              
            OR ((closure.descendant<>[closure].[ancestor]) AND (closure.depth<>0)))         
    ) AS QRY_LAST_INSERTIONS     
    GROUP BY closure.descendant, [QRY_LAST_INSERTIONS].[descendant]     
    HAVING (((closure.descendant)=[QRY_LAST_INSERTIONS].[descendant])) ) AS QRY_GROUP_ALL_OF_THEM 
    GROUP BY QRY_GROUP_ALL_OF_THEM.MinAncestor 
    ORDER BY Max(QRY_GROUP_ALL_OF_THEM.descendant) DESC; 
    

    这个查询的结果,你的数据如下:

    最小祖先:1、10、11

    MaxDescendant:17、14、13

    希望对你有帮助。


    在您对 TOP 语句的评论(它不适用于 MySQL)之后,最终查询必须是这个:

    SELECT 
        QRY_GROUP_ALL_OF_THEM.MinAncestor, 
        Max(QRY_GROUP_ALL_OF_THEM.descendant) AS MaxDescendant LIMIT 0,3
    FROM 
        (  
            SELECT 
                Min(closure.ancestor) AS MinAncestor, 
                [QRY_LAST_INSERTIONS].[descendant]     
            FROM closure, 
                (
                    SELECT DISTINCT closure.descendant 
                    FROM   closure 
                    GROUP  BY closure.descendant, 
                              closure.depth, 
                              closure.ancestor, 
                              closure.descendant 
                    HAVING ( ( ( closure.descendant > 12 
                                 AND closure.descendant <> [closure].[ancestor] ) 
                               AND ( closure.depth <> 0 ) ) 
                              OR ( ( closure.descendant <> [closure].[ancestor] ) 
                                   AND ( closure.depth <> 0 ) ) )        
                ) AS QRY_LAST_INSERTIONS     
            GROUP BY 
                closure.descendant, 
                [QRY_LAST_INSERTIONS].[descendant]     
            HAVING (((closure.descendant)=[QRY_LAST_INSERTIONS].[descendant])) 
        ) AS QRY_GROUP_ALL_OF_THEM 
    GROUP BY QRY_GROUP_ALL_OF_THEM.MinAncestor 
    ORDER BY Max(QRY_GROUP_ALL_OF_THEM.descendant) DESC;
    

    【讨论】:

    • 我会尝试解析您的查询,但 MySQL 不支持 TOP 语句。谢谢!
    • OK...我以前不知道,但是您可以更改 LIMIT 的 TOP 语句...它将是这样的:“SELECT QRY_GROUP_ALL_OF_THEM.MinAncestor, Max(QRY_GROUP_ALL_OF_THEM.后代)AS MaxDescendant LIMIT 0,3 "等...
    • 您好 wk,我认为您在最终答案中使用了我的方法(限制 3),我也认为我的答案是最佳的,而不是您的方法......所以我认为创建自己的方法是不礼貌的使用其他人的答案来回答,而不是评估人们给你的答案......这取决于你,但请确保我不会再帮助你。玩得开心...
    【解决方案6】:
    select x.ancestor
    from nodes n
    join closure c on (c.descendant = n.node)
    join (
    -- all root node
       select ancestor 
       from closure
       group by descendant 
       having count(*) = 1
    ) x ON x.ancestor = c.ancestor
    where c.depth = 1
    order by n.time desc
    limit 3
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-15
      • 1970-01-01
      • 2018-06-22
      • 1970-01-01
      • 1970-01-01
      • 2011-03-21
      • 2013-06-15
      • 1970-01-01
      相关资源
      最近更新 更多