PostgreSQL 递归 select 从叶中查找根元素

PostgreSQL recursive select find root element from leaf

我正在为一个论坛开发一个数据库,其中包含线程和消息。线程以没有 parent_id 的消息开始;回复是带有 parent_id.

的消息

我有一个 table 的消息。每个项目都引用相同 table 上的项目,将它们关联为父子关系。

create table messages(
  id int,
  title text,
  content text,
  parent_id int
);

现在我用一些数据填充table:

insert into messages values 
(1, 'One', 'First  thread main post', null), -- First thread
(2, 'Two', 'First thread reply', 1), 
(3, 'Three', 'First thread reply', 2),
(4, 'Four', 'Second thread main post', null), -- Second thread
(5, 'Five', 'Second thread reply', 4),
(6, 'Six', 'Second thread reply', 5),
(7, 'Six', 'Second thread reply', 6),
(8, 'Six', 'Second thread reply', 7),
(9, 'Six', 'Second thread reply', 8); 

知道线程第一条消息的id,我可以用递归查询检索所有回复:

with recursive my_tree as (
    select * from messages 
    where parent_id IS null and id = 4
  union all
    select messages.* from messages 
    join my_tree on messages.parent_id = my_tree.id
) select my_tree.* 
    from my_tree;

这里是fiddle.

现在,假设我知道某些回复的 id,并且我想获取线程第一条消息的 id。我该怎么做?

编辑: 我知道我可以从已知的 id 向上检索线程的所有消息:

with recursive my_tree as (
    select * from messages 
    where id = 9
  union all
    select messages.* from messages 
    join my_tree on messages.id = my_tree.parent_id
) select my_tree.* 
    from my_tree;

这里有一个fiddle.但是如果线程由几千条消息组成效率会很低。有没有什么方法可以让递归查询到达第一项而不检索所有消息?

以目前的数据结构,除了访问从叶子到根的整个路径,别无他法。您可以通过仅对关键列进行操作来加快搜索速度:

with recursive my_tree as (
    select id, parent_id
    from messages 
    where id = 9
union all
    select m.id, m.parent_id 
    from messages m
    join my_tree t on m.id = t.parent_id
) 
select m.*
from messages m
join my_tree t using(id)
where t.parent_id is null

db<>fiddle.

如果这个方案不理想,可以考虑在table

中增加一列
alter table messages add root_id int;

用递归查询填充此列将很容易,您将可以立即访问每个节点的根。

另一种方法是使用 ltree extension.

更改数据结构

您的查询检索的行数已达到最低限度。找到原始父级的唯一方法是检索父级,然后检索该父级,...并坚持下去,直到找到最终父级。

关于递归查询的递归部分,您可能不知道的一件事是,每个步骤仅适用于前一步的结果,而不适用于迄今为止累积的 UNION 结果。这意味着通过查询示例的每个循环只检索一行,即它的直接父级。很难看出如何使它更有效率;六个查询,每个查询 return 一行。

现在,听起来您对保留中间行不感兴趣,您只想要最终父项的 ID。如果您 post-process 使用第二个 CTE 的递归查询结果集,您可以 return 仅那一条记录。我像这样添加到您的原始查询中:

with recursive my_tree as (
    select *,1 as depth from messages 
    where parent_id IS null and id = 4
 union all
    select messages.*, my_tree.depth+1 
    from messages 
    join my_tree on messages.parent_id = my_tree.id
), my_root as (select * from my_tree
     where depth=(select max(depth)
                  from my_tree)
   )
select * 
    from my_root;

此添加仅 return 递归查询的最后一行。