Jalankan kueri dengan LIMIT / OFFSET dan juga dapatkan jumlah baris

90

Untuk tujuan pagination, saya perlu menjalankan kueri dengan klausa LIMITdan OFFSET. Tapi saya juga perlu menghitung jumlah baris yang akan dikembalikan oleh kueri itu tanpa klausa LIMITdan OFFSET.

Saya ingin berlari:

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

Dan:

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

Pada waktu bersamaan. Adakah cara untuk melakukan itu, terutama cara yang memungkinkan Postgres mengoptimalkannya, sehingga lebih cepat daripada menjalankan keduanya secara individual?

Tim
sumber
2
Apakah ini menjawab pertanyaan Anda? Cara terbaik untuk mendapatkan hitungan hasil sebelum LIMIT diterapkan
Marty Neal

Jawaban:

168

Iya. Dengan fungsi jendela sederhana:

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

Ketahuilah bahwa biayanya akan jauh lebih tinggi daripada tanpa jumlah total, tetapi biasanya masih lebih murah daripada dua kueri terpisah. Postgres harus benar-benar menghitung semua baris dengan cara apa pun, yang membebankan biaya tergantung pada jumlah total baris yang memenuhi syarat. Rincian:

Namun , seperti yang ditunjukkan Dani , bila OFFSETsetidaknya sama banyaknya dengan jumlah baris yang dikembalikan dari kueri dasar, tidak ada baris yang dikembalikan. Jadi kami juga tidak mengerti full_count.

Jika itu tidak dapat diterima, solusi yang mungkin untuk selalu mengembalikan hitungan penuh adalah dengan CTE dan 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;

Anda mendapatkan satu baris nilai NULL dengan full_countpenambahan jika OFFSETterlalu besar. Lain, itu ditambahkan ke setiap baris seperti di kueri pertama.

Jika baris dengan semua nilai NULL adalah kemungkinan hasil yang valid, Anda harus memeriksa offset >= full_countuntuk menghilangkan keraguan asal baris kosong.

Ini masih mengeksekusi kueri dasar hanya sekali. Tapi itu menambahkan lebih banyak overhead ke kueri dan hanya membayar jika itu kurang dari mengulangi kueri dasar untuk hitungan.

Jika indeks yang mendukung tata urutan akhir tersedia, mungkin ada baiknya untuk menyertakan ORDER BYdalam CTE (secara berlebihan).

Erwin Brandstetter
sumber
3
Dengan LIMIT dan kondisi, kami memiliki baris untuk dikembalikan, tetapi dengan offset yang diberikan itu tidak akan mengembalikan hasil. Dalam situasi itu, Bagaimana kita bisa mendapatkan jumlah baris?
Dani Mathew
sangat bagus, terima kasih, berfungsi dengan baik saat Anda menggunakan pagination, datatables, cukup tambahkan ini di awal sql Anda, dan gunakan, simpan kueri tambahan untuk jumlah total.
Ahmed Sunny
Bisakah Anda menguraikan ini jika penghitungan dapat diaktifkan secara dinamis dalam kueri melalui parameter masukan? Saya memiliki persyaratan serupa tetapi pengguna memutuskan apakah dia ingin jumlah sebaris atau tidak.
julealgon
1
@julealgon: Silakan memulai pertanyaan baru dengan detail yang menentukan. Anda selalu dapat menautkan ke yang satu ini untuk konteks dan menambahkan tinggalkan komentar di sini untuk menautkan kembali (dan dapatkan perhatian saya) jika Anda mau.
Erwin Brandstetter
1
@JustinL .: Overhead tambahan seharusnya hanya signifikan untuk kueri dasar yang relatif murah. Selain itu, Postgres 12 telah meningkatkan kinerja CTE dalam berbagai cara. (Meskipun CTE ini masih MATERIALIZEDsecara default, direferensikan dua kali.)
Erwin Brandstetter
1

edit: jawaban ini valid saat mengambil tabel yang tidak difilter. Saya akan membiarkannya jika itu bisa membantu seseorang tetapi mungkin tidak menjawab pertanyaan awal dengan tepat.

Jawaban Erwin Brandstetter sempurna jika Anda membutuhkan nilai yang akurat. Namun, pada tabel besar Anda seringkali hanya membutuhkan perkiraan yang cukup bagus. Postgres memberi Anda hal itu dan akan jauh lebih cepat karena tidak perlu mengevaluasi setiap baris:

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;

Saya sebenarnya tidak yakin apakah ada keuntungan untuk mengeksternalisasi RIGHT JOINatau memilikinya seperti dalam kueri standar. Itu akan membutuhkan beberapa pengujian.

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
sumber
2
Tentang perkiraan penghitungan cepat: stackoverflow.com/a/7945274/939860 Seperti yang Anda katakan: valid saat mengambil seluruh tabel - yang bertentangan dengan WHEREklausa dalam kueri Anda. Kueri kedua secara logis salah (mengambil satu baris untuk setiap tabel di DB) - dan lebih mahal bila diperbaiki.
Erwin Brandstetter
-7

Praktik buruknya untuk memanggil dua kali kueri yang sama hanya untuk mendapatkan jumlah baris dari hasil returend. Ini akan memakan waktu eksekusi dan akan membuang sumber daya server.

Lebih baik, Anda dapat menggunakan SQL_CALC_FOUND_ROWSkueri yang akan memberi tahu MySQL untuk mengambil jumlah total baris bersama dengan hasil kueri batas.

Contoh ditetapkan sebagai:

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

SELECT FOUND_ROWS();

Dalam Query di atas, Cukup tambahkan SQL_CALC_FOUND_ROWSopsi di sisa query yang diperlukan dan jalankan baris kedua yaitu SELECT FOUND_ROWS()mengembalikan jumlah baris dalam set hasil yang dikembalikan oleh pernyataan itu.

Mohd Rashid
sumber
1
Solusinya membutuhkan postgres, bukan mysql.
MuffinMan
@MuffinMan, Anda dapat menggunakan hal yang sama di mysql. Sejak MYSQL 4.0, itu digunakan opsi SQL_CALC_FOUND_ROWS dalam kueri. Tapi dari MYSQL 8.0 itu rusak.
Mohd Rashid
Tidak berhubungan. Pertanyaan ini telah dijawab bertahun-tahun yang lalu. Jika Anda ingin berkontribusi, posting pertanyaan baru dengan subjek yang sama tetapi khusus untuk MySQL.
MuffinMan
selalu relevan
Ali Hussain
-14

Tidak.

Mungkin ada beberapa keuntungan kecil yang secara teoritis dapat Anda peroleh dari menjalankannya secara individual dengan mesin yang cukup rumit di bawah kapnya. Namun, jika Anda ingin mengetahui berapa banyak baris yang cocok dengan kondisi, Anda harus menghitungnya, bukan hanya subset LIMITed.

Richard Huxton
sumber