Зміщення рядків у SQL сервері


133

Чи є спосіб у SQL Server отримати результати, починаючи з заданого зміщення? Наприклад, в іншому типі бази даних SQL можна зробити:

SELECT * FROM MyTable OFFSET 50 LIMIT 25

щоб отримати результати 51-75. Схоже, ця конструкція не існує в SQL Server.

Як я можу це зробити, не завантажуючи всі рядки, які мені не цікаві? Дякую!


Ви можете використовувати зміщення та отримання наступного оператора. youtu.be/EqHkAiiBwPc
Амреш Кумар Сінгх

Відповіді:


152

Я б уникав використання 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
Чому ви пропонуєте уникати SELECT, навіть якщо ви вибираєте всі стовпці?
Адам Несс

12
Я впевнений, що він використав "*", тому що це було простіше ввести і отримав кращу точку краще, ніж "col1, col2, ... colN"
gillonba

9
Щодо того, чому його не використовувати, SELECT *означає, що якщо структура таблиці зміниться, ваш запит все ще працює, але дає різні результати. Якщо стовпець доданий, це може бути корисним (хоча ви все ще повинні десь його використовувати по імені); якщо стовпець буде видалено або перейменовано, ваш SQL краще зламати помітно, ніж код далі, ведучи себе дивно, оскільки змінна неініціалізована.
IMSoP

5
вибрати всі дані таблиці та вирізати? якщо має 5000000000 рядків? виберіть 5000000000 рядків і виріжте для кожного запиту? його не ефективно для процесора та пам'яті сервера.
e-info128

3
Зверніть увагу, що в 2012+ вона реалізується набагато краще. Дивіться відповідь + Мартін Сміт
меридіус

100

Якщо ви будете обробляти всі сторінки для того, щоб просто запам'ятати останнє значення ключа, помічене на попередній сторінці, і використовувати, TOP (25) ... WHERE Key > @last_key ORDER BY Keyможе бути найкращим методом, якщо існують відповідні індекси, які дозволять це ефективно шукати - або курсором API, якщо вони не .

Для вибору сторінки довільній кращим рішення для SQL Server 2005 R2 - 2008, ймовірно , 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.
Коуан

5
Це рішення для SQL2000 не працює для останньої сторінки в наборі результатів, якщо тільки загальна кількість рядків не кратна розміру сторінки.
Білл Карвін

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

Дивіться мій вибір для сторінки-пагінатора

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 займе більше часу.
ванвал

2

Залежно від вашої версії, ви не можете це зробити безпосередньо, але ви можете зробити щось нахабне

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

де 'поле' є ключовим.


4
Це рішення для SQL2000 не працює для останньої сторінки в наборі результатів, якщо тільки загальна кількість рядків не кратна розміру сторінки.
Білл Карвін

2

Далі буде показано 25 записів, за винятком перших 50 записів роботи в SQL Server 2012.

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

ви можете замінити ідентифікатор як свою вимогу


Будь ласка, додайте це можливо в SQL SERVER 2012
Usman Younas

2

Ви повинні бути обережними при використанні ROW_NUMBER() OVER (ORDER BY)оператора, оскільки продуктивність досить низька. Те саме стосується використання загальних табличних виразів, 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.
Аарон Бертран

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-го запису Rank & Id передається наступному

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 рядків.
Аарон Бертран

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

Мені б не хотілося бачити результати цього, коли ви підходите до кінця таблиці ...
Аарон Бертран

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, щоб включити перший запис.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.