Как получить N строк, начиная со строки M, из отсортированной таблицы в T-SQL - PullRequest
63 голосов
/ 17 апреля 2009

Существует простой способ получить верхние N строк из любой таблицы:

SELECT TOP 10 * FROM MyTable ORDER BY MyColumn

Есть ли эффективный способ запроса M строк, начиная со строки N

Например,

Id Value
1    a
2    b
3    c
4    d
5    e
6    f

И запрос как этот

SELECT [3,2] * FROM MyTable ORDER BY MyColumn /* hypothetical syntax */

запрашивает 2 строки, начиная с 3-й строки, т. Е. Возвращаются 3-я и 4-я строки.

Ответы [ 17 ]

89 голосов
/ 17 апреля 2009

ОБНОВЛЕНИЕ Если вы используете SQL 2012, новый синтаксис был добавлен, чтобы сделать это действительно простым. См. Реализация функциональности подкачки (пропустить / взять) с этим запросом

Полагаю, самым элегантным является использование функции ROW_NUMBER (доступно из MS SQL Server 2005):

WITH NumberedMyTable AS
(
    SELECT
        Id,
        Value,
        ROW_NUMBER() OVER (ORDER BY Id) AS RowNumber
    FROM
        MyTable
)
SELECT
    Id,
    Value
FROM
    NumberedMyTable
WHERE
    RowNumber BETWEEN @From AND @To
16 голосов
/ 25 сентября 2014

Проблема с предложениями в этой теме и в других разделах сети состоит в том, что все предлагаемые решения выполняются за линейное время по отношению к количеству записей. Например, рассмотрим запрос, подобный следующему.

select *
from
(
    select
        Row_Number() over (order by ClusteredIndexField) as RowNumber,
        *
    from MyTable
) as PagedTable
where RowNumber between @LowestRowNumber and @HighestRowNumber;

При получении страницы 1 запрос занимает 0,577 секунды. Однако при получении страницы 15 619 этот же запрос занимает более 2 минут 55 секунд.

Мы можем значительно улучшить это, создав номер записи, индексную перекрестную таблицу, как показано в следующем запросе. Кросс-таблица называется PagedTable и является непостоянной.

select *
from
(
    select
        Row_Number() over (order by Field1 asc, Field2 asc, Field3 asc) as RowNumber,
        ClusteredIndexField
    from MyTable
) as PagedTable
left join MyTable on MyTable.ClusteredIndexField = PagedTable.ClusteredIndexField
where RowNumber between @LowestRowNumber and @HighestRowNumber;

Как и в предыдущем примере, я проверил это на очень широкой таблице с 780 928 записями. Я использовал размер страницы 50, что привело к 15 619 страницам.

Общее время, затрачиваемое на страницу 1 (первую страницу), составляет 0,413 секунды. Общее время, затрачиваемое на страницу 15 619 (последнюю страницу), составляет 0,987 секунды, что в два раза больше, чем на странице 1. Это время измерялось с помощью SQL Server Profiler, а СУБД была SQL Server 2008 R2.

Это решение подходит для любого случая, когда вы сортируете свою таблицу по индексу. Индекс не должен быть кластеризованным или простым. В моем случае индекс состоял из трех полей: varchar (50) asc, varchar (15) asc, числовой (19,0) asc. То, что производительность была превосходной, несмотря на громоздкий индекс, еще раз демонстрирует, что этот подход работает.

Однако очень важно, чтобы предложение order by в оконной функции Row_Number соответствовало индексу. В противном случае производительность снизится до того же уровня, что и в первом примере.

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

Для всех практических целей этот дизайн уменьшает масштабирование подкачки на стороне сервера от линейной операции до логарифмической операции, позволяющей масштабировать большие таблицы. Ниже приведено полное решение.

-- For a sproc, make these your input parameters
declare
    @PageSize int = 50,
    @Page int = 15619;

-- For a sproc, make these your output parameters
declare @RecordCount int = (select count(*) from MyTable);
declare @PageCount int = ceiling(convert(float, @RecordCount) / @PageSize);
declare @Offset int = (@Page - 1) * @PageSize;
declare @LowestRowNumber int = @Offset;
declare @HighestRowNumber int = @Offset + @PageSize - 1;

select
    @RecordCount as RecordCount,
    @PageCount as PageCount,
    @Offset as Offset,
    @LowestRowNumber as LowestRowNumber,
    @HighestRowNumber as HighestRowNumber;

select *
from
(
    select
        Row_Number() over (order by Field1 asc, Field2 asc, Field3 asc) as RowNumber,
        ClusteredIndexField
    from MyTable
) as PagedTable
left join MyTable on MyTable.ClusteredIndexField = PagedTable.ClusteredIndexField
where RowNumber between @LowestRowNumber and @HighestRowNumber;
10 голосов
/ 16 сентября 2015

В SQL 2012 вы можете использовать OFFSET и FETCH:

SELECT *
FROM MyTable
ORDER BY MyColumn
OFFSET @N ROWS
FETCH NEXT @M ROWS ONLY;


Я лично предпочитаю:
DECLARE @CurrentSetNumber int = 0;
DECLARE @NumRowsInSet int = 2;

SELECT *
FROM MyTable
ORDER BY MyColumn
OFFSET @NumRowsInSet * @CurrentSetNumber ROWS
FETCH NEXT @NumRowsInSet ROWS ONLY;

SET @CurrentSetNumber = @CurrentSetNumber + 1;

, где @NumRowsInSet - это количество строк, которое вы хотите вернуть, а @CurrentSetNumber - это число @NumRowsInSet, которое нужно пропустить.

8 голосов
/ 02 марта 2011

Если вы хотите выбрать 100 записей из 25-й записи:

select TOP 100 * from TableName
where PrimaryKeyField 
   NOT IN(Select TOP 24 PrimaryKeyField from TableName);
5 голосов
/ 17 апреля 2009

Гадкий, хакерский, но должен работать:

select top(M + N - 1) * from TableName
except
select top(N - 1) * from TableName
3 голосов
/ 25 октября 2012

Вероятно, хорошо для небольших результатов, работает во всех версиях TSQL:

SELECT 
        * 
FROM
     (SELECT TOP (N) * 
      FROM 
            (SELECT TOP (M + N - 1) 
             FROM 
                   Table
             ORDER BY 
                      MyColumn) qasc
      ORDER BY 
               MyColumn DESC) qdesc
 ORDER BY 
         MyColumn
3 голосов
/ 01 мая 2013
        -- *some* implementations may support this syntax (mysql?)
SELECT Id,Value
FROM xxx
ORDER BY Id
LIMIT 2 , 0
   ;

        -- Separate LIMIT, OFFSET
SELECT Id,Value
FROM xxx
ORDER BY Id
LIMIT 2 OFFSET 2
   ;

        -- SQL-2008 syntax
SELECT Id,Value
FROM xxx
ORDER BY Id
OFFSET 4
FETCH NEXT 2 ROWS ONLY
  ;
2 голосов
/ 19 сентября 2016

Чтобы сделать это в SQL Server, необходимо упорядочить запрос по столбцу, чтобы можно было указать нужные строки.

Вы не можете использовать ключевое слово "TOP" при этом, вы должны использовать смещение N строк для выборки следующих M строк.

Пример:

select * from table order by [some_column] 
offset 10 rows
FETCH NEXT 10 rows only

Вы можете узнать больше здесь: https://technet.microsoft.com/pt-br/library/gg699618%28v=sql.110%29.aspx

2 голосов
/ 14 апреля 2016

Эта тема довольно старая, но в настоящее время вы можете сделать это: намного чище imho

SELECT *
FROM Sales.SalesOrderDetail
ORDER BY SalesOrderDetailID
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY;
GO

источник: http://blog.sqlauthority.com/2013/12/30/sql-server-mysql-limit-and-offset-skip-and-return-only-next-few-rows-paging-solution/

2 голосов
/ 17 апреля 2009
@start = 3
@records = 2

Select ID, Value 
From
(SELECT ROW_NUMBER() OVER(ORDER BY ID) AS RowNum, ID,Value 
From MyTable) as sub
Where sub.RowNum between @start and @start+@records

Это один из способов. Есть много других, если вы Google Paging SQL.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...