Mendapatkan jumlah baris total dari OFFSET / FETCH NEXT

92

Jadi, saya punya fungsi yang mengembalikan sejumlah catatan yang ingin saya terapkan paging untuk situs web saya. Disarankan kepada saya agar saya menggunakan Offset / Fetch Next di SQL Server 2012 untuk mencapai ini. Di situs web kami, kami memiliki area yang mencantumkan jumlah total rekaman dan halaman apa yang Anda buka saat itu.

Sebelumnya, saya mendapatkan seluruh kumpulan rekor dan mampu membangun paging pada itu secara terprogram. Tetapi menggunakan cara SQL dengan HANYA FETCH NEXT X ROWS, saya hanya diberikan baris X kembali, jadi saya tidak tahu apa kumpulan record total saya dan bagaimana menghitung halaman min dan max saya. Satu-satunya cara saya tahu melakukan ini adalah memanggil fungsi dua kali dan melakukan hitungan baris pada baris pertama, lalu menjalankan yang kedua dengan FETCH NEXT. Adakah cara yang lebih baik agar saya tidak menjalankan kueri dua kali? Saya mencoba untuk mempercepat kinerja, bukan memperlambatnya.

CrystalBlue
sumber

Jawaban:

115

Anda dapat menggunakan COUNT(*) OVER()... berikut adalah contoh cepat menggunakan sys.all_objects:

DECLARE 
  @PageSize INT = 10, 
  @PageNum  INT = 1;

SELECT 
  name, object_id, 
  overall_count = COUNT(*) OVER()
FROM sys.all_objects
ORDER BY name
  OFFSET (@PageNum-1)*@PageSize ROWS
  FETCH NEXT @PageSize ROWS ONLY;

Namun, ini harus disediakan untuk kumpulan data kecil; pada set yang lebih besar, kinerjanya bisa sangat buruk. Lihat artikel Paul White ini untuk alternatif yang lebih baik , termasuk mempertahankan tampilan terindeks (yang hanya berfungsi jika hasilnya tidak difilter atau Anda mengetahui WHEREklausul sebelumnya) dan menggunakan ROW_NUMBER()trik.

Aaron Bertrand
sumber
44
Dalam tabel dengan 3.500.000 record, COUNT (*) OVER () membutuhkan waktu 1 menit dan 3 detik. Pendekatan yang dijelaskan di bawah oleh James Moberg membutuhkan waktu 13 detik untuk mengambil kumpulan data yang sama. Saya yakin pendekatan Count Over berfungsi dengan baik untuk kumpulan data yang lebih kecil, tetapi ketika Anda mulai menjadi sangat besar, itu melambat secara signifikan.
matthew_360
Atau Anda bisa menggunakan COUNT (1) OVER () yang sangat cepat karena tidak perlu membaca data aktual dari tabel, seperti count (*)
ldx
1
@AaronBrand Benarkah? itu berarti Anda memiliki indeks yang mencakup semua kolom, atau indeks ini telah banyak ditingkatkan sejak 2008R2. Dalam versi itu, hitungan (*) bekerja secara berurutan, artinya * pertama (seperti dalam: semua kolom) dipilih, lalu dihitung. Jika Anda menghitung (1), Anda tinggal memilih konstanta, yang jauh lebih cepat daripada membaca data sebenarnya.
ldx
5
@idx Tidak, itu juga bukan cara kerjanya di 2008 R2, maaf. Saya telah menggunakan SQL Server sejak 6.5 dan saya tidak ingat saat mesin tidak cukup pintar untuk hanya memindai indeks tersempit untuk COUNT (*) atau COUNT (1). Tentu saja tidak sejak tahun 2000. Tapi hei, saya memiliki contoh 2008 R2, dapatkah Anda menyiapkan repro di SQLfiddle yang menunjukkan perbedaan yang Anda klaim ada? Saya senang mencobanya.
Aaron Bertrand
2
pada database sql server 2016, mencari di atas tabel dengan sekitar 25 juta baris, menampilkan lebih dari 3000 hasil (dengan beberapa gabungan, termasuk ke fungsi bernilai tabel), ini membutuhkan waktu milidetik - luar biasa!
jkerak
142

Saya mengalami beberapa masalah kinerja menggunakan metode COUNT ( ) OVER (). (Saya tidak yakin apakah itu server karena butuh 40 detik untuk mengembalikan 10 catatan dan kemudian tidak memiliki masalah apa pun.) Teknik ini bekerja dalam semua kondisi tanpa harus menggunakan COUNT ( ) OVER () dan menyelesaikan hal yang sama:

DECLARE 
    @PageSize INT = 10, 
    @PageNum  INT = 1;

WITH TempResult AS(
    SELECT ID, Name
    FROM Table
), TempCount AS (
    SELECT COUNT(*) AS MaxRows FROM TempResult
)
SELECT *
FROM TempResult, TempCount
ORDER BY TempResult.Name
    OFFSET (@PageNum-1)*@PageSize ROWS
    FETCH NEXT @PageSize ROWS ONLY
James Moberg
sumber
32
Akan sangat luar biasa jika ada kemungkinan untuk menyimpan nilai COUNT (*) ke variabel. Saya akan dapat mengaturnya sebagai parameter OUTPUT dari Prosedur Tersimpan saya. Ada ide?
Ke Ka
1
Apakah ada cara untuk menghitung di tabel terpisah? Sepertinya Anda hanya dapat menggunakan "TempResult" untuk pernyataan SELECT sebelumnya yang pertama.
matthew_360
4
Mengapa ini bekerja dengan baik? Dalam CTE pertama, semua baris dipilih, lalu dikupas oleh pengambilan. Saya akan menduga bahwa memilih semua baris di CTE pertama akan memperlambat segalanya secara signifikan. Bagaimanapun, terima kasih untuk ini!
jbd
1
dalam kasus saya itu melambat dari COUNT (1) OVER () .. mungkin karena fungsi di pilih.
Tiju John
1
Ini berfungsi sempurna untuk database kecil ketika baris berjumlah jutaan, itu membutuhkan terlalu banyak waktu.
Kiya
1

Berdasarkan jawaban James Moberg :

Ini adalah penggunaan alternatif Row_Number(), jika Anda tidak memiliki SQL server 2012 dan Anda tidak dapat menggunakan OFFSET

DECLARE 
    @PageNumEnd INT = 10, 
    @PageNum  INT = 1;

WITH TempResult AS(
    SELECT ID, NAME
    FROM Tabla
), TempCount AS (
    SELECT COUNT(*) AS MaxRows FROM TempResult
)

select * 
from
(
    SELECT
     ROW_NUMBER() OVER ( ORDER BY PolizaId DESC) AS 'NumeroRenglon', 
     MaxRows, 
     ID,
     Name
    FROM TempResult, TempCount

)resultados
WHERE   NumeroRenglon >= @PageNum
    AND NumeroRenglon <= @PageNumEnd
ORDER BY NumeroRenglon
elblogdelbeto
sumber