ChatGPT解决这个技术问题 Extra ChatGPT

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

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

我想跑:

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

和:

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

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

这回答了你的问题了吗? Best way to get result count before LIMIT was applied

E
Erwin Brandstetter

是的。用一个简单的窗口函数:

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

请注意,成本将大大高于没有总数的情况,但通常仍比两个单独的查询便宜。 Postgres 必须以任何一种方式实际计算所有行,这取决于符合条件的行的总数。细节:

在应用 LIMIT 之前获取结果计数的最佳方法

但是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 以消除空行的来源的歧义。

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

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


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

虽然 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 你是对的,我编辑了答案以包括你的修复。
F
François Gueguen

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

如果您需要准确的值,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 子句相矛盾。第二个查询在逻辑上是错误的(为数据库中的 every 表检索一行) - 修复后成本更高。
R
Richard Huxton

不。

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