关闭表的预订(深度优先)遍历

时间:2013-04-23 16:00:24

标签: sql postgresql

我有一个评论树及其closure table

create table comment (
    id serial primary key,
    author varchar(100) not null,
    content text not null
);

create table closure (
    ancestor integer not null references comment (id),
    descendant integer not null references comment (id) on delete cascade,
    depth integer not null,
    primary key (ancestor, descendant)
);

我希望在ID为4的评论下获取所有评论的子树。对评论主题进行广度优先遍历并不太难:

select comment.*, closure.depth
from comment
inner join closure on closure.descendant = comment.id
where ancestor = 4
order by depth asc;

如何对评论主题进行预订(深度优先)遍历?

(我意识到使用嵌套集进行预订遍历很容易,但我特别好奇如何使用闭包表。)

2 个答案:

答案 0 :(得分:1)

首先,请不要先考虑广度或深度问题。理想情况下,您将把它看作一系列构建结果集的set操作。执行此操作的SQLy方法是执行类似于广度优先的操作,但一次只能在一个深度级别上操作。如果您尝试使用全宽度优先或全深度优先方法,则会遇到性能问题。

最佳方式

WITH RECURSIVE closure_tree AS (
     SELECT descendent as id, ancestor as parent_id, 0 as depth, descendent::text as path
       FROM closure
      WHERE ancestor = 4
  UNION ALL 
     SELECT c.descendent, ct.id, ct.depth + 1, ct.path || ',' || descendent::text
       FROM closure c
       JOIN closure_tree ct ON ct.id = c.ancestor
)
SELECT c.*, ct.depth, string_to_array(ct.path, ',')::int[]
  FROM comment c
  JOIN closure_tree ct ON c.id = ct.id
 ORDER BY string_to_array(ct.path, ',')::int[];

未经测试,但这会给你一个想法。基本上,它会根据深度级别扫描表格(索引或顺序,具体取决于注释数量),在每次扫描时检索完整的图形宽度。请记住,SQL擅长管理集合,因此这是唯一能够做到这一点的理智方式。当然,这意味着闭包表应该只存储直接​​的父/子关系。

请注意,这会以深度优先的方式返回结果,而查询会首先以宽度返回结果。为了做到这一点,你需要以某种方式存储或生成路径,否则你无法获得真正的深度信息。所以你可以以非规范化的方式为你的闭包表添加路径,或者你可以抓住父/子关系并在查询中生成这个,如我的例子所示。

答案 1 :(得分:1)

我已经在我的ruby gem closure_tree中实现了预先排序的遍历,并且gem生成的SQL可以与MySQL和PostgreSQL一起使用。我相信我的实现是新颖的 - 但它非常多毛,需要两个选择,并且要求排序列为数字和[0-N],其中N是兄弟姐妹的数量,并且顺序没有间隙。

https://github.com/mceachen/closure_tree/blob/master/lib/closure_tree/numeric_deterministic_ordering.rb#L48