【问题标题】:Global row numbers in chunked query分块查询中的全局行号
【发布时间】:2016-08-26 20:18:57
【问题描述】:

我想在我的结果集中包含一个带有行号序列的列 row_number,其中 1 是最新的项目,没有间隙。这有效:

SELECT id, row_number() over (ORDER BY id desc) AS row_number, title
FROM mytable
WHERE group_id = 10;

现在我想以 1000 个块为单位查询相同的数据,以便更容易记忆:

SELECT id, row_number() over (ORDER BY id desc) AS row_number, title
FROM mytable
WHERE group_id = 10 AND id >= 0 AND id < 1000
ORDER BY id ASC;

这里的 row_number 对于每个块都从 1 重新开始,但我希望它就像是全局查询的一部分,就像在第一种情况下一样。有没有简单的方法可以做到这一点?

【问题讨论】:

    标签: sql postgresql pagination row-number postgresql-9.4


    【解决方案1】:

    假设:

    • id 定义为PRIMARY KEY - 这意味着UNIQUENOT NULL。否则,您可能必须处理 NULL 值和/或重复项(关系)。

    • 您没有对表的并发写访问权限 - 或者您不在乎拍摄快照后会发生什么。

    MATERIALIZED VIEW,就像你演示的in your answer,是一个不错的选择。

    CREATE MATERIALIZED VIEW mv_temp AS
    SELECT row_number() OVER (ORDER BY id DESC) AS rn, id, title
    FROM   mytable
    WHERE  group_id = 10;
    

    但索引和后续查询必须在行号rn上才能获取

    1000 个数据块

    CREATE INDEX ON mv_temp (rn);
    
    SELECT * FROM mv_temp WHERE rn BETWEEN 1000 AND 2000;

    您的实现将需要保证无间隙的id 列 - 这将不需要添加以...开头的行号

    完成后:

    DROP MATERIALIZED VIEW mv_temp;
    

    索引与表(在本例中为物化视图)一起自动消失。

    相关,有更多细节:

    【讨论】:

    • 哈哈我本来打算接受我自己的答案,但你改写得更仔细,更详细,有趣的情况。对于我的用例,索引/查询是在 id 还是 rn 上完成并不重要,因为我使用它来批量导出数据。我会接受你的答案,所以它上升得更快,因为我认为其他答案不适合大桌子。
    【解决方案2】:

    您想查询前 1000 行,然后查询接下来的 1000 行,以此类推?

    通常您只需编写一个查询(您已经使用的查询),让您的应用获取 1000 条记录,对它们执行一些操作,然后获取下一个 1000 条,依此类推。因此,不需要单独的查询。

    但是,编写这样的部分查询会相当容易:

    select *
    from
    (
      SELECT id, row_number() over (ORDER BY id desc) AS rn, title
      FROM mytable
      WHERE group_id = 10
    ) numbered
    where rn between 1 and 1000; -- <- simply change the row number range here
                                 --    e.g. where rn between 1001 and 2000 for the second chunk
    

    【讨论】:

    • 我理解您所建议的数据库端游标的概念,但不幸的是有时无法使用它们。您的答案有效,但在大表上非常慢,因为最终的“x 和 y 之间”过滤器未编入索引:我发现 pgsql 对完整子查询进行顺序过滤以获得最终分页,最多需要 30我桌子上的秒数:(
    • 无论您如何编写查询,DBMS 都必须对整个表进行排序,以便知道例如第 5001 到 6000 行是什么,然后移动到该块。我没有看到真正的替代方案。 (Madhivanan 建议使用 LIMITOFFSET 不是一个坏主意,但结果应该差不多;在子查询中对记录进行排序,移动到块,然后将 row_numbers 赋予这些行。)
    • OFFSET 不适合对数百万行进行分页。
    【解决方案3】:

    你需要一个分页。试试这个

    SELECT id, row_number() over (ORDER BY id desc)+0 AS row_number, title
    FROM mytable
    WHERE group_id = 10 AND id >= 0 AND id < 1000
    ORDER BY id ASC;
    

    下次,当您在 WHERE 子句中更改 id 的起始值时,请在 row_number() 中更改它,如下所示

    SELECT id, row_number() over (ORDER BY id desc)+1000 AS row_number, title
    FROM mytable
    WHERE group_id = 10 AND id >= 1000 AND id < 2000
    ORDER BY id ASC;
    

    或者更好的是,您可以使用 OFFSET 和 LIMIT 方法进行分页 https://wiki.postgresql.org/images/3/35/Pagination_Done_the_PostgreSQL_Way.pdf

    【讨论】:

    • 但是,这样做+1000,您假设每个块/页面恰好有 1000 个结果,这可能不是这种情况,由于间隙,或者可能某些行不满足 group_id=10,对?
    • 那看看我贴的链接
    【解决方案4】:

    最后我是这样做的:

    首先我创建一个临时物化视图:

    CREATE MATERIALIZED VIEW vw_temp AS SELECT id, row_number() over (ORDER BY id desc) AS rn, title
    FROM mytable
    WHERE group_id = 10;
    

    然后我定义索引:

    CREATE INDEX idx_temp ON vw_temp USING btree(id);
    

    现在我可以非常快速地执行所有操作,并且带有编号的行:

    SELECT * FROM vw_temp WHERE id BETWEEN 1000 AND 2000;
    

    完成操作后,清理:

    DROP INDEX idx_temp;
    DROP MATERIALIZED VIEW vw_temp;
    

    尽管 Thorsten Kettner 的答案似乎是最干净的,但由于太慢,对我来说并不实用。感谢大家的贡献。对于那些对实际用例感兴趣的人,我使用它来向 Sphinx 索引器提供数据。

    【讨论】:

      猜你喜欢
      • 2016-10-03
      • 1970-01-01
      • 1970-01-01
      • 2015-12-12
      • 1970-01-01
      • 2016-09-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多