使用LIMIT/OFFSET运行查询,并获取总行数

Tim*_*Tim 56 sql postgresql pagination count limit

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

我想跑:

SELECT * FROM table WHERE /* whatever */ ORDER BY col1 LIMIT ? OFFSET ?
Run Code Online (Sandbox Code Playgroud)

和:

SELECT COUNT(*) FROM table WHERE /* whatever */
Run Code Online (Sandbox Code Playgroud)

同时.有没有办法做到这一点,特别是让Postgres优化它的方式,这样它比单独运行更快?

Erw*_*ter 106

是.具有简单的窗口功能:

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

请注意,成本将远高于没有总数的成本,但仍然比两个单独的查询便宜.在任何一种情况下,Postgres都必须实际计算所有行,这会产生一定的成本,具体取决于合格行的总数.细节:

但是,正如Dani指出的那样,当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;
Run Code Online (Sandbox Code Playgroud)

你得到一行NULL值,full_count附加的if OFFSET太大了.或者它像第一个查询中一样附加到每一行.

如果具有所有NULL值的行是可能的有效结果,则必须检查offset >= full_count以消除空行的原点的歧义.

这仍然只执行一次基本查询.但它增加了查询的开销,只有在重复计数的基本查询时才会付费.

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

  • 对于任何想知道的人;如果您还想限制对视图执行的 COUNT(\*) 操作,例如当您有一个巨大的表并希望防止对超出特定数字的所有内容进行计数时,那么您可以使用: COUNT(\*) OVER(ROWS BETWEEN CURRENT ROW AND 1000 FOLLOWING) 其中 1000 是计数将停止的数字,无论您的查询(没有 LIMIT)是否会返回更多行 (4认同)
  • 通过LIMIT和条件,我们有返回的行,但是使用给定的偏移量将返回没有结果.在那种情况下,我们如何才能获得行数? (2认同)
  • @DaniMathew:好点.我添加了另一种选择. (2认同)

tre*_*con 18

虽然Erwin Brandstetter的答案非常有效,但它返回每行的总行数,如下所示:

col1 - col2 - col3 - total
--------------------------
aaaa - aaaa - aaaa - count
bbbb - bbbb - bbbb - count
cccc - cccc - cccc - count
Run Code Online (Sandbox Code Playgroud)

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

total - rows
------------
count - [{col1: 'aaaa'},{col2: 'aaaa'},{col3: 'aaaa'}
         {col1: 'bbbb'},{col2: 'bbbb'},{col3: 'bbbb'}
         {col1: 'cccc'},{col2: 'cccc'},{col3: 'cccc'}]
Run Code Online (Sandbox Code Playgroud)

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 
Run Code Online (Sandbox Code Playgroud)

  • 您还需要“WHERE”“count(*)”子查询,否则您将只获得整个表的计数,不是吗? (4认同)
  • @BenNeill,你是对的,我编辑了答案以包含你的修复。 (2认同)

Fra*_*uen 6

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

如果您需要准确的值, Erwin Brandstetter的答案是完美的。然而,在大表上,您通常只需要一个相当好的近似值。Postgres为您提供了这一点,并且速度会快得多,因为它不需要评估每一行:

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;
Run Code Online (Sandbox Code Playgroud)

我实际上不太确定将其外部化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 ?
Run Code Online (Sandbox Code Playgroud)

  • 关于快速计数估计:/sf/answers/556169211/ 就像你说的:检索整个表时有效 - 这与查询中的“WHERE”子句相矛盾。第二个查询在逻辑上是错误的(为数据库中的*每个*表检索一行) - 并且修复后更昂贵。 (6认同)