Chạy truy vấn với LIMIT / OFFSET và cũng nhận được tổng số hàng


91

Đối với mục đích phân trang, tôi cần chạy một truy vấn với mệnh đề LIMITOFFSET. Nhưng tôi cũng cần đếm số hàng sẽ được trả về bởi truy vấn đó mà không có mệnh đề LIMITOFFSET.

Tôi muốn chạy:

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

Và:

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

Đồng thời. Có cách nào để làm điều đó, đặc biệt là cách cho phép Postgres tối ưu hóa nó, để nó nhanh hơn so với việc chạy riêng lẻ cả hai?


2
Điều này có trả lời câu hỏi của bạn không? Cách tốt nhất để tính kết quả trước khi LIMIT được áp dụng
Marty Neal

Câu trả lời:


169

Đúng. Với một chức năng cửa sổ đơn giản:

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

Lưu ý rằng chi phí sẽ cao hơn đáng kể so với không có tổng số, nhưng thường vẫn rẻ hơn so với hai truy vấn riêng biệt. Postgres thực sự phải đếm tất cả các hàng theo cả hai cách, điều này áp đặt chi phí tùy thuộc vào tổng số hàng đủ điều kiện. Chi tiết:

Tuy nhiên , như Dani đã chỉ ra , khi OFFSETít nhất bằng số hàng được trả về từ truy vấn cơ sở, không có hàng nào được trả về. Vì vậy, chúng tôi cũng không nhận được full_count.

Nếu điều đó không được chấp nhận, một giải pháp khả thi để luôn trả về tổng số đầy đủ sẽ là với CTE và 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;

Bạn nhận được một hàng giá trị NULL với full_countthêm nếu OFFSETquá lớn. Mặt khác, nó được thêm vào mọi hàng như trong truy vấn đầu tiên.

Nếu một hàng có tất cả các giá trị NULL là một kết quả hợp lệ có thể có, bạn phải kiểm tra offset >= full_countđể phân biệt nguồn gốc của hàng trống.

Điều này vẫn thực hiện truy vấn cơ sở chỉ một lần. Nhưng nó thêm nhiều chi phí hơn vào truy vấn và chỉ trả tiền nếu điều đó ít hơn việc lặp lại truy vấn cơ sở cho số lượng.

Nếu các chỉ mục hỗ trợ thứ tự sắp xếp cuối cùng có sẵn, bạn có thể phải trả tiền để đưa ORDER BYvào CTE (dự phòng).


3
Theo cả LIMIT và điều kiện, chúng ta có các hàng được trả về, nhưng với phần bù đã cho, nó sẽ không trả về kết quả. Trong tình huống đó, Làm thế nào chúng ta có thể đếm được hàng?
Dani Mathew

rất tốt, cảm ơn, hoạt động tuyệt vời khi bạn sử dụng phân trang, dữ liệu, chỉ cần thêm cái này vào đầu sql của bạn và sử dụng nó, lưu thêm một truy vấn cho tổng số.
Ahmed Sunny

Bạn có thể giải thích thêm về điều này nếu việc đếm có thể được kích hoạt động trong truy vấn thông qua một tham số đầu vào không? Tôi có một yêu cầu tương tự nhưng người dùng quyết định xem anh ta có muốn đếm nội tuyến hay không.
julealgon

1
@julealgon: Vui lòng bắt đầu một câu hỏi mới với các chi tiết xác định. Bạn luôn có thể liên kết đến cái này để biết ngữ cảnh và thêm để lại nhận xét ở đây để liên kết lại (và thu hút sự chú ý của tôi) nếu bạn muốn.
Erwin Brandstetter

1
@JustinL: Chi phí bổ sung chỉ nên có ý nghĩa đối với các truy vấn cơ sở tương đối rẻ. Ngoài ra, Postgres 12 đã cải thiện hiệu suất CTE theo nhiều cách. (Mặc dù CTE này vẫn là MATERIALIZEDmặc định, được tham chiếu hai lần.)
Erwin Brandstetter

1

chỉnh sửa: câu trả lời này hợp lệ khi truy xuất bảng chưa được lọc. Tôi sẽ để nó trong trường hợp nó có thể giúp ai đó nhưng nó có thể không trả lời chính xác câu hỏi ban đầu.

Câu trả lời của Erwin Brandstetter là hoàn hảo nếu bạn cần một giá trị chính xác. Tuy nhiên, trên các bảng lớn, bạn thường chỉ cần một giá trị gần đúng khá tốt. Postgres cung cấp cho bạn điều đó và nó sẽ nhanh hơn nhiều vì nó sẽ không cần phải đánh giá từng hàng:

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;

Tôi thực sự không chắc liệu có lợi thế để ngoại hóa RIGHT JOINhoặc có nó như trong một truy vấn chuẩn hay không. Nó sẽ xứng đáng được thử nghiệm.

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
Giới thiệu về ước tính số lượng nhanh: stackoverflow.com/a/7945274/939860 Giống như bạn đã nói: hợp lệ khi truy xuất toàn bộ bảng - điều này mâu thuẫn với WHEREmệnh đề trong truy vấn của bạn. Truy vấn thứ hai nó sai về mặt logic (truy xuất một hàng cho mọi bảng trong DB) - và đắt hơn khi được sửa.
Erwin Brandstetter

-7

Thực tế không tốt khi gọi hai lần cùng một truy vấn cho Chỉ để lấy tổng số hàng của kết quả returend. Nó sẽ mất thời gian thực hiện và gây lãng phí tài nguyên máy chủ.

Tốt hơn, bạn có thể sử dụng SQL_CALC_FOUND_ROWStrong truy vấn sẽ yêu cầu MySQL tìm nạp tổng số hàng cùng với kết quả truy vấn giới hạn.

Ví dụ được đặt là:

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

SELECT FOUND_ROWS();

Trong Truy vấn trên, Chỉ cần thêm SQL_CALC_FOUND_ROWStùy chọn trong truy vấn bắt buộc còn lại và thực hiện dòng thứ hai, tức là SELECT FOUND_ROWS()trả về số hàng trong tập kết quả được trả về bởi câu lệnh đó.


1
Giải pháp yêu cầu postgres, không phải mysql.
MuffinMan

@MuffinMan, bạn có thể sử dụng tương tự trên mysql. Kể từ MYSQL 4.0, nó đang được sử dụng tùy chọn SQL_CALC_FOUND_ROWS trong truy vấn. Nhưng từ MYSQL 8.0, nó đã được khử sửa.
Mohd Rashid

Không liên quan. Câu hỏi này đã được trả lời cách đây nhiều năm. Nếu bạn muốn đóng góp, hãy đăng một câu hỏi mới có cùng chủ đề nhưng dành riêng cho MySQL.
MuffinMan

luôn có liên quan
Ali Hussain

-14

Không.

Có lẽ có một số lợi ích nhỏ mà bạn có thể đạt được về mặt lý thuyết khi vận hành chúng riêng lẻ với đủ máy móc phức tạp dưới mui xe. Tuy nhiên, nếu bạn muốn biết có bao nhiêu hàng phù hợp với một điều kiện, bạn sẽ phải đếm chúng thay vì chỉ một tập con LIMITed.

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.