Как получить N строк, начиная с строки M из отсортированной таблицы в T-SQL
существует простой способ получить верхние 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 строки, начиная с 3d строки, т. е. 3d и 4-й строки возвращаются.
17 ответов:
обновление если вы используете 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
проблема с предложениями в этой теме и в других местах в Интернете заключается в том, что все предлагаемые решения выполняются в линейном времени относительно количества записей. Например, рассмотрим следующий запрос.
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 Профилировщиком и СУБД был SQL Server 2008 R2.
Это решение работает для любого случая, когда вы сортируете таблицу по индексу. Индекс не обязательно должен быть кластеризованным или простым. В моем случае индекс состоял из трех полей: varchar(50) asc, varchar(15) asc, numeric(19,0) asc. То, что производительность была отличной, несмотря на громоздкий индекс, еще раз демонстрирует, что этот подход работает.
однако очень важно, чтобы предложение order by в Оконная функция Row_Number соответствует индексу. В противном случае производительность снизится до того же уровня, что и в первом примере.
этот подход по-прежнему требует линейной операции для создания непостоянной кросс-таблицы, но поскольку это всего лишь индекс с добавленным номером строки, это происходит очень быстро. В моем случае это заняло 0.347 секунды, но в моем случае было varchars, которые должны быть скопированы. Один числовой индекс займет гораздо меньше времени.
для всех практических цель, эта конструкция уменьшает масштабирование серверной подкачки от линейной операции до логарифмической операции, позволяющей масштабировать большие таблицы. Ниже приведено полное решение.
-- 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;
на 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пропустить.
Если вы хотите выбрать 100 записей из 25-й записи:
select TOP 100 * from TableName where PrimaryKeyField NOT IN(Select TOP 24 PrimaryKeyField from TableName);
некрасиво, халтурно, но должно работать:
select top(M + N - 1) * from TableName except select top(N - 1) * from TableName
вероятно, хорошо для небольших результатов, работает во всех версиях 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
-- *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 ;
@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 SQL подкачки.
этот поток довольно старый, но в настоящее время вы можете сделать это: гораздо чище, имхо
SELECT * FROM Sales.SalesOrderDetail ORDER BY SalesOrderDetailID OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY; GO
Ниже приведен простой запрос перечислит N строк из M+1-й строки таблицы. Замените M и N предпочтительными номерами.
Select Top N B.PrimaryKeyColumn from (SELECT top M PrimaryKeyColumn FROM MyTable ) A right outer join MyTable B on A.PrimaryKeyColumn = B.PrimaryKeyColumn where A.PrimaryKeyColumn IS NULLпожалуйста, дайте мне знать, полезно ли это для вашей ситуации.
и это, как вы можете достичь той же цели на таблицах без первичный ключ:
select * from ( select row_number() over(order by (select 0)) rowNum,* from your_table ) tmp where tmp.rowNum between 20 and 30 -- any numbers you need
Я прочитал все ответы здесь и, наконец, придумал полезное решение, которое просто. Проблемы с производительностью возникают из оператора BETWEEN, а не из-за генерации самих номеров строк. Поэтому я использовал алгоритм для динамической подкачки, передавая номер страницы и количество записей.
проходы-это не начальная строка и количество строк, а скорее "строки на страницу (500)" и "номер страницы (4)", которые будут строками 1501 - 2000. Эти значения могут быть заменены переменные хранимой процедуры, поэтому вы не заблокированы в использовании определенного объема подкачки.
select * from ( select (((ROW_NUMBER() OVER(ORDER BY MyField) - 1) / 500) + 1) AS PageNum , * from MyTable ) as PagedTable where PageNum = 4;
для этого в 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
найти id для строки N Затем получите верхние M строк, которые имеют идентификатор больше или равен этому
declare @N as int set @N = 2 declare @M as int set @M = 3 declare @Nid as int set @Nid = max(id) from (select top @N * from MyTable order by id) select top @M * from MyTable where id >= @Nid order by idчто-то подобное ... но я сделал некоторые предположения здесь (например, вы хотите заказать по id)
существует довольно прямой метод для
T-SQL, хотя я не уверен, что это prestanda-эффективно, если вы пропускаете большое количество строк.SELECT TOP numberYouWantToTake [yourColumns...] FROM yourTable WHERE yourIDColumn NOT IN ( SELECT TOP numberYouWantToSkip yourIDColumn FROM yourTable ORDER BY yourOrderColumn ) ORDER BY yourOrderColumnЕсли вы используете .Net, вы можете использовать следующее, например, IEnumerable с вашими результатами данных:
IEnumerable<yourDataType> yourSelectedData = yourDataInAnIEnumerable.Skip(nubmerYouWantToSkip).Take(numberYouWantToTake);Это зад, что вы получаете все данные из хранилища данных.
почему бы не сделать два запроса:
select top(M+N-1) * from table into temp tmp_final with no log; select top(N-1) * from tmp_final order by id desc;
SELECT * FROM ( SELECT Row_Number() Over (Order by (Select 1)) as RawKey, * FROM [Alzh].[dbo].[DM_THD_TRANS_FY14] ) AS foo WHERE RawKey between 17210400 and 17210500
Comments