Row Offset ใน SQL Server


133

มีวิธีใดใน SQL Server เพื่อให้ได้ผลลัพธ์ที่เริ่มต้นจากค่าชดเชยที่กำหนด ตัวอย่างเช่นในฐานข้อมูล SQL ประเภทอื่นสามารถทำได้:

SELECT * FROM MyTable OFFSET 50 LIMIT 25

เพื่อให้ได้ผลลัพธ์ 51-75 โครงสร้างนี้ไม่มีอยู่ใน SQL Server

ฉันจะทำสิ่งนี้ได้อย่างไรโดยไม่ต้องโหลดแถวทั้งหมดที่ฉันไม่สนใจ ขอบคุณ!


คุณสามารถใช้ offset และเรียกคำสั่งถัดไป youtu.be/EqHkAiiBwPc
Amresh Kumar Singh

คำตอบ:


154

SELECT *ฉันจะหลีกเลี่ยงการใช้ ระบุคอลัมน์ที่คุณต้องการแม้ว่าจะเป็นคอลัมน์ทั้งหมดก็ตาม

SQL Server 2005+

SELECT col1, col2 
FROM (
    SELECT col1, col2, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum
    FROM MyTable
) AS MyDerivedTable
WHERE MyDerivedTable.RowNum BETWEEN @startRow AND @endRow

SQL Server 2000

การเพจอย่างมีประสิทธิภาพผ่านชุดผลลัพธ์ขนาดใหญ่ใน SQL Server 2000

วิธีที่มีประสิทธิภาพมากขึ้นสำหรับการเพจผ่านชุดผลลัพธ์ขนาดใหญ่


6
เหตุใดคุณจึงแนะนำให้หลีกเลี่ยงการเลือกแม้ว่าคุณจะเลือกคอลัมน์ทั้งหมดก็ตาม
Adam Ness

12
ฉันแน่ใจว่าเขาใช้ "*" เพราะพิมพ์ง่ายกว่าและจับประเด็นได้ดีกว่า "col1, col2, ... colN"
gillonba

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

5
เลือกข้อมูลทั้งหมดของตารางและตัด? ถ้ามี 5000000000 แถว? เลือก 5000000000 แถวและตัดสำหรับแต่ละแบบสอบถาม? ไม่เหมาะสำหรับซีพียูและหน่วยความจำของเซิร์ฟเวอร์
e-info128

3
โปรดทราบว่า 2012+ ได้ใช้วิธีที่ดีขึ้น ดูคำตอบโดย + Martin Smith
meridius

100

หากคุณจะประมวลผลหน้าทั้งหมดตามลำดับเพียงแค่จำค่าคีย์สุดท้ายที่เห็นในหน้าก่อนหน้านี้และการใช้TOP (25) ... WHERE Key > @last_key ORDER BY Keyอาจเป็นวิธีที่มีประสิทธิภาพดีที่สุดหากมีดัชนีที่เหมาะสมเพื่อให้สามารถค้นหาสิ่งนี้ได้อย่างมีประสิทธิภาพ - หรือเคอร์เซอร์ APIหากไม่มี .

สำหรับการเลือกเพจอนุญาโตตุลาการทางออกที่ดีที่สุดสำหรับ SQL Server 2005 - 2008 R2 น่าจะเป็นROW_NUMBERและBETWEEN

สำหรับ SQL Server 2012+ คุณสามารถใช้คำสั่ง ORDER BY ที่ปรับปรุงแล้วสำหรับความต้องการนี้

SELECT  *
FROM     MyTable 
ORDER BY OrderingColumn ASC 
OFFSET  50 ROWS 
FETCH NEXT 25 ROWS ONLY 

แม้ว่าตัวเลือกนี้จะมีประสิทธิภาพดีเพียงใด


2
ตอนนี้พร้อมใช้งานใน SQL Server Compact 4.0 -> msdn.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
Bart Verkoeijen

13
มันเป็นเรื่องของเวลาที่พวกเขาเพิ่มรายการนี้ลง TSQL
JohnFx

3
สำหรับ Sql Server 2012 เท่านั้น :(
e-info128

22

นี่เป็นวิธีหนึ่ง (SQL2000)

SELECT * FROM
(
    SELECT TOP (@pageSize) * FROM
    (
        SELECT TOP (@pageNumber * @pageSize) *
        FROM tableName 
        ORDER BY columnName ASC
    ) AS t1 
    ORDER BY columnName DESC
) AS t2 
ORDER BY columnName ASC

และนี่เป็นอีกวิธีหนึ่ง (SQL 2005)

;WITH results AS (
    SELECT 
        rowNo = ROW_NUMBER() OVER( ORDER BY columnName ASC )
        , *
    FROM tableName 
) 
SELECT * 
FROM results
WHERE rowNo between (@pageNumber-1)*@pageSize+1 and @pageNumber*@pageSize

เพียงเพื่อชี้แจงในข้อแรก ... (@pageSize) เป็นตัวยึดสำหรับค่าที่แท้จริง คุณจะต้องทำ '25 อันดับแรก' โดยเฉพาะ SQL Server 2000 ไม่สนับสนุนตัวแปรในคำสั่ง TOP สิ่งนี้ทำให้เกิดความเจ็บปวดเกี่ยวกับไดนามิก SQL
Cowan

5
โซลูชันนั้นสำหรับ SQL2000 ใช้ไม่ได้กับหน้าสุดท้ายในชุดผลลัพธ์เว้นแต่ว่าจำนวนแถวทั้งหมดจะเป็นจำนวนหลายขนาดของเพจ
Bill Karwin

10

คุณสามารถใช้ROW_NUMBER()ฟังก์ชันเพื่อรับสิ่งที่คุณต้องการ:

SELECT *
FROM (SELECT ROW_NUMBER() OVER(ORDER BY id) RowNr, id FROM tbl) t
WHERE RowNr BETWEEN 10 AND 20

7

มีอยู่OFFSET .. FETCHใน SQL Server 2012 แต่คุณจะต้องระบุORDER BYคอลัมน์

หากคุณไม่มีคอลัมน์ที่ชัดเจนที่คุณสามารถส่งผ่านเป็นORDER BYคอลัมน์ได้ (ตามที่คนอื่นแนะนำ) คุณสามารถใช้เคล็ดลับนี้:

SELECT * FROM MyTable 
ORDER BY @@VERSION 
OFFSET 50 ROWS FETCH NEXT 25 ROWS ONLY

... หรือ

SELECT * FROM MyTable 
ORDER BY (SELECT 0)
OFFSET 50 ROWS FETCH NEXT 25 ROWS ONLY

เรากำลังใช้มันในjOOQเมื่อผู้ใช้ไม่ได้ระบุคำสั่งซื้ออย่างชัดเจน จากนั้นจะสร้างการสั่งซื้อแบบสุ่มโดยไม่มีค่าใช้จ่ายเพิ่มเติม


6

สำหรับตารางที่มีคอลัมน์ข้อมูลมากขึ้นฉันชอบ:

SELECT 
  tablename.col1,
  tablename.col2,
  tablename.col3,
  ...
FROM
(
  (
    SELECT
      col1
    FROM 
    (
      SELECT col1, ROW_NUMBER() OVER (ORDER BY col1 ASC) AS RowNum
      FROM tablename
      WHERE ([CONDITION])
    )
    AS T1 WHERE T1.RowNum BETWEEN [OFFSET] AND [OFFSET + LIMIT]
  )
  AS T2 INNER JOIN tablename ON T2.col1=tablename.col1
);

-

[CONDITION] can contain any WHERE clause for searching.
[OFFSET] specifies the start,
[LIMIT] the maximum results.

มีประสิทธิภาพที่ดีกว่ามากบนตารางที่มีข้อมูลขนาดใหญ่เช่น BLOB เนื่องจากฟังก์ชัน ROW_NUMBER ต้องมองผ่านคอลัมน์เดียวเท่านั้นและจะแสดงเฉพาะแถวที่ตรงกันพร้อมกับคอลัมน์ทั้งหมด


5

ดูตัวเลือกของฉันสำหรับ paginator

SELECT TOP @limit * FROM (
   SELECT ROW_NUMBER() OVER (ORDER BY colunx ASC) offset, * FROM (

     -- YOU SELECT HERE
     SELECT * FROM mytable


   ) myquery
) paginator
WHERE offset > @offset

สิ่งนี้ช่วยแก้การแบ่งหน้า;)


3
SELECT TOP 75 * FROM MyTable
EXCEPT 
SELECT TOP 50 * FROM MyTable

ประสิทธิภาพที่ชาญฉลาดดูเหมือนจะไม่ดีที่สุดเนื่องจากการสืบค้นจะถูกเรียกใช้สองครั้งโดยไม่จำเป็น โดยเฉพาะอย่างยิ่งเมื่อผู้ใช้ไปที่หน้าที่สูงขึ้นการค้นหาเพื่อละทิ้งแถวเช่นส่วนด้านล่าง EXCEPT จะใช้เวลานานขึ้นและนานขึ้น
vanval

2

ขึ้นอยู่กับเวอร์ชันของคุณคุณไม่สามารถทำได้โดยตรง แต่คุณสามารถทำสิ่งที่แฮ็กได้เช่น

select top 25 *
from ( 
  select top 75 *
  from   table 
  order by field asc
) a 
order by field desc 

โดยที่ 'ฟิลด์' เป็นกุญแจสำคัญ


4
โซลูชันนั้นสำหรับ SQL2000 ใช้ไม่ได้กับหน้าสุดท้ายในชุดผลลัพธ์เว้นแต่ว่าจำนวนแถวทั้งหมดจะเป็นจำนวนหลายขนาดของเพจ
Bill Karwin

2

ต่อไปนี้จะแสดง 25 ระเบียนยกเว้น 50 ระเบียนแรกที่ทำงานใน SQL Server 2012

SELECT * FROM MyTable ORDER BY ID OFFSET 50 ROWS FETCH NEXT 25 ROWS ONLY;

คุณสามารถแทนที่ ID ตามความต้องการของคุณ


โปรดเพิ่มสิ่งนี้เป็นไปได้ใน SQL SERVER 2012
Usman Younas

2

คุณควรระมัดระวังในการใช้ROW_NUMBER() OVER (ORDER BY)ข้อความนี้เนื่องจากประสิทธิภาพค่อนข้างแย่ เช่นเดียวกันกับการใช้ Common Table Expressions ROW_NUMBER()ที่แย่กว่านั้น ฉันใช้ตัวอย่างข้อมูลต่อไปนี้ที่พิสูจน์แล้วว่าเร็วกว่าการใช้ตัวแปรตารางที่มีข้อมูลประจำตัวเพื่อระบุหมายเลขหน้า

DECLARE @Offset INT = 120000
DECLARE @Limit INT = 10

DECLARE @ROWCOUNT INT = @Offset+@Limit
SET ROWCOUNT @ROWCOUNT

SELECT * FROM MyTable INTO #ResultSet
WHERE MyTable.Type = 1

SELECT * FROM
(
    SELECT *, ROW_NUMBER() OVER(ORDER BY SortConst ASC) As RowNumber FROM
    (
        SELECT *, 1 As SortConst FROM #ResultSet
    ) AS ResultSet
) AS Page
WHERE RowNumber BETWEEN @Offset AND @ROWCOUNT

DROP TABLE #ResultSet

สิ่งนี้จะส่งคืน 11 แถวไม่ใช่ 10
Aaron Bertrand

1

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

ตารางมีดังต่อไปนี้:

ID, KeyId, Rank

อันดับเดียวกันจะถูกกำหนดให้กับ KeyId มากกว่าหนึ่งรายการ

SQL คือ select top 2 * from Table1 where Rank >= @Rank and ID > @Id

เป็นครั้งแรกที่ฉันผ่าน 0 สำหรับทั้งคู่ ครั้งที่สองผ่าน 1 & 14 ครั้งที่ 3 ผ่าน 2 และ 6 ....

ค่าของอันดับและรหัสระเบียนที่ 10 จะถูกส่งต่อไปยังรายการถัดไป

11  21  1
14  22  1
7   11  1
6   19  2
12  31  2
13  18  2

สิ่งนี้จะมีความเครียดน้อยที่สุดในระบบ


1

ใน SqlServer2005 คุณสามารถทำสิ่งต่อไปนี้:

DECLARE @Limit INT
DECLARE @Offset INT
SET @Offset = 120000
SET @Limit = 10

SELECT 
    * 
FROM
(
   SELECT 
       row_number() 
   OVER 
      (ORDER BY column) AS rownum, column2, column3, .... columnX
   FROM   
     table
) AS A
WHERE 
 A.rownum BETWEEN (@Offset) AND (@Offset + @Limit-1) 

ควร@Offset + @Limit - 1หรือไม่? ถ้า @Limit เท่ากับ 10 สิ่งนี้จะส่งคืน 11 แถว
Aaron Bertrand

1

วิธีที่ดีที่สุดโดยไม่ต้องเสียเวลาในการสั่งซื้อบันทึกมีดังนี้:

select 0 as tmp,Column1 from Table1 Order by tmp OFFSET 5000000 ROWS FETCH NEXT 50 ROWS ONLY

ใช้เวลาไม่ถึงหนึ่งวินาที!
ทางออกที่ดีที่สุดสำหรับโต๊ะขนาดใหญ่


0

ฉันค้นหาคำตอบนี้มาระยะหนึ่งแล้ว (สำหรับข้อความค้นหาทั่วไป) และพบวิธีอื่นในการทำบน SQL Server 2000+ โดยใช้ ROWCOUNT และเคอร์เซอร์และไม่มี TOP หรือตารางชั่วคราวใด ๆ

ใช้คำสั่งSET ROWCOUNT [OFFSET+LIMIT]คุณสามารถ จำกัด ผลลัพธ์และด้วยเคอร์เซอร์ให้ไปที่แถวที่คุณต้องการโดยตรงจากนั้นวนซ้ำ 'จนจบ

ดังนั้นคำถามของคุณจะเป็นดังนี้:

SET ROWCOUNT 75 -- (50 + 25)
DECLARE MyCursor SCROLL CURSOR FOR SELECT * FROM pessoas
OPEN MyCursor
FETCH ABSOLUTE 50 FROM MyCursor -- OFFSET
WHILE @@FETCH_STATUS = 0 BEGIN
    FETCH next FROM MyCursor
END
CLOSE MyCursor
DEALLOCATE MyCursor
SET ROWCOUNT 0

ฉันไม่อยากเห็นการแสดงของสิ่งนี้เมื่อคุณไปอยู่ท้ายตาราง ...
Aaron Bertrand

0

ด้วย SQL Server 2012 (11.x) และใหม่กว่าและฐานข้อมูล Azure SQL คุณยังสามารถมี "fetch_row_count_expression" คุณยังสามารถมีคำสั่ง ORDER BY ควบคู่ไปด้วย

USE AdventureWorks2012;  
GO  
-- Specifying variables for OFFSET and FETCH values    
DECLARE @skip int = 0  , @take int = 8;  
SELECT DepartmentID, Name, GroupName  
FROM HumanResources.Department  
ORDER BY DepartmentID ASC   
    OFFSET @skip ROWS   
    FETCH NEXT @take ROWS ONLY; 

https://docs.microsoft.com/en-us/sql/t-sql/queries/select-order-by-clause-transact-sql?view=sql-server-ver15

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

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