Nhận tổng số hàng từ OFFSET / FETCH NEXT


92

Vì vậy, tôi có một hàm trả về một số bản ghi mà tôi muốn triển khai phân trang trên trang web của mình. Tôi đã gợi ý rằng tôi sử dụng Offset / Fetch Next trong SQL Server 2012 để thực hiện điều này. Trên trang web của chúng tôi, chúng tôi có một khu vực liệt kê tổng số bản ghi và trang bạn đang truy cập vào thời điểm đó.

Trước đây, tôi đã thiết lập toàn bộ kỷ lục và có thể tạo phân trang trên đó theo chương trình. Nhưng sử dụng cách SQL với CHỈ FETCH NEXT X ROWS, tôi chỉ được trả lại X hàng, vì vậy tôi không biết tổng bộ bản ghi của mình là bao nhiêu và cách tính số trang tối thiểu và tối đa của tôi. Cách duy nhất tôi có thể biết để làm điều này là gọi hàm hai lần và thực hiện đếm số hàng trên lần đầu tiên, sau đó chạy lần thứ hai với FETCH NEXT. Có cách nào tốt hơn mà không khiến tôi phải chạy truy vấn hai lần không? Tôi đang cố gắng tăng tốc hiệu suất, không làm chậm nó.

Câu trả lời:


115

Bạn có thể sử dụng COUNT(*) OVER()... đây là một ví dụ nhanh bằng cách sử dụng 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;

Tuy nhiên, điều này nên được dành riêng cho các tập dữ liệu nhỏ; trên các bộ lớn hơn, hiệu suất có thể rất khó. Xem bài viết này của Paul White để biết các lựa chọn thay thế tốt hơn , bao gồm duy trì các chế độ xem được lập chỉ mục (chỉ hoạt động nếu kết quả chưa được lọc hoặc bạn biết WHEREtrước các điều khoản) và sử dụng các ROW_NUMBER()thủ thuật.


44
Trong một bảng có 3.500.000 bản ghi, COUNT (*) OVER () mất 1 phút 3 giây. Cách tiếp cận được mô tả dưới đây bởi James Moberg mất 13 giây để truy xuất cùng một tập dữ liệu. Tôi chắc chắn rằng phương pháp Count Over hoạt động tốt đối với các tập dữ liệu nhỏ hơn, nhưng khi bạn bắt đầu trở nên thực sự lớn, nó sẽ chậm lại đáng kể.
matthew_360

Hoặc bạn có thể chỉ cần sử dụng COUNT (1) OVER (), nhanh hơn helluvalot vì nó không phải đọc dữ liệu thực tế từ bảng, giống như count (*)
ldx

1
@AaronBertrand Thật không? điều đó có nghĩa là bạn có một chỉ mục bao gồm tất cả các cột hoặc chỉ mục này đã được cải thiện rất nhiều kể từ 2008R2. Trong phiên bản đó, số đếm (*) hoạt động tuần tự, có nghĩa là * đầu tiên (như trong: tất cả các cột) được chọn, sau đó được tính. Nếu bạn đã đếm (1), bạn chỉ cần chọn một hằng số, nhanh hơn rất nhiều so với việc đọc dữ liệu thực tế.
ldx

5
@idx Không, đó không phải là cách hoạt động trong 2008 R2, xin lỗi. Tôi đã sử dụng SQL Server từ 6.5 và tôi không nhớ lại thời điểm mà công cụ không đủ thông minh để chỉ quét chỉ mục hẹp nhất cho cả COUNT (*) hoặc COUNT (1). Chắc chắn không phải từ năm 2000. Nhưng này, tôi có một phiên bản 2008 R2, bạn có thể thiết lập một repro trên SQLfiddle để chứng minh sự khác biệt này mà bạn cho là tồn tại không? Tôi rất vui khi thử nó.
Aaron Bertrand

2
trên cơ sở dữ liệu sql server 2016, tìm kiếm trên một bảng với khoảng 25 triệu hàng, phân trang trên khoảng 3000 kết quả (với một số phép nối, bao gồm cả một hàm có giá trị bảng), điều này mất mili giây - thật tuyệt vời!
jkerak

141

Tôi đã gặp một số vấn đề về hiệu suất khi sử dụng phương thức COUNT ( ) OVER (). (Tôi không chắc đó có phải là máy chủ hay không vì mất 40 giây để trả về 10 bản ghi và sau đó không gặp bất kỳ sự cố nào.) Kỹ thuật này hoạt động trong mọi điều kiện mà không cần phải sử dụng COUNT ( ) OVER () và hoàn thành điều tương tự:

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

32
Sẽ thực sự tuyệt vời nếu có khả năng lưu giá trị COUNT (*) vào một biến. Tôi có thể đặt nó làm tham số OUTPUT của Thủ tục đã lưu trữ của tôi. Có ý kiến ​​gì không?
Đến Ka

1
Có cách nào để có được số lượng trong một bảng riêng biệt không? Có vẻ như bạn chỉ có thể sử dụng "TempResult" cho câu lệnh SELECT đứng trước đầu tiên.
matthew_360

4
Tại sao điều này hoạt động rất tốt? Trong CTE đầu tiên, tất cả các hàng được chọn, sau đó giảm dần theo lần tìm nạp. Tôi đã đoán rằng việc chọn tất cả hàng trong CTE đầu tiên sẽ làm mọi thứ chậm lại đáng kể. Trong mọi trường hợp, cảm ơn vì điều này!
jbd

1
trong trường hợp của tôi, nó chậm hơn COUNT (1) OVER () .. có thể do một hàm trong vùng chọn.
Tiju John

1
Điều này hoạt động hoàn hảo cho cơ sở dữ liệu nhỏ khi hàng triệu hàng mất quá nhiều thời gian.
Kiya

1

Dựa trên câu trả lời của James Moberg :

Đây là một giải pháp thay thế bằng cách sử dụng Row_Number(), nếu bạn không có SQL server 2012 và bạn không thể sử dụng 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.