如何从T-SQL中的排序表中的行M开始获取N行

时间:2009-04-16 21:48:49

标签: sql tsql

有一种简单的方法可以从任何表中获得前N行:

SELECT TOP 10 * FROM MyTable ORDER BY MyColumn

是否有任何有效的方法可以从第N行开始查询M行

例如,

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

并像这样查询

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

从3d行开始查询2行,即返回3d和第4行。

17 个答案:

答案 0 :(得分:88)

  

更新如果您使用的是SQL 2012,则会添加新语法以使其变得非常简单。见Implement paging (skip / take) functionality with this query

我想最优雅的是使用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

答案 1 :(得分:16)

此主题和网络上其他地方的建议存在的问题是,所有提议的解决方案都在相对于记录数的线性时间内运行。例如,请考虑以下查询。

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测量的,DBMS是SQL Server 2008 R2。

当您按索引对表进行排序时,此解决方案适用于任何情况。索引不必是聚簇的或简单的。在我的例子中,索引由三个字段组成:varchar(50)asc,varchar(15)asc,numeric(19,0)asc。虽然繁琐的索引表现非常出色,但这进一步说明了这种方法的有效性。

但是,Row_Number窗口函数中的order by子句对应于索引是至关重要的。否则,性能将降低到与第一个示例相同的水平。

这种方法仍然需要线性操作来生成非持久性交叉表,但由于这只是一个添加了行号的索引,因此它很快就会发生。在我的情况下花了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;

答案 2 :(得分:10)

SQL 2012 中,您可以使用$ ./echoserver.pl Spawned PID 20953 Reaped PID 20953 bye 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的数量。

答案 3 :(得分:8)

如果要从第25条记录中选择100条记录:

select TOP 100 * from TableName
where PrimaryKeyField 
   NOT IN(Select TOP 24 PrimaryKeyField from TableName);

答案 4 :(得分:5)

丑陋,hackish,但应该工作:

select top(M + N - 1) * from TableName
except
select top(N - 1) * from TableName

答案 5 :(得分:3)

对于小结果可能有用,适用于所有版本的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

答案 6 :(得分:3)

        -- *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
  ;

答案 7 :(得分:2)

@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

这是一种方式。如果你谷歌SQL分页,还有很多其他的。

答案 8 :(得分:2)

这个帖子很老了,但是目前你可以这样做: 更清洁的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/

答案 9 :(得分:2)

为了在SQL Server中执行此操作,您必须按列排序查询,以便指定所需的行。

你不能使用&#34; TOP&#34;关键字在执行此操作时,必须使用偏移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

答案 10 :(得分:1)

以下是简单查询将从表的第M + 1行列出N行。用您喜欢的号码替换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

请告诉我这是否对您的情况有用。

答案 11 :(得分:1)

这就是如何在没有主键的表上实现相同的目标:

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

答案 12 :(得分:1)

我在这里阅读了所有的回复,最后提出了一个简单易用的解决方案。性能问题来自BETWEEN语句,而不是行号本身的生成。所以我使用算法通过传递页码和记录数来进行动态分页。

传递不是开始行和行数,而是&#34;每页行数(500)&#34;和&#34;页码(4)&#34;这些值可以是行1501 - 2000.这些值可以由存储过程变量替换,因此您不会被锁定使用特定的分页量。

select * from (
    select
        (((ROW_NUMBER() OVER(ORDER BY MyField) - 1) / 500) + 1) AS PageNum
        , *
    from MyTable
) as PagedTable
where PageNum = 4;

答案 13 :(得分:0)

为什么不进行两次查询:

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;

答案 14 :(得分:0)

T-SQL有一个非常简单的方法,但如果你跳过大量的行,我不确定它是否是预先有效的。

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);

这样做的背后是你从数据存储中获取所有数据。

答案 15 :(得分:0)

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

答案 16 :(得分:0)

查找第N行的ID 然后获取id大于或等于

的前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订购)