出于分页目的,我需要使用
LIMIT
和 OFFSET
子句运行查询。但我还需要计算该查询在没有 LIMIT
和 OFFSET
子句的情况下返回的行数。
我想跑步:
SELECT * FROM table WHERE /* whatever */ ORDER BY col1 LIMIT ? OFFSET ?
并且:
SELECT COUNT(*) FROM table WHERE /* whatever */
同时。有没有办法做到这一点,特别是让 Postgres 优化它的方法,这样它比单独运行两者更快?
是的。 有一个简单的窗口函数。
SELECT *, count(*) OVER() AS full_count
FROM tbl
WHERE /* whatever */
ORDER BY col1
OFFSET ?
LIMIT ?
请注意,成本将远高于没有总数的情况。无论哪种方式,Postgres 都必须实际计算所有行,这会根据符合条件的行总数产生一定的成本。参见:
两个单独的查询(一个用于结果集,一个用于总计数)可能会更快,也可能不会更快。但是执行两个单独的查询和处理结果的开销通常会造成巨大的损失。取决于查询、索引、资源、基数的性质...
但是,[正如 Dani 指出的][1],当
OFFSET
至少与从基本查询返回的行数一样大时,不会返回任何行。所以我们也没有得到full_count
。如果这种情况很少见,只需运行第二个查询来获取这种情况下的计数。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 ?
);
虽然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
的答案是完美的。然而,在大表上,您通常只需要一个相当好的近似值。 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 ?
理论上,与在引擎盖下使用足够复杂的机器单独运行它们相比,您可能会获得一些小的收益。但是,如果您想知道有多少行匹配某个条件,您必须对它们进行计数,而不仅仅是有限的子集。