Как лучше всего разбивать результаты на страницы в SQL Server

Каков наилучший способ (с точки зрения производительности) разбивать результаты на страницы в SQL Server 2000, 2005, 2008, 2012, если вы также хотите получить общее количество результатов (до разбивки на страницы)?

Мне всегда было интересно, почему они просто не поддерживают указание смещения как части TOP (например, поддержка MySQL / Posgresql с LIMIT / OFFSET). Например, у них может быть просто синтаксис «SELECT TOP x, y ....», где x = количество строк, y = начальное смещение. Он также был бы обратно совместим.

gregmac 21.09.2008 00:47

эй, я тоже ... реализация разбиения на страницы в sql 2005 это действительно так неудобно ...

opensas 27.04.2009 22:42

@gregmac - Sql Server 2012 теперь имеет ограничение / смещение.

O.O 31.08.2013 21:29

Принятое решение не показывает, насколько это лучший способ (с точки зрения производительности). Есть ли резервные копии данных на больших наборах данных?

O.O 31.08.2013 21:32

@ O.O: Хороший тест можно найти здесь: 4guysfromrolla.com/webtech/042606-1.shtml. Однако метод поиска превосходит любую разбивку на страницы на основе смещения.

Lukas Eder 26.10.2013 21:55

Я собирался оставить эту тему в покое, потому что я полагал, что миллиард людей собираются вскочить на нее, но это была не такая загруженная тема, как я думал. Есть несколько статей об использовании номера строки и оператора BETWEEN для эффективной разбивки на страницы. http://www.codeproject.com/KB/database/row_number.aspxhttp://www.singingeels.com/Articles/Pagination_In_SQL_Server‌ _2005.aspx и для типа поддельных номеров строк в sql server 2000 эта ссылка должна дать вам кое-что

stephenbayer 21.09.2008 00:37

этот пример работает у меня. stackoverflow.com/posts/9328808/revisions

Cristian Agudelo 16.06.2018 04:22

Это дубликат этот вопрос

d.popov 03.12.2019 15:10
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
514
8
482 069
19
Перейти к ответу Данный вопрос помечен как решенный

Ответы 19

Вы не указали язык или драйвер, который используете. Поэтому я описываю это абстрактно.

  • Создайте прокручиваемый набор результатов / набор данных. Это потребовало первичного на столе (ах)
  • прыгать до конца
  • запросить количество строк
  • перейти к началу страницы
  • пролистайте строки до конца страницы
Ответ принят как подходящий

Получение общего количества результатов и разбиение на страницы - это две разные операции. Для этого примера предположим, что вы имеете дело с запросом

SELECT * FROM Orders WHERE OrderDate >= '1980-01-01' ORDER BY OrderDate

В этом случае вы должны определить общее количество результатов, используя:

SELECT COUNT(*) FROM Orders WHERE OrderDate >= '1980-01-01'

... что может показаться неэффективным, но на самом деле довольно производительно, если все индексы и т. д. настроены правильно.

Затем, чтобы получить фактические результаты обратно в постраничном режиме, наиболее эффективным будет следующий запрос:

SELECT  *
FROM    ( SELECT    ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum, *
          FROM      Orders
          WHERE     OrderDate >= '1980-01-01'
        ) AS RowConstrainedResult
WHERE   RowNum >= 1
    AND RowNum < 20
ORDER BY RowNum

Это вернет строки 1-19 исходного запроса. Здесь здорово, особенно для веб-приложений, то, что вам не нужно сохранять какое-либо состояние, кроме номеров строк, которые нужно вернуть.

Просто отметим, что ROW_NUMBER () не существует в SQL Server 2000.

John Hunter 03.12.2008 16:28

возвращает ли это все строки из внутреннего запроса, а затем фильтрует на основе внешнего запроса? например: внутренний запрос возвращает 100000, а внешний запрос возвращает только 20.

SoftwareGeek 16.06.2011 07:59

@SoftwareGeek: подумайте об этом как о подзапросе (внутреннем запросе), возвращающем поток, который затем читается до тех пор, пока не будет выполнено внешнее предложение WHERE. Каким образом строки могут быть задействованы в этом, полностью зависит от запроса, но оптимизатор обычно очень хорошо справляется с минимизацией этого числа. Использование графического средства просмотра плана выполнения в SQL Server Management Studio (используйте Query / Include Actual Execution Plan) в этом отношении очень полезно.

mdb 16.06.2011 11:51

Меня устраивает такой подход, если он не требует слишком больших затрат с точки зрения процесса.

SoftwareGeek 20.06.2011 07:40

хорошо, что, если вы получаете дубликаты во внутреннем выборе (например, когда у вас есть внутреннее соединение), как вы используете разные, потому что Row_Number отличается, и он не работает

user217648 03.08.2012 16:18

пожалуйста, проверьте эту ссылку -> связь

Fredrick Gauss 22.11.2012 18:11

@mdb - я не вижу доказательств того, что это лучшая производительность. Я вижу только один способ сделать это. Я читал, что использование TOP на самом деле более производительно.

O.O 31.08.2013 21:31

@mdb Есть ли проблемы с производительностью с вышеуказанным запросом, если у нас есть больше записей? проверьте мой вопрос stackoverflow.com/questions/19118532/…

Peru 03.10.2013 13:44

Microsoft добавила новую функцию в SQL 2012, которая делает нумерацию страниц похожей на MySQL. Перейдите по этой ссылке, чтобы узнать, как это сделать. Интересная статья: dbadiaries.com/…

Arash 20.12.2013 17:49

Этот подход не обеспечивает хорошей производительности (по крайней мере, менее 1 секунды), если таблица содержит много записей, т.е.> 3.000.000, и мы выполняем запрос, который имеет "offset / rownum" около 3.000.000. Я просто попробовал с простой таблицей «человек (электронная почта, PK, имя, фамилия)», содержащей 3.000.000 записей, но для получения последних записей с указанным выше запросом требуется 6 секунд. Я использую sql server 2008.

broadband 27.12.2013 17:11

Как я могу использовать его БЕЗ предложения order by в исходном запросе?

delphirules 03.02.2014 20:33

лучше, если мы добавим фразу TOP 20 во внутренний запрос выбора, например: .. SELECT TOP 20 ROW_NUMBER() OVER ...

S.Serpooshan 02.09.2014 15:29

@saeedserpooshan Если вы сделаете это, вы получите только первые 20 результатов и ничего больше. Фактическая разбивка на страницы происходит потому, что внутренний запрос возвращает номера строк, из которых вы можете выбрать.

Ricardo Souza 06.01.2015 00:28

@saeed serpooshan TOP нельзя параметризовать с помощью параметра! at!

Franziee 25.02.2015 22:16

@broadband - если у вас 3 000 000 строк и вы хотите пролистать страницы до конца, вам нужно переосмыслить законность вашего варианта использования (мне трудно поверить, что Пользователь будет сидеть там, просматривая 3 000 000 строк). И если у вас действительно есть законно неизбежное требование, которое позволяет пользователям листать до конца (в чем я сомневаюсь), тогда вам не следует реализовывать разбиение на страницы так наивно, у вас должно быть эффективное решение с курсором (не курсор sql), которое требует некоторых своего рода постоянство эфемерного позиционного состояния, а не размещенное здесь решение для печенья, которое решает 99% случаев.

AaronHS 27.11.2015 05:00

@AaronHS Я согласен с тем, что пользователь не будет выполнять подкачку до последней страницы, но просто хотел указать на проблемы с производительностью, связанные с большим количеством записей. В конце концов, я решил эту проблему с помощью пейджинга с Id. select top 100 from table where id > 35400 и запомнил последний идентификатор и следующую страницу: select top 100 from table where id > 35515. Почему Id 35515, а не 35400. Скажем так, некоторые записи были удалены между 35400 и 35515. При таком подходе я, конечно, получил ту же производительность, если в базе данных 10 или 10 миллионов записей. Столбец Id - это первичный ключ и автоинкремент (идентификатор в ms sql).

broadband 30.11.2015 10:19

этот метод успешен, когда вам нужно глубже отфильтровать результат с ограничением строки, например, когда customerName как '% Chris%', он просто возвращает часть желаемых результатов

Edwin O. 07.01.2016 02:36

если вы можете использовать столбец первичного ключа в ROW_NUMBER () OVER (ORDER BY PrimaryKeyCol), это будет самым быстрым. особенно в таблице с 1 млн строк.

bh_earth0 27.06.2016 09:23

Getting the total number of results and paginating are two different operations.: Не обязательно. См. Здесь: stackoverflow.com/a/14744475/109941

Jim G. 29.03.2017 20:06

Как следует определять столбец x в OVER ( ORDER BY x)? Я предполагаю, что вы, @mdb, выбрали OrderDate, поскольку он обеспечивает упорядочивание. Однако что, если в таблице нет естественного порядка? Спасибо

Kevin Meredith 10.08.2017 16:18

Это решение возвращает все записи независимо от RowNum <20. Что-то не так.

usefulBee 19.03.2018 23:47

Совет по производительности с использованием предложения TOP (предполагается, что следующий код находится внутри хранимой процедуры): SELECT TOP (@PageSize) * FROM (SELECT TOP (@PageSize * @Page) ROW_NUMBER() OVER (ORDER BY OrderDate ) AS RowNum, * FROM Orders WHERE OrderDate >= '1980-01-01' ) AS RowConstrainedResult WHERE RowNum BETWEEN (@Page - 1) * @PageSize + 1 AND @Page * @PageSize

user4266998 05.09.2018 16:10

На http://www.codeproject.com/KB/aspnet/PagingLarge.aspx есть хороший обзор различных методов пейджинга.

Я довольно часто использовал метод ROWCOUNT, в основном с SQL Server 2000 (также будет работать с 2005 и 2008, просто измерьте производительность по сравнению с ROW_NUMBER), он молниеносный, но вам нужно убедиться, что отсортированные столбцы (в основном ) уникальные значения.

Интересно, что в этой статье не упоминается метод поиска, который может выполнять подкачку в постоянное время ... Тем не менее, хорошая статья

Lukas Eder 26.10.2013 21:53

Я использовал следующий пример запроса в своей базе данных SQL 2000, он хорошо работает и для SQL 2005. Возможности, которые он дает, - это динамический порядок с использованием нескольких столбцов. Я вам скажу ... это мощно :)

    ALTER PROCEDURE [dbo].[RE_ListingReports_SelectSummary] 

@CompanyID  int,
@pageNumber     int,
@pageSize   int, 
@sort       varchar(200)
AS

DECLARE @sql nvarchar(4000)
DECLARE @strPageSize nvarchar(20)
DECLARE @strSkippedRows nvarchar(20)
DECLARE @strFields nvarchar(4000)
DECLARE @strFilter nvarchar(4000)
DECLARE @sortBy nvarchar(4000)
DECLARE @strFrom nvarchar(4000)
DECLARE @strID nvarchar(100)

If(@pageNumber < 0)
  SET @pageNumber = 1
SET @strPageSize = CAST(@pageSize AS varchar(20)) 
SET @strSkippedRows = CAST(((@pageNumber - 1) * @pageSize) AS varchar(20))-- For    example if pageNumber is 5  pageSize is 10, then SkippedRows = 40.
SET @strID = 'ListingDbID'
SET @strFields = 'ListingDbID,
ListingID,  
[ExtraRoom]
'
SET @strFrom = ' vwListingSummary '

SET @strFilter = ' WHERE
        CompanyID = ' + CAST(@CompanyID As varchar(20)) 
End
SET @sortBy = ''
if (len(ltrim(rtrim(@sort))) > 0)
SET @sortBy = ' Order By ' + @sort

-- Total Rows Count

SET @sql =  'SELECT Count(' + @strID + ')  FROM ' + @strFROM + @strFilter
EXEC sp_executesql @sql

--// This technique is used in a Single Table pagination
SET @sql = 'SELECT ' + @strFields + ' FROM ' + @strFROM +
    ' WHERE ' + @strID +  ' IN ' + 
   '  (SELECT TOP ' + @strPageSize + ' ' + @strID + ' FROM ' + @strFROM + @strFilter + 
             ' AND  ' + @strID + ' NOT IN ' + '
          (SELECT TOP ' + @strSkippedRows + ' ' + @strID + ' FROM ' + @strFROM + @strFilter + @SortBy + ') ' 
   + @SortBy + ') ' + @SortBy
Print @sql 
EXEC sp_executesql @sql

Лучшая часть - sp_executesql кэширует более поздние вызовы при условии, что вы передаете те же параметры, то есть генерируете тот же текст sql.

Для SQL Server 2000 вы можете моделировать ROW_NUMBER (), используя табличную переменную со столбцом IDENTITY:

DECLARE @pageNo int -- 1 based
DECLARE @pageSize int
SET @pageNo = 51
SET @pageSize = 20

DECLARE @firstRecord int
DECLARE @lastRecord int
SET @firstRecord = (@pageNo - 1) * @pageSize + 1 -- 1001
SET @lastRecord = @firstRecord + @pageSize - 1   -- 1020

DECLARE @orderedKeys TABLE (
  rownum int IDENTITY NOT NULL PRIMARY KEY CLUSTERED,
  TableKey int NOT NULL
)

SET ROWCOUNT @lastRecord
INSERT INTO @orderedKeys (TableKey) SELECT ID FROM Orders WHERE OrderDate >= '1980-01-01' ORDER BY OrderDate

SET ROWCOUNT 0

SELECT t.*
FROM Orders t
  INNER JOIN @orderedKeys o ON o.TableKey = t.ID
WHERE o.rownum >= @firstRecord
ORDER BY o.rownum

Этот подход может быть распространен на таблицы с ключами из нескольких столбцов, и он не влечет за собой накладных расходов на производительность, связанных с использованием OR (которое пропускает использование индекса). Обратной стороной является количество используемого временного пространства, если набор данных очень велик и находится рядом с последней страницей. Я не тестировал работу курсора в этом случае, но может быть лучше.

Обратите внимание, что этот подход можно оптимизировать для первой страницы данных. Кроме того, использовалось ROWCOUNT, поскольку TOP не принимает переменную в SQL Server 2000.

Наконец, был выпущен Microsoft SQL Server 2012, мне очень нравится его простота для разбивки на страницы, вам не нужно использовать сложные запросы, такие как ответы здесь.

Чтобы получить следующие 10 строк, просто выполните этот запрос:

SELECT * FROM TableName ORDER BY id OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;

https://docs.microsoft.com/en-us/sql/t-sql/queries/select-order-by-clause-transact-sql#using-offset-and-fetch-to-limit-the-rows- вернулся

Ключевые моменты, которые следует учитывать при его использовании:

  • ORDER BY является обязательным для использования пункта OFFSET ... FETCH.
  • Пункт OFFSET является обязательным для FETCH. Вы не можете использовать ORDER BY ... FETCH.
  • TOP нельзя комбинировать с OFFSET и FETCH в одном запросе. выражение.

Все еще жду LISTAGG() / GROUP_CONCAT().

Bacon Bits 10.11.2014 23:07

@BaconBits См. Этот ответ, чтобы узнать хитрый способ сделать это с FOR XML: stackoverflow.com/a/273330/429949

Richard Marskell - Drackir 22.07.2015 18:48

@ RichardMarskell-Drackir С FOR XML PATH ('') много проблем. Во-первых, он заменяет управляющие символы XML кодами объектов XML. Надеюсь, в ваших данных нет <, > или &! Во-вторых, FOR XML PATH (''), используемый таким образом, на самом деле является недокументированным синтаксисом. Вы должны указать именованный столбец или альтернативное имя элемента. Ни то, ни другое не указано в документе, что означает, что такое поведение ненадежно. В-третьих, чем больше мы принимаем нарушенный синтаксис FOR XML PATH (''), тем меньше вероятность того, что MS действительно предоставляет функцию настоящийLISTAGG() [ OVER() ], как им было нужно.

Bacon Bits 22.07.2015 19:13

@BaconBits Вы можете обойти проблему кодирования, используя FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)'), как в ответе. В любом случае, я просто показывал обходной путь, который относительно эффективен, пока они не добавят эту функцию в ядро ​​SQL Server.

Richard Marskell - Drackir 22.07.2015 20:39

жаль, что производительность такая плохая mssqlgirl.com/…

Jon 08.09.2015 18:29

@Jon, это связанное сообщение в блоге не является репрезентативным, в том смысле, что оно делает сравнения на основе возврата результата страницы путем поиска значений столбца id.

Noel Abrahams 13.11.2015 14:38

MSDN: ROW_NUMBER (Transact-SQL)

Returns the sequential number of a row within a partition of a result set, starting at 1 for the first row in each partition.

The following example returns rows with numbers 50 to 60 inclusive in the order of the OrderDate.

WITH OrderedOrders AS
(
    SELECT
        ROW_NUMBER() OVER(ORDER BY FirstName DESC) AS RowNumber, 
        FirstName, LastName, ROUND(SalesYTD,2,1) AS "Sales YTD"
    FROM [dbo].[vSalesPerson]
) 
SELECT RowNumber, 
    FirstName, LastName, Sales YTD 
FROM OrderedOrders 
WHERE RowNumber > 50 AND RowNumber < 60;
  RowNumber FirstName    LastName               SalesYTD
  --- -----------  ---------------------- -----------------
  1   Linda        Mitchell               4251368.54
  2   Jae          Pak                    4116871.22
  3   Michael      Blythe                 3763178.17
  4   Jillian      Carson                 3189418.36
  5   Ranjit       Varkey Chudukatil      3121616.32
  6   José         Saraiva                2604540.71
  7   Shu          Ito                    2458535.61
  8   Tsvi         Reiter                 2315185.61
  9   Rachel       Valdez                 1827066.71
  10  Tete         Mensa-Annan            1576562.19
  11  David        Campbell               1573012.93
  12  Garrett      Vargas                 1453719.46
  13  Lynn         Tsoflias               1421810.92
  14  Pamela       Ansman-Wolfe           1352577.13

Невероятно, но ни в одном другом ответе не упоминается способ самый быстрый для разбивки на страницы во всех версиях SQL Server. Смещение может быть очень медленным для больших номеров страниц, как и протестировано здесь. Существует совершенно другой, гораздо более быстрый способ разбивки на страницы в SQL. Это часто называется «методом поиска» или «разбиением на страницы по набору клавиш», как описано в это сообщение в блоге здесь.

SELECT TOP 10 first_name, last_name, score, COUNT(*) OVER()
FROM players
WHERE (score < @previousScore)
   OR (score = @previousScore AND player_id < @previousPlayerId)
ORDER BY score DESC, player_id DESC

Предикат поиска

Значения @previousScore и @previousPlayerId являются соответствующими значениями последней записи с предыдущей страницы. Это позволяет вам перейти на «следующую» страницу. Если направление ORDER BY - ASC, просто используйте вместо него >.

С помощью описанного выше метода вы не можете сразу перейти на страницу 4, не загрузив сначала предыдущие 40 записей. Но зачастую вы все равно не хотите прыгать так далеко. Вместо этого вы получаете гораздо более быстрый запрос, который может извлекать данные за постоянное время, в зависимости от вашей индексации. Кроме того, ваши страницы остаются «стабильными» независимо от того, изменяются ли базовые данные (например, на странице 1, пока вы находитесь на странице 4).

Это лучший способ реализовать разбиение на страницы, например, при ленивой загрузке большего количества данных в веб-приложениях.

Обратите внимание, что «метод поиска» также называется пагинация клавиатуры.

Всего записей до разбивки на страницы

Функция окна COUNT(*) OVER() поможет вам подсчитать общее количество записей «до разбивки на страницы». Если вы используете SQL Server 2000, вам придется прибегнуть к двум запросам для COUNT(*).

А как насчет SQl SERVER 2012 LIMIT FETCH / OFFSET?

user960567 16.04.2014 16:34

@ user960567: С точки зрения производительности пейджинг с помощью набора ключей всегда будет превосходить пейджинг со смещением, независимо от того, реализуете ли вы пейджинг со смещением с помощью стандарта SQL OFFSET .. FETCH или с помощью предыдущих приемов ROW_NUMBER().

Lukas Eder 16.04.2014 18:31

Лукас, из моих тестов WITH C AS(SELECT TOP(@rowsPerPage * @pageNum), ResultNum = ROW_NUMBER() OVER (ORDER BY id)...) SELECT * FROM C WHERE ResultNum > ((@pageNum - 1) * @rowsPerPage) был самым быстрым. sqlserverplanet.com/optimization/…

user960567 16.04.2014 19:55

За какое значение @pageNum? Обязательно прочтите вся эта статья. Вы увидите, что подкачка со смещением не превзойдет подкачку с помощью набора клавиш, даже если она может появляться быстро для малых смещений.

Lukas Eder 16.04.2014 20:12

до тысяч номеров страниц

user960567 16.04.2014 20:16

Я попробовал сначала, что заняло до 3 секунд. WITH C AS занимает всего 1 мс.

user960567 16.04.2014 20:17

Что ж, у вас есть соответствующие индексы, установленные для разбиения по страницам набора ключей, не так ли? :-)

Lukas Eder 16.04.2014 20:27

Я так быстро не добавил индекса в свою таблицу

user960567 16.04.2014 20:55

У меня есть три проблемы с методом поиска. [1] Пользователь не может перейти на страницу. [2] он предполагает последовательные ключи, т.е. если кто-то удалит около 3 строк, тогда я получу страницу из 7 элементов вместо 10. RowNumber дает мне согласованные 10 элементов на страницу. [3] он не работает с существующими сетками, которые предполагают pagenumber и pagesize.

Rebecca 29.07.2015 12:55

@Junto: [1] правильно, но это не всегда нужно. Вы по-прежнему можете «имитировать» переход на следующие несколько страниц, сделав фактический номер страницы больше, чем тот, который отображается пользователю. [2] Этого не должно происходить, возможно, у вас нет уникальных критериев сортировки? [3] Верно. Но что касается вопроса ОП, это не было требованием ..

Lukas Eder 29.07.2015 12:58

@Junto: пейджинг с клавиатуры подходит не для всех случаев. Это определенно не для сетей данных. Но он идеально подходит для таких сценариев, как бесконечная прокрутка страницы ленты Facebook. Не имеет значения, добавляются ли новые сообщения вверху, ваши последующие сообщения в ленте будут правильно добавлены внизу, пока вы прокручиваете страницу вниз. Идеальный пример использования для этого ... Такое много было бы намного сложнее реализовать, используя ограничение / выборку смещения, используя только числа.

Robert Koritnik 31.07.2015 03:21

Я должен согласиться с Хунто. Этот метод полностью исключает клиента с довольно стандартным пользовательским интерфейсом разбиения на страницы «Предыдущий 1 2 3 (4) 5 6 Далее», где пользователи могут перейти вперед. По моему опыту, это не совсем крайний случай ...

AaronHS 27.11.2015 09:10

Это ключевая функция, которую я искал: COUNT(*) OVER()

Jaider 04.04.2017 21:33

Статья о пагинации клавиатуры здесь

Stphane 09.08.2017 19:30

@Stphane В этом нет ничего плохого, но ИМХО совершенно бесполезная статья (читал раньше). Проблема в том, что он не объясняет, как делать полезные, реальные запросы (то есть с фильтрацией записей). Это выполнимо с помощью набора ключей, но болезненно, как в одном из ответов выше. Скорость падает. Не совсем беспроигрышная ситуация, как это часто кажется.

Oak_3260548 30.07.2020 12:13

@ Oak_3260548: Используйте jOOQ, чтобы получить доступ к простому синтаксису для безболезненного paginatino набора ключей.

Lukas Eder 04.08.2020 15:46

@LukasEder Спасибо, я посмотрю

Oak_3260548 04.08.2020 20:30

   CREATE view vw_sppb_part_listsource as 
    select row_number() over (partition by sppb_part.init_id order by sppb_part.sppb_part_id asc ) as idx, * from (
      select 
          part.SPPB_PART_ID
          , 0 as is_rev
          , part.part_number 
          , part.init_id 
      from t_sppb_init_part part 
      left join t_sppb_init_partrev prev on ( part.SPPB_PART_ID = prev.SPPB_PART_ID )
      where prev.SPPB_PART_ID is null 
      union 
      select 
          part.SPPB_PART_ID
          , 1 as is_rev
          , prev.part_number 
          , part.init_id 
      from t_sppb_init_part part 
      inner join t_sppb_init_partrev prev on ( part.SPPB_PART_ID = prev.SPPB_PART_ID )
    ) sppb_part

перезапустит idx, когда дело доходит до другого init_id

Попробуйте такой подход:

SELECT TOP @offset a.*
FROM (select top @limit b.*, COUNT(*) OVER() totalrows 
        from TABLENAME b order by id asc) a
ORDER BY id desc;

С точки зрения использования, следующие варианты кажутся простыми и быстрыми. Просто установите номер страницы.

use AdventureWorks
DECLARE @RowsPerPage INT = 10, @PageNumber INT = 6;
with result as(
SELECT SalesOrderDetailID, SalesOrderID, ProductID,
ROW_NUMBER() OVER (ORDER BY SalesOrderDetailID) AS RowNum
FROM Sales.SalesOrderDetail
where 1=1
)
select SalesOrderDetailID, SalesOrderID, ProductID from result
WHERE result.RowNum BETWEEN ((@PageNumber-1)*@RowsPerPage)+1
AND @RowsPerPage*(@PageNumber)

также без CTE

use AdventureWorks
DECLARE @RowsPerPage INT = 10, @PageNumber INT = 6
SELECT SalesOrderDetailID, SalesOrderID, ProductID
FROM (
SELECT SalesOrderDetailID, SalesOrderID, ProductID,
ROW_NUMBER() OVER (ORDER BY SalesOrderDetailID) AS RowNum
FROM Sales.SalesOrderDetail
where 1=1
 ) AS SOD
WHERE SOD.RowNum BETWEEN ((@PageNumber-1)*@RowsPerPage)+1
AND @RowsPerPage*(@PageNumber)

Что значит где 1 = 1, сэр?

Errol Paleracio 02.12.2019 04:57

Начиная с SQL Server 2012, мы можем использовать предложения OFFSET и FETCH NEXT для разбивки на страницы.

Попробуйте это для SQL Server:

In the SQL Server 2012 a new feature was added in the ORDER BY clause, to query optimization of a set data, making work easier with data paging for anyone who writes in T-SQL as well for the entire Execution Plan in SQL Server.

Below the T-SQL script with the same logic used in the previous example.

--CREATING A PAGING WITH OFFSET and FETCH clauses IN "SQL SERVER 2012"
DECLARE @PageNumber AS INT, @RowspPage AS INT
SET @PageNumber = 2
SET @RowspPage = 10 
SELECT ID_EXAMPLE, NM_EXAMPLE, DT_CREATE
FROM TB_EXAMPLE
ORDER BY ID_EXAMPLE
OFFSET ((@PageNumber - 1) * @RowspPage) ROWS
FETCH NEXT @RowspPage ROWS ONLY;

TechNet: пейджинг запроса с помощью SQL Server

самый точный ответ в этом испытании

Vikrant 19.02.2020 14:14

@Vikrant, только если вы игнорируете всех людей, использующих более раннюю версию, чем 2012

The Fool 19.08.2020 11:42

Для метода ROW_NUMBER, если у вас нет столбца сортировки для использования, вы можете использовать CURRENT_TIMESTAMP следующим образом:

SELECT TOP 20 
    col1,
    col2,
    col3,
    col4
FROM (
    SELECT 
         tbl.col1 AS col1
        ,tbl.col2 AS col2
        ,tbl.col3 AS col3
        ,tbl.col4 AS col4
        ,ROW_NUMBER() OVER (
            ORDER BY CURRENT_TIMESTAMP
            ) AS sort_row
    FROM dbo.MyTable tbl
    ) AS query
WHERE query.sort_row > 10
ORDER BY query.sort_row

Это хорошо сработало для меня при поиске по таблицам размером даже до 700000.

Это выбирает записи с 11 по 30.

Рекомендуется использовать разбиение на страницы для сортировки по уникальному набору столбцов в результирующем наборе, поскольку порядок не следует рассматривать как гарантированный.

Arin Taylor 26.09.2016 00:55

Это выбирает записи с 11 по 30.

Ardalan Shahgholi 05.10.2016 18:07

Это мои решения для разбиения на страницы результата запроса на стороне SQL-сервера. эти подходы различаются между SQL Server 2008 и 2012. Также я добавил концепцию фильтрации и упорядочивания по одному столбцу. Это очень эффективно, когда вы просматриваете страницы, фильтруете и упорядочиваете в своем Gridview.

Перед тестированием вы должны создать одну примерную таблицу и вставить какую-то строку в эту таблицу: (В реальном мире вам нужно изменить предложение Where с учетом полей таблицы, и, возможно, у вас есть соединение и подзапрос в основной части select)

Create Table VLT
(
    ID int IDentity(1,1),
    Name nvarchar(50),
    Tel Varchar(20)
)
GO


Insert INTO VLT
VALUES
    ('NAME' + Convert(varchar(10),@@identity),'FAMIL' + Convert(varchar(10),@@identity))
GO 500000

Во всех этих примерах я хочу запросить 200 строк на страницу, и я получаю строку для страницы с номером 1200.

В SQL Server 2008 вы можете использовать концепцию CTE. По этой причине я написал два типа запросов для SQL Server 2008+.

- SQL Server 2008+

DECLARE @PageNumber Int = 1200
DECLARE @PageSize INT = 200
DECLARE @SortByField int = 1 --The field used for sort by
DECLARE @SortOrder nvarchar(255) = 'ASC' --ASC or DESC
DECLARE @FilterType nvarchar(255) = 'None' --The filter type, as defined on the client side (None/Contain/NotContain/Match/NotMatch/True/False/)
DECLARE @FilterValue nvarchar(255) = '' --The value the user gave for the filter
DECLARE @FilterColumn int = 1 --The column to wich the filter is applied, represents the column number like when we send the information.

SELECT 
  Data.ID,
  Data.Name,
  Data.Tel
FROM
  (  
    SELECT 
      ROW_NUMBER() 
        OVER( ORDER BY 
                CASE WHEN @SortByField = 1 AND @SortOrder = 'ASC'
                      THEN VLT.ID END ASC,
                CASE WHEN @SortByField = 1 AND @SortOrder = 'DESC'
                      THEN VLT.ID END DESC,
                CASE WHEN @SortByField = 2 AND @SortOrder = 'ASC'
                      THEN VLT.Name END ASC,
                CASE WHEN @SortByField = 2 AND @SortOrder = 'DESC'
                      THEN VLT.Name END ASC,
                CASE WHEN @SortByField = 3 AND @SortOrder = 'ASC'
                      THEN VLT.Tel END ASC,
                CASE WHEN @SortByField = 3 AND @SortOrder = 'DESC'
                      THEN VLT.Tel END ASC
         ) AS RowNum
      ,*  
    FROM VLT 
    WHERE
      ( -- We apply the filter logic here
        CASE
          WHEN @FilterType = 'None' THEN 1

          -- Name column filter
          WHEN @FilterType = 'Contain' AND @FilterColumn = 1
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.ID LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'NotContain' AND @FilterColumn = 1
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.ID NOT LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'Match' AND @FilterColumn = 1
            AND VLT.ID = @FilterValue THEN 1
          WHEN @FilterType = 'NotMatch' AND @FilterColumn = 1
            AND VLT.ID <> @FilterValue THEN 1               

          -- Name column filter
          WHEN @FilterType = 'Contain' AND @FilterColumn = 2
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.Name LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'NotContain' AND @FilterColumn = 2
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.Name NOT LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'Match' AND @FilterColumn = 2
            AND VLT.Name = @FilterValue THEN 1
          WHEN @FilterType = 'NotMatch' AND @FilterColumn = 2
            AND VLT.Name <> @FilterValue THEN 1         

         -- Tel column filter   
         WHEN @FilterType = 'Contain' AND @FilterColumn = 3
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.Tel LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'NotContain' AND @FilterColumn = 3
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.Tel NOT LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'Match' AND @FilterColumn = 3
            AND VLT.Tel = @FilterValue THEN 1
          WHEN @FilterType = 'NotMatch' AND @FilterColumn = 3
            AND VLT.Tel <> @FilterValue THEN 1    

        END
      ) = 1   
  ) AS Data
WHERE Data.RowNum > @PageSize * (@PageNumber - 1)
  AND Data.RowNum <= @PageSize * @PageNumber
ORDER BY Data.RowNum

GO

И второе решение с CTE в SQL Server 2008+

DECLARE @PageNumber Int = 1200
DECLARE @PageSize INT = 200
DECLARE @SortByField int = 1 --The field used for sort by
DECLARE @SortOrder nvarchar(255) = 'ASC' --ASC or DESC
DECLARE @FilterType nvarchar(255) = 'None' --The filter type, as defined on the client side (None/Contain/NotContain/Match/NotMatch/True/False/)
DECLARE @FilterValue nvarchar(255) = '' --The value the user gave for the filter
DECLARE @FilterColumn int = 1 --The column to wich the filter is applied, represents the column number like when we send the information.

;WITH
  Data_CTE
  AS
  (  
    SELECT 
      ROW_NUMBER() 
        OVER( ORDER BY 
                CASE WHEN @SortByField = 1 AND @SortOrder = 'ASC'
                      THEN VLT.ID END ASC,
                CASE WHEN @SortByField = 1 AND @SortOrder = 'DESC'
                      THEN VLT.ID END DESC,
                CASE WHEN @SortByField = 2 AND @SortOrder = 'ASC'
                      THEN VLT.Name END ASC,
                CASE WHEN @SortByField = 2 AND @SortOrder = 'DESC'
                      THEN VLT.Name END ASC,
                CASE WHEN @SortByField = 3 AND @SortOrder = 'ASC'
                      THEN VLT.Tel END ASC,
                CASE WHEN @SortByField = 3 AND @SortOrder = 'DESC'
                      THEN VLT.Tel END ASC
         ) AS RowNum
      ,*  
    FROM VLT
    WHERE
      ( -- We apply the filter logic here
        CASE
          WHEN @FilterType = 'None' THEN 1

          -- Name column filter
          WHEN @FilterType = 'Contain' AND @FilterColumn = 1
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.ID LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'NotContain' AND @FilterColumn = 1
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.ID NOT LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'Match' AND @FilterColumn = 1
            AND VLT.ID = @FilterValue THEN 1
          WHEN @FilterType = 'NotMatch' AND @FilterColumn = 1
            AND VLT.ID <> @FilterValue THEN 1               

          -- Name column filter
          WHEN @FilterType = 'Contain' AND @FilterColumn = 2
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.Name LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'NotContain' AND @FilterColumn = 2
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.Name NOT LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'Match' AND @FilterColumn = 2
            AND VLT.Name = @FilterValue THEN 1
          WHEN @FilterType = 'NotMatch' AND @FilterColumn = 2
            AND VLT.Name <> @FilterValue THEN 1         

         -- Tel column filter   
         WHEN @FilterType = 'Contain' AND @FilterColumn = 3
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.Tel LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'NotContain' AND @FilterColumn = 3
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.Tel NOT LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'Match' AND @FilterColumn = 3
            AND VLT.Tel = @FilterValue THEN 1
          WHEN @FilterType = 'NotMatch' AND @FilterColumn = 3
            AND VLT.Tel <> @FilterValue THEN 1    

        END
      ) = 1     
  )

SELECT 
  Data.ID,
  Data.Name,
  Data.Tel
FROM Data_CTE AS Data
WHERE Data.RowNum > @PageSize * (@PageNumber - 1)
  AND Data.RowNum <= @PageSize * @PageNumber
ORDER BY Data.RowNum

- SQL Server 2012+

DECLARE @PageNumber Int = 1200
DECLARE @PageSize INT = 200
DECLARE @SortByField int = 1 --The field used for sort by
DECLARE @SortOrder nvarchar(255) = 'ASC' --ASC or DESC
DECLARE @FilterType nvarchar(255) = 'None' --The filter type, as defined on the client side (None/Contain/NotContain/Match/NotMatch/True/False/)
DECLARE @FilterValue nvarchar(255) = '' --The value the user gave for the filter
DECLARE @FilterColumn int = 1 --The column to wich the filter is applied, represents the column number like when we send the information.

;WITH
  Data_CTE
  AS
  (  
    SELECT 
      *  
    FROM VLT
    WHERE
      ( -- We apply the filter logic here
        CASE
          WHEN @FilterType = 'None' THEN 1

          -- Name column filter
          WHEN @FilterType = 'Contain' AND @FilterColumn = 1
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.ID LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'NotContain' AND @FilterColumn = 1
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.ID NOT LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'Match' AND @FilterColumn = 1
            AND VLT.ID = @FilterValue THEN 1
          WHEN @FilterType = 'NotMatch' AND @FilterColumn = 1
            AND VLT.ID <> @FilterValue THEN 1               

          -- Name column filter
          WHEN @FilterType = 'Contain' AND @FilterColumn = 2
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.Name LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'NotContain' AND @FilterColumn = 2
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.Name NOT LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'Match' AND @FilterColumn = 2
            AND VLT.Name = @FilterValue THEN 1
          WHEN @FilterType = 'NotMatch' AND @FilterColumn = 2
            AND VLT.Name <> @FilterValue THEN 1         

         -- Tel column filter   
         WHEN @FilterType = 'Contain' AND @FilterColumn = 3
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.Tel LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'NotContain' AND @FilterColumn = 3
            AND ( -- In this case, when the filter value is empty, we want to show everything.
                VLT.Tel NOT LIKE '%' + @FilterValue + '%'
               OR
                @FilterValue = ''
               ) THEN 1
          WHEN @FilterType = 'Match' AND @FilterColumn = 3
            AND VLT.Tel = @FilterValue THEN 1
          WHEN @FilterType = 'NotMatch' AND @FilterColumn = 3
            AND VLT.Tel <> @FilterValue THEN 1    

        END
      ) = 1         
  )

SELECT 
  Data.ID,
  Data.Name,
  Data.Tel
FROM Data_CTE AS Data
ORDER BY 
    CASE WHEN @SortByField = 1 AND @SortOrder = 'ASC'
        THEN Data.ID END ASC,
    CASE WHEN @SortByField = 1 AND @SortOrder = 'DESC'
        THEN Data.ID END DESC,
    CASE WHEN @SortByField = 2 AND @SortOrder = 'ASC'
        THEN Data.Name END ASC,
    CASE WHEN @SortByField = 2 AND @SortOrder = 'DESC'
        THEN Data.Name END ASC,
    CASE WHEN @SortByField = 3 AND @SortOrder = 'ASC'
        THEN Data.Tel END ASC,
    CASE WHEN @SortByField = 3 AND @SortOrder = 'DESC'
        THEN Data.Tel END ASC
OFFSET @PageSize * (@PageNumber - 1) ROWS FETCH NEXT @PageSize ROWS ONLY;

Лучший способ подкачки в sql server 2012 - использовать смещение и выборку далее в хранимой процедуре. СМЕЩЕНИЕ Ключевое слово - Если мы используем смещение с предложением order by, тогда запрос пропустит количество записей, которое мы указали в OFFSET n Rows.

ПОЛУЧИТЬ СЛЕДУЮЩИЕ ключевые слова - когда мы используем Fetch Next только с предложением order by, он возвращает количество строк, которые вы хотите отображать в разбивке по страницам, без смещения, тогда SQL выдаст ошибку. вот пример, приведенный ниже.

create procedure sp_paging
(
 @pageno as int,
 @records as int
)
as
begin
declare @offsetcount as int
set @offsetcount=(@pageno-1)*@records
select id,bs,variable from salary order by id offset @offsetcount rows fetch Next @records rows only
end

вы можете выполнить это следующим образом.

exec sp_paging 2,3

create PROCEDURE SP_Company_List (@pagesize int = -1 ,@pageindex int= 0   ) > AS BEGIN  SET NOCOUNT ON;


    select  Id , NameEn     from Company  ORDER by Id ASC  
OFFSET (@pageindex-1 )* @pagesize   ROWS FETCH NEXt @pagesize ROWS ONLY END  GO

DECLARE   @return_value int

EXEC  @return_value = [dbo].[SP_Company_List]         @pagesize = 1 ,         > @pageindex = 2

SELECT    'Return Value' = @return_value

GO

Этот бит дает вам возможность разбивать на страницы с помощью SQL Server и более новых версий MySQL и содержит общее количество строк в каждой строке. Использует ваш первичный ключ для подсчета количества уникальных строк.

WITH T AS
(  
  SELECT TABLE_ID, ROW_NUMBER() OVER (ORDER BY TABLE_ID) AS RN
  , (SELECT COUNT(TABLE_ID) FROM TABLE) AS TOTAL 
  FROM TABLE (NOLOCK)
)

SELECT T2.FIELD1, T2.FIELD2, T2.FIELD3, T.TOTAL 
FROM TABLE T2 (NOLOCK)
INNER JOIN T ON T2.TABLE_ID=T.TABLE_ID
WHERE T.RN >= 100
AND T.RN < 200

Не могли бы вы оставить какие-либо комментарии, объясняющие, что делает ваш код?

Doug F 27.03.2019 20:01

Это дубликат старого вопроса SO 2012 года: эффективный способ реализации пейджинга

FROM [TableX] ORDER BY [FieldX] OFFSET 500 ROWS FETCH NEXT 100 ROWS ONLY

Здесь тема обсуждается более подробно и с альтернативными подходами.

С 2012 года мы можем использовать OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY

Другие вопросы по теме