为了分页,我需要使用LIMIT和OFFSET子句运行查询。但是我还需要该查询在没有LIMIT和OFFSET子句的情况下返回的行数的计数。
我想跑步:
SELECT * FROM table WHERE /* whatever */ ORDER BY col1 LIMIT ? OFFSET ?
And:
SELECT COUNT(*) FROM table WHERE /* whatever */
同时。有没有办法做到这一点,特别是让Postgres优化它的方法,使它比单独运行两个更快?
编辑:这个答案在检索未过滤的表时有效。我会让它来帮助别人,但它可能不能完全回答最初的问题。
如果你需要一个准确的值,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 ?
编辑:这个答案在检索未过滤的表时有效。我会让它来帮助别人,但它可能不能完全回答最初的问题。
如果你需要一个准确的值,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 ?
虽然Erwin Brandstetter的答案很有魅力,但它返回了每行中的行数,如下所示:
col1 - col2 - col3 - total
--------------------------
aaaa - aaaa - aaaa - count
bbbb - bbbb - bbbb - count
cccc - cccc - cccc - count
你可以考虑使用只返回一次total 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
是的。用一个简单的窗口函数:
SELECT *, count(*) OVER() AS full_count
FROM tbl
WHERE /* whatever */
ORDER BY col1
OFFSET ?
LIMIT ?
注意,这样做的成本将大大高于没有总数的情况,但通常仍然比两个单独的查询便宜。Postgres实际上必须以任何一种方式计算所有行,这将根据符合条件的行总数施加成本。细节:
在应用LIMIT之前获得结果计数的最佳方法
但是,正如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;
如果OFFSET太大,就会得到一行NULL值,并附加full_count。否则,它像第一个查询一样被追加到每一行。
如果全为NULL值的行是一个可能的有效结果,则必须检查偏移量>= full_count以消除空行的原点的歧义。
这仍然只执行一次基本查询。但是它会给查询增加更多的开销,并且只有在开销小于为计数重复基本查询时才会付费。
如果支持最终排序顺序的索引可用,则可能需要在CTE中包含order BY(冗余)。