เรียกใช้แบบสอบถามด้วย LIMIT / OFFSET และรับจำนวนแถวทั้งหมด


102

เพื่อจุดประสงค์ในการแบ่งหน้าฉันต้องการเรียกใช้แบบสอบถามที่มีLIMITและOFFSETอนุประโยค แต่ฉันยังต้องการจำนวนแถวที่จะถูกส่งคืนโดยแบบสอบถามนั้นโดยไม่มีLIMITและส่วนOFFSETคำสั่ง

ฉันต้องการวิ่ง:

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

และ:

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

ในเวลาเดียวกัน. มีวิธีดำเนินการโดยเฉพาะอย่างยิ่งวิธีที่ช่วยให้ Postgres ปรับให้เหมาะสมเพื่อให้ทำงานได้เร็วกว่าการทำงานทั้งสองอย่างทีละรายการ


คำตอบ:


178

ใช่. ด้วยฟังก์ชันหน้าต่างที่เรียบง่าย:

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

โปรดทราบว่าค่าใช้จ่ายจะสูงกว่าการไม่มีจำนวนทั้งหมดอย่างมาก แต่โดยทั่วไปแล้วจะยังคงถูกกว่าการค้นหาแยกกันสองรายการ Postgres ต้องนับแถวทั้งหมดไม่ว่าจะด้วยวิธีใดก็ตามซึ่งกำหนดค่าใช้จ่ายขึ้นอยู่กับจำนวนแถวทั้งหมดที่มีคุณสมบัติเหมาะสม รายละเอียด:

อย่างไรก็ตาม , เป็น Dani ชี้ให้เห็นเมื่อOFFSETเป็นอย่างน้อยเป็นใหญ่เป็นจำนวนแถวกลับจากการสืบค้นฐานไม่มีแถวจะถูกส่งกลับ full_countดังนั้นเราจึงยังไม่ได้รับ

หากไม่เป็นที่ยอมรับวิธีแก้ปัญหาที่เป็นไปได้ในการคืนค่าการนับเต็มจะเป็น CTE และ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;

คุณจะได้รับค่า NULL หนึ่งแถวพร้อมfull_countต่อท้ายหากOFFSETมีขนาดใหญ่เกินไป มิฉะนั้นจะต่อท้ายทุกแถวเหมือนในแบบสอบถามแรก

หากแถวที่มีค่า NULL ทั้งหมดเป็นผลลัพธ์ที่เป็นไปได้คุณต้องตรวจสอบoffset >= full_countเพื่อแยกแยะจุดเริ่มต้นของแถวว่าง

การดำเนินการนี้ยังคงเรียกใช้การสืบค้นพื้นฐานเพียงครั้งเดียว แต่จะเพิ่มค่าใช้จ่ายให้กับแบบสอบถามมากขึ้นและจะจ่ายเฉพาะในกรณีที่น้อยกว่าการทำแบบสอบถามพื้นฐานซ้ำสำหรับการนับ

หากดัชนีที่สนับสนุนลำดับการจัดเรียงสุดท้ายพร้อมใช้งานอาจต้องจ่ายเพื่อรวมORDER BYCTE ไว้ใน CTE (ซ้ำซ้อน)


3
โดยทั้ง LIMIT และเงื่อนไขเรามีแถวที่จะส่งคืน แต่ด้วยค่าชดเชยที่กำหนดจะไม่ส่งคืนผลลัพธ์ ในสถานการณ์นั้นเราจะนับแถวได้อย่างไร?
Dani Mathew

ดีมากขอบคุณใช้งานได้ดีเมื่อคุณใช้การแบ่งหน้าข้อมูลเพียงแค่เพิ่มสิ่งนี้ในการเริ่มต้น sql ของคุณและใช้มันบันทึกแบบสอบถามเพิ่มเติมสำหรับการนับรวม
Ahmed Sunny

คุณช่วยอธิบายรายละเอียดเกี่ยวกับเรื่องนี้ได้หรือไม่หากสามารถเปิดใช้งานการนับแบบไดนามิกในแบบสอบถามผ่านพารามิเตอร์อินพุตได้ ฉันมีข้อกำหนดที่คล้ายกัน แต่ผู้ใช้ตัดสินใจว่าเขาต้องการนับแบบอินไลน์หรือไม่
julealgon

1
@julealgon: โปรดเริ่มคำถามใหม่พร้อมระบุรายละเอียด คุณสามารถเชื่อมโยงไปยังบริบทนี้ได้ตลอดเวลาและเพิ่มแสดงความคิดเห็นที่นี่เพื่อเชื่อมโยงกลับ (และรับความสนใจจากฉัน) หากคุณต้องการ
Erwin Brandstetter

1
@JustinL: ค่าโสหุ้ยที่เพิ่มควรมีความสำคัญสำหรับการสืบค้นพื้นฐานที่ค่อนข้างถูกเท่านั้น นอกจากนี้ Postgres 12 ยังปรับปรุงประสิทธิภาพ CTE ในหลาย ๆ ด้าน (แม้ว่า CTE นี้จะยังคงเป็นMATERIALIZEDค่าเริ่มต้น แต่มีการอ้างอิงถึงสองครั้ง)
Erwin Brandstetter

0

แก้ไข: คำตอบนี้ใช้ได้เมื่อดึงตารางที่ไม่กรอง ฉันจะปล่อยไว้เผื่อว่ามันจะช่วยใครสักคนได้ แต่มันอาจไม่ตรงกับคำถามเริ่มต้น

คำตอบของErwin Brandstetterนั้นสมบูรณ์แบบหากคุณต้องการค่าที่ถูกต้อง อย่างไรก็ตามบนโต๊ะขนาดใหญ่คุณมักจะต้องมีการประมาณที่ค่อนข้างดี Postgres ให้คุณแค่นั้นและจะเร็วกว่ามากเพราะไม่จำเป็นต้องประเมินแต่ละแถว:

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;

จริง ๆ แล้วฉันไม่แน่ใจว่ามีข้อได้เปรียบในการทำให้ภายนอกRIGHT JOINหรือมีเหมือนในแบบสอบถามมาตรฐาน มันสมควรได้รับการทดสอบบางอย่าง

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
เกี่ยวกับการประมาณจำนวนอย่างรวดเร็ว: stackoverflow.com/a/7945274/939860เช่นเดียวกับที่คุณกล่าว: ใช้ได้เมื่อเรียกข้อมูลทั้งตารางซึ่งขัดแย้งกับWHEREประโยคในข้อความค้นหาของคุณ แบบสอบถามที่สองมีเหตุผลผิด (ดึงข้อมูลหนึ่งแถวสำหรับทุกตารางใน DB) - และมีราคาแพงกว่าเมื่อแก้ไข
Erwin Brandstetter

0

ในขณะที่คำตอบของErwin Brandstetterทำงานได้อย่างมีเสน่ห์ แต่จะส่งกลับจำนวนแถวทั้งหมดในทุกแถวดังต่อไปนี้:

col1 - col2 - col3 - total
--------------------------
aaaa - aaaa - aaaa - count
bbbb - bbbb - bbbb - count
cccc - cccc - cccc - count

คุณอาจต้องการพิจารณาใช้วิธีการที่ส่งกลับจำนวนรวมเพียงครั้งเดียวดังต่อไปนี้:

total - rows
------------
count - [{col1: 'aaaa'},{col2: 'aaaa'},{col3: 'aaaa'}
         {col1: 'bbbb'},{col2: 'bbbb'},{col3: 'bbbb'}
         {col1: 'cccc'},{col2: 'cccc'},{col3: 'cccc'}]

แบบสอบถาม SQL:

SELECT 
    (SELECT COUNT(*) FROM table) as count, 
    (SELECT json_agg(t.*) FROM (
        SELECT * FROM table
        WHERE /* whatever */
        ORDER BY col1
        OFFSET ?
        LIMIT ?
    ) AS t) AS rows 

-6

วิธีปฏิบัติที่ไม่ดีในการเรียกแบบสอบถามเดียวกันสองครั้งสำหรับ Just เพื่อให้ได้จำนวนแถวทั้งหมดของผลลัพธ์ returend จะใช้เวลาดำเนินการและจะสิ้นเปลืองทรัพยากรเซิร์ฟเวอร์

ดีกว่าคุณสามารถใช้SQL_CALC_FOUND_ROWSในแบบสอบถามซึ่งจะบอกให้ MySQL ดึงจำนวนแถวทั้งหมดพร้อมกับผลลัพธ์แบบสอบถามที่ จำกัด

ตัวอย่างตั้งค่าเป็น:

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

SELECT FOUND_ROWS();

ในแบบสอบถามด้านบนเพียงแค่เพิ่มSQL_CALC_FOUND_ROWSตัวเลือกในแบบสอบถามที่จำเป็นที่เหลือและดำเนินการบรรทัดที่สองคือSELECT FOUND_ROWS()ส่งคืนจำนวนแถวในชุดผลลัพธ์ที่ส่งคืนโดยคำสั่งนั้น


1
วิธีแก้ปัญหาต้องใช้ postgres ไม่ใช่ mysql
MuffinMan

@MuffinMan คุณสามารถใช้สิ่งเดียวกันกับ mysql ได้ ตั้งแต่ MYSQL 4.0 จึงถูกใช้ตัวเลือก SQL_CALC_FOUND_ROWS ในแบบสอบถาม แต่จาก MYSQL 8.0 จะถูกยกเลิก
Mohd Rashid

ไม่เกี่ยวข้อง คำถามนี้ได้รับคำตอบเมื่อหลายปีก่อน หากคุณต้องการมีส่วนร่วมให้โพสต์คำถามใหม่ในหัวข้อเดียวกัน แต่เจาะจงไปที่ MySQL
MuffinMan

มีความเกี่ยวข้องเสมอ
Ali Hussain

-15

ไม่

บางทีอาจมีกำไรเล็กน้อยในทางทฤษฎีที่คุณจะได้รับจากการทำงานทีละอย่างด้วยเครื่องจักรที่ซับซ้อนเพียงพอภายใต้ประทุน แต่ถ้าคุณต้องการทราบจำนวนแถวที่ตรงกับเงื่อนไขคุณจะต้องนับแถวเหล่านั้นแทนที่จะเป็นเพียงชุดย่อยที่ จำกัด ไว้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.