【问题标题】:Run a query with a LIMIT/OFFSET and also get the total number of rows使用 LIMIT/OFFSET 运行查询并获取总行数
【发布时间】:2015-05-07 10:38:40
【问题描述】:

出于分页目的,我需要使用LIMITOFFSET 子句运行查询。但我还需要计算没有LIMITOFFSET 子句的查询将返回的行数。

我想跑步:

SELECT * FROM table WHERE /* whatever */ ORDER BY col1 LIMIT ? OFFSET ?

还有:

SELECT COUNT(*) FROM table WHERE /* whatever */

同时。有没有办法做到这一点,特别是让 Postgres 对其进行优化的方法,使其比单独运行更快?

【问题讨论】:

标签: sql postgresql count pagination limit


【解决方案1】:

是的。使用简单的窗口函数:

SELECT *, count(*) OVER() AS full_count
FROM   tbl
WHERE  /* whatever */
ORDER  BY col1
OFFSET ?
LIMIT  ?

请注意,成本将大大高于没有总数的情况,但通常仍比两个单独的查询便宜。无论哪种方式,Postgres 都必须实际计算所有行数,这会根据符合条件的行数产生成本。详情:

但是as Dani pointed out,当OFFSET 至少与基本查询返回的行数一样多时,不会返回任何行。所以我们也没有得到full_count

如果这不可接受,一个可能的始终返回完整计数的解决方法是使用 CTE 和 OUTER JOIN

WITH cte AS (
   SELECT *
   FROM   tbl
   WHERE  /* whatever */
   )
SELECT *
FROM  (
   TABLE  cte
   ORDER  BY col1
   LIMIT  ?
   OFFSET ?
   ) sub
RIGHT  JOIN (SELECT count(*) FROM cte) c(full_count) ON true;

如果OFFSET 太大,您会得到一行带有full_count 的NULL 值。否则,它会像第一个查询一样附加到每一行。

如果具有所有 NULL 值的行可能是有效结果,您必须检查 offset >= full_count 以消除空行的来源。

这仍然只执行一次基本查询。但它会增加查询的开销,并且仅在少于重复基本查询的次数时才需要付费。

如果支持最终排序顺序的索引可用,则在 CTE 中包含 ORDER BY 可能是值得的(冗余)。

【讨论】:

  • 根据 LIMIT 和条件,我们有要返回的行,但是对于给定的偏移量,它不会返回任何结果。在那种情况下,我们如何才能获得行数?
  • 非常好,谢谢,当您使用分页、数据表时效果很好,只需将其添加到您的 sql 开头并使用它,为总数保存一个额外的查询。
  • @julealgon:请用定义的细节开始一个新问题。如果您愿意,您可以随时链接到此链接以获取上下文,并在此处添加评论以链接回(并引起我的注意)。
  • 对于任何想知道的人;如果您还想限制在视图上完成的 COUNT(*),例如当您有一个巨大的表并且想要防止计算超出某个数字的所有内容时,那么您可以使用: COUNT(*) OVER(ROWS BETWEEN CURRENT ROW AND 1000 FOLLOWING),其中 1000 是计数将停止的数字,无论您的查询(没有 LIMIT)是否会返回更多行
  • @JustinL.:增加的开销应该只对相对便宜的基本查询很重要。此外,Postgres 12 以多种方式提高了 CTE 性能。 (虽然这个 CTE 默认还是MATERIALIZED,被引用了两次。)
【解决方案2】:

虽然Erwin Brandstetter 的回答很有魅力,但它会返回每行的总行数,如下所示:

col1 - col2 - col3 - total
--------------------------
aaaa - aaaa - aaaa - count
bbbb - bbbb - bbbb - count
cccc - cccc - cccc - count

您可能需要考虑使用返回总计数一次的方法,如下所示:

total - rows
------------
count - [{col1: 'aaaa'},{col2: 'aaaa'},{col3: 'aaaa'}
         {col1: 'bbbb'},{col2: 'bbbb'},{col3: 'bbbb'}
         {col1: 'cccc'},{col2: 'cccc'},{col3: 'cccc'}]

SQL 查询:

SELECT
    (SELECT COUNT(*) 
     FROM table
     WHERE /* sth */
    ) as count, 
    (SELECT json_agg(t.*) FROM (
        SELECT * FROM table
        WHERE /* sth */
        ORDER BY col1
        OFFSET ?
        LIMIT ?
    ) AS t) AS rows 

【讨论】:

  • 你还需要WHERE count(*) 子查询,否则你只会得到整个表数,不是吗?
  • @BenNeill 你是对的,我编辑了答案以包含你的修复。
【解决方案3】:

编辑:此答案在检索未过滤表时有效。我会让它以防万一它可以帮助某人,但它可能不能完全回答最初的问题。

如果您需要准确的值,Erwin Brandstetter 的答案是完美的。但是,在大表上,您通常只需要一个很好的近似值。 Postgres gives you just that 会更快,因为它不需要评估每一行:

SELECT *
FROM (
    SELECT *
    FROM tbl
    WHERE /* something */
    ORDER BY /* something */
    OFFSET ?
    LIMIT ?
    ) data
RIGHT JOIN (SELECT reltuples FROM pg_class WHERE relname = 'tbl') pg_count(total_count) ON true;

我实际上不太确定将RIGHT JOIN 外部化或将其作为标准查询是否有优势。它值得进行一些测试。

SELECT t.*, pgc.reltuples AS total_count
FROM tbl as t
RIGHT JOIN pg_class pgc ON pgc.relname = 'tbl'
WHERE /* something */
ORDER BY /* something */
OFFSET ?
LIMIT ?

【讨论】:

  • 关于快速计数估计:stackoverflow.com/a/7945274/939860 就像您说的:检索整个表时有效 - 这与查询中的 WHERE 子句相矛盾。第二个查询在逻辑上是错误的(为数据库中的 每个 表检索一行) - 修复后成本更高。
【解决方案4】:

没有。

理论上,在引擎盖下使用足够复杂的机器单独运行它们时,您可能会获得一些小的收益。但是,如果您想知道有多少行符合某个条件,则必须计算它们,而不仅仅是有限的子集。

【讨论】:

    猜你喜欢
    • 2011-08-21
    • 1970-01-01
    • 1970-01-01
    • 2011-03-20
    • 2020-01-20
    • 1970-01-01
    • 2012-10-04
    • 2012-08-25
    • 1970-01-01
    相关资源
    最近更新 更多