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

Posted

技术标签:

【中文标题】如何从T-SQL中的排序表中从第M行开始获取N行【英文标题】:How to get N rows starting from row M from sorted table in T-SQL 【发布时间】:2010-10-19 23:32:36 【问题描述】:

有一种简单的方法可以从任何表中获取前 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 行。

【问题讨论】:

另见***.com/questions/216673/… 您运行的是什么版本的 SQL?这在 SQL2005+ 中要容易得多 实际上......当你把“高效”这个小词放在那里......没有。即使是支持LIMIT N, Mmysql,与第一页相比,在大表的最后“页面”上也会慢得可怕。唯一接近高效的是,如果您可以使用 ID 或其他一些索引将查询预先限制为行的子集。也许在预查询中将页面映射到 ID 或时间戳范围会有所帮助。 (所有页面或更大的页面组一次计算,而不是每次翻页) 【参考方案1】:

丑陋,骇人听闻,但应该可以:

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

【讨论】:

是的,我想到了。我认为这不会有效地处理大数据集。但我也没有其他想法。 -谢谢。当表没有索引并且无法更改表的元数据时,这很有用。【参考方案2】:

更新如果您使用的是 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

【讨论】:

如果您在 WITH 之前有 select 语句,则必须用分号结束前一个语句(有效地将 WITH 替换为 ;WITH 这可能是相关的,并且是 SQL Server 2012 的最新版本:T-SQL Skip Take Stored Procedure。 没有为我返回任何记录【参考方案3】:
@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 Paging,还有很多其他的。

【讨论】:

【参考方案4】:

查找第 N 行的 id 然后获取id大于等于那个的前M行

将@N 声明为 int 设置@N = 2 将 @M 声明为 int 设置@M = 3 将@Nid 声明为 int 设置@Nid = max(id) 从 (选择顶部@N * 从我的表 按 id 排序) 选择顶部@M * 从我的表 其中 id >= @Nid 按编号订购

类似的东西......但我在这里做了一些假设(例如,你想按 id 订购)

【讨论】:

【参考方案5】:

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

这意味着您从数据存储中获取所有数据。

【讨论】:

【参考方案6】:

为什么不做两个查询:

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;

【讨论】:

【参考方案7】:

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

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

【讨论】:

如果我有非整数主键,或者表中有复合主键,这将失败。 根据返回的数据量,这可能不是大型查询的最佳解决方案 出于性能原因,不建议在生产环境中这样做【参考方案8】:

以下是简单查询将列出表的第 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

请让我知道这对您的情况是否有用。

【讨论】:

【参考方案9】:

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

【讨论】:

当表格的行数少于 M+N-1 时,另请参阅 question linked by @BillKarwin, above 的轻微编辑。【参考方案10】:

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

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

【讨论】:

【参考方案11】:
        -- *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
  ;

【讨论】:

OFFSET/LIMIT 已添加到 SQL Server 2012,因此在 2008 年将无法使用。 OP 中没有提到任何名为“server 2012”的内容。请将此视为仅限 SQL 的回复。 抱歉,我刚刚在该代码部分(即SQL-2008 syntax)上方取消了您的评论。我以为您指的是 SQL Server 2008。【参考方案12】:
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

【讨论】:

【参考方案13】:

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

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;

【讨论】:

附录...我已经使用具有 13,200,833 条记录的表测试了这种方法,并使用了 50 的页面大小,从而产生了 264,017 页。访问第 1 页需要 19 秒。访问最后一页需要 1 分 11 秒。虽然这仍然是对其他方法的巨大改进,但对于实时响应来说还不够。为了克服这个性能问题,在内部选择中添加一个“top N”。这减少了获取任何页面所需的时间,但代价是将总结果限制为 N。您还可以将任何过滤参数添加到内部选择的 where 子句中。 如何在内部选择中放置“前 N 个”。我对我的一个查询进行了尝试,但它只会限制您的交叉表,并且不会返回“前 N”之外的任何范围。我是不是误会了什么?【参考方案14】:

我在这里阅读了所有回复,最后想出了一个简单的可用解决方案。性能问题来自 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;

【讨论】:

【参考方案15】:

SQL 2012 中,您可以使用 OFFSETFETCH

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 数。

【讨论】:

【参考方案16】:

这个帖子很老了,但目前你可以这样做: 更清洁恕我直言

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/

【讨论】:

【参考方案17】:

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

这样做时不能使用“TOP”关键字,必须使用offset N rows fetch next M rows。

例子:

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

【讨论】:

以上是关于如何从T-SQL中的排序表中从第M行开始获取N行的主要内容,如果未能解决你的问题,请参考以下文章

如何从 T-SQL 中的表中选择前 N 行?

经典排序算法 ——直接插入排序

有一字符串,包含n个字符。写一函数,将此字符串中从第m个字符开始的全部字符复制成为另一个字符串

C语言 有一个字符串,包含n个字符。将此字符串中从第m个字符开始的全部字符复制成为另一个字符串

C语言 有一个字符串,包含n个字符。将此字符串中从第m个字符开始的全部字符复制成为另一个字符串

设计一个算法,删除一个顺序表中从第i个元素开始的k个元素。