运行带有 LIMIT/OFFSET 的查询并获取总行数

问题描述 投票:0回答:4

出于分页目的,我需要使用

LIMIT
OFFSET
子句运行查询。但我还需要计算该查询在没有
LIMIT
OFFSET
子句的情况下返回的行数。

我想跑步:

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

并且:

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

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

sql postgresql count pagination limit
4个回答
344
投票

是的。 有一个简单的窗口函数。

添加包含总计数的列

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

请注意,成本将远高于没有总数的情况。无论哪种方式,Postgres 都必须实际计算所有行,这会根据符合条件的行总数产生一定的成本。参见:

两个单独的查询(一个用于结果集,一个用于总计数)可能会更快,也可能不会更快。但是执行两个单独的查询和处理结果的开销通常会造成巨大的损失。取决于查询、索引、资源、基数的性质...

但是,[正如 Dani 指出的][1],当

OFFSET
至少与从基本查询返回的行数一样大时,不会返回任何行。所以我们也没有得到
full_count
。如果这种情况很少见,只需运行第二个查询来获取这种情况下的计数。
如果这不可接受,这里有一个单个查询,始终返回完整计数,带有 CTE 和
OUTER JOIN
。这会增加更多的开销,并且只对某些情况有意义(昂贵的过滤器,很少的合格行)。

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

① 通常在 CTE 中添加(相同)

ORDER BY
是没有代价的。这会强制对所有行进行排序。使用
LIMIT
,通常只需对一小部分进行排序(使用“top-N 堆排序”)。

您会得到一行空值,如果

full_count
太大,则会附加
OFFSET
。否则,它会像第一个查询一样附加到每一行。

如果全部为空值的行可能是有效结果,则必须检查

offset >= full_count
以消除空行来源的歧义。

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

无论哪种方式,每行都会返回总计数(冗余)。并不会增加太多成本。但如果这是一个问题,你可以......

添加包含总计数的行

添加的行必须与查询结果的行类型匹配,并且计数必须适合其中一列的数据类型。有点黑客行为。喜欢:

WITH cte AS (
   SELECT col1, col2, int_col3
   FROM   tbl
   WHERE  /* whatever */
   )
SELECT null AS col1, null AS col2, count(*)::int AS int_col3  -- maybe cast the count
FROM   cte
UNION ALL
(  -- parentheses required
TABLE  cte
ORDER  BY col1
LIMIT  ?
OFFSET ?
);

再次强调,有时仅运行单独的计数可能会更便宜(仍在单个查询中!):

SELECT null AS col1, null AS col2, count(*)::int AS int_col3
FROM   tbl
WHERE  /* whatever */
UNION ALL
(  -- parentheses required
SELECT col1, col2, int_col3
FROM   tbl
WHERE  /* whatever */
ORDER  BY col1
LIMIT  ?
OFFSET ?
);

18
投票

虽然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



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;

我实际上很不确定将 
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 ?



-22
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.