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


90

出于分页目的,我需要使用LIMITOFFSET子句运行查询。但是我还需要计算不带LIMITandOFFSET子句的查询将返回的行数。

我要跑步:

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

和:

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

同时。有没有办法做到这一点,特别是让Postgres优化它的方法,从而使其比单独运行它们都快?


Answers:


168

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

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

请注意,成本将比没有总数大得多,但通常仍比两个单独的查询便宜。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;

您将获得一行NULL值,并在full_count后面加上ifOFFSET太大,。否则,它会像第一个查询一样附加到每一行。

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

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

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


3
通过LIMIT和条件,我们都有要返回的行,但是具有给定的偏移量将不返回任何结果。在这种情况下,我们将如何获得行数?
Dani Mathew

非常好,谢谢,使用pagination,datatables时效果很好,只需将其添加到sql的开头,然后使用,就可以节省额外的查询总数。
艾哈迈德·桑尼

您能否详细说明一下是否可以通过输入参数在查询中动态启用计数?我有一个类似的要求,但用户可以决定是否需要内联计数。
julealgon

1
@julealgon:请使用定义详细信息开始一个新问题。您随时可以链接到此上下文,并在此处添加评论以链接回(并引起我的注意)。
Erwin Brandstetter

1
@JustinL .:仅对于相对便宜的基本查询而言,增加的开销才有意义。此外,Postgres 12还通过多种方式提高了CTE性能。(尽管MATERIALIZED默认情况下仍会引用此CTE ,但会被引用两次。)
Erwin Brandstetter

1

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

如果您需要准确的价值,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 ?

2
关于快速计数估计:stackoverflow.com/a/7945274/939860如您所说:检索整个表时有效-这与WHERE查询中的子句相矛盾。第二个查询在逻辑上是错误的(为数据库中的每个表检索一行),并且在修复时更昂贵。
欧文·布兰德斯特

-7

不好的做法是对Just调用两次相同的查询以获取回退结果的总行数。这将花费执行时间,并浪费服务器资源。

更好的是,您可以SQL_CALC_FOUND_ROWS在查询中使用,这将告诉MySQL获取行数的总数以及限制查询结果。

示例设置为:

SELECT SQL_CALC_FOUND_ROWS employeeName, phoneNumber FROM employee WHERE employeeName LIKE 'a%' LIMIT 10;

SELECT FOUND_ROWS();

在上面的查询中,只需SQL_CALC_FOUND_ROWS在其余必需的查询中添加选项,然后执行第二行,即SELECT FOUND_ROWS()返回该语句返回的结果集中的行数。


1
该解决方案需要postgres,而不是mysql。
MuffinMan

@MuffinMan,您可以在mysql上使用相同的名称。从MYSQL 4.0开始,在查询中使用SQL_CALC_FOUND_ROWS选项。但是从MYSQL 8.0起已弃用。
Mohd Rashid

不相关。几年前回答了这个问题。如果您想做出贡献,请发布一个主题相同但针对MySQL的新问题。
MuffinMan

总是很重要
阿里·侯赛因

-14

没有。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.