Führen Sie eine Abfrage mit einem LIMIT / OFFSET aus und ermitteln Sie die Gesamtzahl der Zeilen

97

Für Paginierungszwecke muss eine Abfrage mit den Klauseln LIMITund ausgeführt werden OFFSET. Ich benötige aber auch die Anzahl der Zeilen, die von dieser Abfrage ohne die Klauseln LIMITund zurückgegeben werden OFFSET.

Ich möchte rennen:

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

Und:

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

Gleichzeitig. Gibt es eine Möglichkeit, dies zu tun, insbesondere eine Möglichkeit, mit der Postgres es optimieren kann, sodass es schneller ist, als beide einzeln auszuführen?

Tim
quelle

Antworten:

174

Ja. Mit einer einfachen Fensterfunktion:

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

Beachten Sie, dass die Kosten wesentlich höher sind als ohne die Gesamtzahl, aber in der Regel immer noch günstiger als zwei separate Abfragen. Postgres muss tatsächlich alle Zeilen in beide Richtungen zählen, was abhängig von der Gesamtzahl der qualifizierenden Zeilen Kosten verursacht. Einzelheiten:

Jedoch , wie Dani wies darauf hin , wenn OFFSETmindestens so groß wie die Anzahl der Zeilen aus der Basis Abfrage zurückgegeben, werden keine Zeilen zurückgegeben. Also bekommen wir auch nicht full_count.

Wenn dies nicht akzeptabel ist, besteht eine mögliche Problemumgehung, um immer die volle Anzahl zurückzugeben , in einem CTE und einem 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;

Sie erhalten eine Zeile mit NULL-Werten mit dem full_countangehängten Wert, wenn dieser OFFSETzu groß ist. Andernfalls wird es wie in der ersten Abfrage an jede Zeile angehängt.

Wenn eine Zeile mit allen NULL-Werten ein mögliches gültiges Ergebnis ist, müssen Sie überprüfen offset >= full_count, ob der Ursprung der leeren Zeile eindeutig ist.

Dadurch wird die Basisabfrage immer noch nur einmal ausgeführt. Es erhöht jedoch den Overhead der Abfrage und zahlt sich nur aus, wenn dies weniger ist als das Wiederholen der Basisabfrage für die Zählung.

Wenn Indizes verfügbar sind, die die endgültige Sortierreihenfolge unterstützen, kann es sich lohnen, die ORDER BYin den CTE aufzunehmen (redundant).

Erwin Brandstetter
quelle
3
Sowohl unter LIMIT als auch unter Bedingungen müssen Zeilen zurückgegeben werden, aber mit dem angegebenen Offset würde kein Ergebnis zurückgegeben. Wie könnten wir in dieser Situation die Zeilenanzahl ermitteln?
Dani Mathew
Sehr schön, danke, funktioniert hervorragend, wenn Sie Paginierung und Datentabellen verwenden. Fügen Sie diese einfach zu Beginn Ihrer SQL hinzu und verwenden Sie sie. Speichern Sie eine zusätzliche Abfrage für die Gesamtzahl.
Ahmed Sunny
Könnten Sie dies näher erläutern, wenn die Zählung in der Abfrage über einen Eingabeparameter dynamisch aktiviert werden könnte? Ich habe eine ähnliche Anforderung, aber der Benutzer entscheidet, ob er die Inline-Zählung möchte oder nicht.
Julealgon
1
@ julealgon: Bitte beginnen Sie eine neue Frage mit den definierenden Details. Sie können jederzeit einen Link zu diesem Kontext erstellen und hier einen Kommentar hinterlassen, um einen Link zu erstellen (und meine Aufmerksamkeit zu erhalten), wenn Sie dies wünschen.
Erwin Brandstetter
1
@JustinL.: Der zusätzliche Overhead sollte nur für relativ günstige Basisabfragen von Bedeutung sein. Außerdem hat Postgres 12 die CTE-Leistung in mehrfacher Hinsicht verbessert. (Obwohl dieser CTE immer noch MATERIALIZEDstandardmäßig ist und zweimal referenziert wird.)
Erwin Brandstetter
0

Bearbeiten: Diese Antwort ist gültig, wenn die ungefilterte Tabelle abgerufen wird. Ich werde es zulassen, falls es jemandem helfen könnte, aber es könnte die ursprüngliche Frage nicht genau beantworten.

Die Antwort von Erwin Brandstetter ist perfekt, wenn Sie einen genauen Wert benötigen. Bei großen Tischen benötigen Sie jedoch oft nur eine ziemlich gute Annäherung. Postgres bietet Ihnen genau das und es wird viel schneller sein, da nicht jede Zeile ausgewertet werden muss:

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;

Ich bin mir eigentlich nicht sicher, ob es einen Vorteil gibt, das zu externalisieren RIGHT JOINoder es wie in einer Standardabfrage zu haben. Es würde einige Tests verdienen.

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 ?
François Gueguen
quelle
2
Informationen zur schnellen Zählung: stackoverflow.com/a/7945274/939860 Wie Sie sagten: Gültig beim Abrufen der gesamten Tabelle - was durch die WHEREKlausel in Ihren Abfragen widerlegt wird . Die zweite Abfrage ist logisch falsch (ruft eine Zeile für jede Tabelle in der Datenbank ab) - und teurer, wenn sie behoben ist.
Erwin Brandstetter
-6

Es ist eine schlechte Praxis, zweimal dieselbe Abfrage für Just aufzurufen, um die Gesamtzahl der Zeilen des Returend-Ergebnisses zu erhalten. Es dauert Ausführungszeit und verschwendet die Serverressource.

Besser, Sie können SQL_CALC_FOUND_ROWSin der Abfrage verwenden, die MySQL anweist, die Gesamtzahl der Zeilen zusammen mit den Ergebnissen der Grenzwertabfrage abzurufen.

Beispiel gesetzt als:

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

SELECT FOUND_ROWS();

Fügen Sie in der obigen Abfrage einfach die SQL_CALC_FOUND_ROWSOption in der restlichen erforderlichen Abfrage hinzu und führen Sie die zweite Zeile aus, dh geben Sie SELECT FOUND_ROWS()die Anzahl der Zeilen in der von dieser Anweisung zurückgegebenen Ergebnismenge zurück.

Mohd Rashid
quelle
1
Die Lösung erfordert Postgres, nicht MySQL.
MuffinMan
@MuffinMan, können Sie das gleiche auf MySQL verwenden. Seit MYSQL 4.0 wird die Option SQL_CALC_FOUND_ROWS in der Abfrage verwendet. Aber von MYSQL 8.0 ist es deprectaed.
Mohd Rashid
Nicht relevant. Diese Frage wurde vor Jahren beantwortet. Wenn Sie einen Beitrag leisten möchten, stellen Sie eine neue Frage mit demselben Thema, die jedoch spezifisch für MySQL ist.
MuffinMan
immer relevant sein
Ali Hussain
-15

Nein.

Es gibt vielleicht einen kleinen Gewinn, den Sie theoretisch gewinnen könnten, wenn Sie sie einzeln mit genügend komplizierten Maschinen unter der Haube betreiben. Wenn Sie jedoch wissen möchten, wie viele Zeilen einer Bedingung entsprechen, müssen Sie sie zählen und nicht nur eine BEGRENZTE Teilmenge.

Richard Huxton
quelle