选择指定行之前/之后的 N 行

Jos*_*osh 5 postgresql

假设我在 Postgres (11.3) 中有一个简单的表:

create table posts
(
    id serial not null,
    created_at timestamp(0)
    constraint posts_pkey
        primary key (id)
);
Run Code Online (Sandbox Code Playgroud)

如果用户请求 id=5869,我需要能够在按列排序的查询中返回该行之前的 N 行和之后的 N 行created_at。如果我们能够假设 越大id,则 越大created_at,我们可以做一些相对简单的事情,如下所示:

(select * from posts where id < 5869 order by id limit 10)
union all
(select * from posts where id >= 5869 order by id limit 11);
Run Code Online (Sandbox Code Playgroud)

但是,我无法假设更高的 id 是最近创建的,我想知道在这种情况下检索该数据的最佳方法是什么。此方法有效,但在 100k 行数据集上速度非常慢:

WITH 
  boundaries AS (
    SELECT *,
           row_number() OVER (ORDER BY created_at DESC) AS rownum
    FROM posts
  ),
  target_boundary AS (
     SELECT *
     FROM boundaries
     WHERE boundaries.id = 5869
 )
SELECT posts.*, boundaries.rownum
FROM posts
LEFT JOIN boundaries ON posts.id = boundaries.id
JOIN target_boundary ON boundaries.rownum BETWEEN target_boundary.rownum - 10 AND target_boundary.rownum + 10
Run Code Online (Sandbox Code Playgroud)

运行整个过程需要 800 毫秒以上,这对于如此小的数据集来说太慢了。

lead()我还尝试了使用and的上述变体lag(),但效率更低。

有没有更好的方法来执行此查询?我在 Postgres 中是否缺少一个可以处理它的窗口函数?

Lau*_*lbe 3

使用以下力量UNION ALL

WITH init AS (
   SELECT created_at
   FROM posts 
   WHERE id = 5869
)
(
   (SELECT posts.*
    FROM posts
       CROSS JOIN init
    WHERE posts.created_at <= init.created_at 
    ORDER BY posts.created_at DESC 
    LIMIT 11) 
 UNION ALL 
   (SELECT posts.* 
    FROM posts 
       CROSS JOIN init 
    WHERE posts.created_at > init.created_at 
    ORDER BY posts.created_at 
    LIMIT 10)
);
Run Code Online (Sandbox Code Playgroud)

此查询假设 中没有重复项created_at

为了获得良好的性能,您需要索引id(带有主键)和created_at

如果您需要对结果进行排序,请使用我的查询作为子选择并添加一个ORDER BY.