PostgreSQL有效地找到线性列表中的最后一个后代

时间:2017-03-06 22:01:51

标签: sql postgresql recursive-query

我目前尝试从结构链接列表中有效地检索最后一个decendet。

基本上有一个包含数据系列的表格,我将其拆分以获得这样的列表

current_id | NEXT_ID

例如

1  | 2
2  | 3
3  | 4
4  | NULL
42 | 43
43 | 45
45 | NULL
etc...

会产生像

这样的列表

1 - > 2 - > 3 - > 4

42 - > 43 - > 45

现在我想从每个列表中获取第一个和最后一个id。

这就是我现在所拥有的:

WITH RECURSIVE contract(ruid, rdid, rstart_ts, rend_ts) AS ( -- recursive Query to traverse the "linked list" of continuous timestamps
    SELECT start_ts, end_ts FROM track_caps tc
    UNION
    SELECT c.rstart_ts, tc.end_ts AS end_ts0 FROM contract c INNER JOIN track_caps tc ON (tc.start_ts = c.rend_ts AND c.rend_ts IS NOT NULL AND tc.end_ts IS NOT NULL)
),
fcontract AS ( --final step, after traversing the "linked list", pick the largest timestamp found as the end_ts and the smallest as the start_ts
    SELECT DISTINCT ON(start_ts, end_ts) min(rstart_ts) AS start_ts, rend_ts AS end_ts
    FROM (
        SELECT rstart_ts, max(rend_ts) AS rend_ts FROM contract
        GROUP BY rstart_ts
    ) sq
    GROUP BY end_ts
)
SELECT * FROM fcontract
ORDER BY start_ts

在这种情况下,我只使用了对给定数据有效的时间戳。

基本上我只是使用遍历所有节点的递归查询,直到它到达结尾,正如StackOverflow和其他站点上的许多其他帖子所建议的那样。下一个查询将删除所有子步骤并返回我想要的内容,如第一个列表示例中所示:1 | 4

仅举例说明,递归查询生成的结果集如下所示:

1  | 2
2  | 3
3  | 4
1  | 3
2  | 4
1  | 4

尽管它很有效,但这对记忆很重要,但在查看EXPLAIN ANALYZE的结果时绝对不足为奇。 对于大约42,600行的数据集,递归查询产生高达849,542,346行。现在它实际上应该处理大约2,000,000行,但现在使用该解决方案似乎非常不可行。

我是否只是不正确地使用递归查询?有没有办法减少它产生的数据量?(比如删除子步骤?) 或者是否有更好的单一查询解决方案来解决这个问题?

1 个答案:

答案 0 :(得分:2)

主要问题是您的递归查询没有正确过滤由您拥有的模型引起的根节点。因此,非递归部分已经选择整个表,然后Postgres需要递归表的每一行。

为了提高效率,只需在查询的非递归部分中选择根节点。这可以使用:

完成
select t1.current_id, t1.next_id, t1.current_id as root_id
from track_caps t1
where not exists (select * 
                  from track_caps t2
                  where t2.next_id = t1.current_id)

现在这仍然不是非常有效(与#34;通常" where parent_id is null设计相比),但至少确保递归不需要处理更多行然后必要。

要查找每个树的根节点,只需将其选为查询的非递归部分中的额外列,并将其传递到递归部分中的每一行。

所以你结束了这样的事情:

with recursive contract as (
  select t1.current_id, t1.next_id, t1.current_id as root_id
  from track_caps t1
  where not exists (select * 
                    from track_caps t2
                    where t2.next_id = t1.current_id)
  union 
  select c.current_id, c.next_id, p.root_id
  from track_caps c
    join contract p on c.current_id = p.next_id
  and c.next_id is not null
)
select *
from contract
order by current_id;

在线示例:http://rextester.com/DOABC98823