SQL Server 中的行偏移量

Posted

技术标签:

【中文标题】SQL Server 中的行偏移量【英文标题】:Row Offset in SQL Server 【发布时间】:2010-09-16 07:46:38 【问题描述】:

SQL Server 中是否有任何方法可以从给定偏移量开始获取结果?例如,在另一种类型的 SQL 数据库中,可以这样做:

SELECT * FROM MyTable OFFSET 50 LIMIT 25

获得结果 51-75。 SQL Server 中似乎不存在此构造。

如何在不加载所有我不关心的行的情况下完成此操作?谢谢!

【问题讨论】:

对于喜欢官方文档而不是视频的人:docs.microsoft.com/en-us/sql/t-sql/queries/… 【参考方案1】:

我会避免使用SELECT *。指定您实际需要的列,即使它可能是所有列。

SQL Server 2005+

SELECT col1, col2 
FROM (
    SELECT col1, col2, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum
    FROM MyTable
) AS MyDerivedTable
WHERE MyDerivedTable.RowNum BETWEEN @startRow AND @endRow

SQL Server 2000

Efficiently Paging Through Large Result Sets in SQL Server 2000

A More Efficient Method for Paging Through Large Result Sets

【讨论】:

即使您选择了所有列,为什么还建议避免使用 SELECT? 我确信他使用了“*”,因为它比“col1, col2, ... colN”更易于键入并且更容易理解。 至于为什么不用它,SELECT *的意思是如果表的结构发生变化,你的查询仍然运行,但是给出不同的结果。如果添加了一个列,这可能很有用(尽管您仍然必须在某处按名称使用它);如果一列被删除或重命名,最好让你的 SQL 明显中断,而不是因为变量未初始化而导致代码行为异常。 选择表的所有数据并剪切?如果有 5000000000 行?为每个查询选择 5000000000 行并剪切?它对服务器的cpu和内存效率不高。 请注意,2012+ 的实现方式更好。查看 +Martin Smith 的回答【参考方案2】:

如果您将按顺序处理所有页面,那么只需记住在上一页上看到的最后一个键值并使用TOP (25) ... WHERE Key > @last_key ORDER BY Key 可能是最佳性能方法,如果存在合适的索引以允许有效地寻找它 - 或@987654321 @如果他们不这样做。

对于选择任意页面,SQL Server 2005 - 2008 R2 的最佳解决方案可能是 ROW_NUMBERBETWEEN

对于 SQL Server 2012+,您可以使用增强的 ORDER BY 子句来满足此需求。

SELECT  *
FROM     MyTable 
ORDER BY OrderingColumn ASC 
OFFSET  50 ROWS 
FETCH NEXT 25 ROWS ONLY 

虽然it remains to be seen how well performing this option will be.

【讨论】:

现在可以在 SQL Server Compact 4.0 中使用 --> msdn.microsoft.com/en-us/library/gg699618(v=sql.110).aspx 是时候将它添加到 tSQL 中了 仅适用于 Sql Server 2012 :(【参考方案3】:

这是一种方式(SQL2000)

SELECT * FROM
(
    SELECT TOP (@pageSize) * FROM
    (
        SELECT TOP (@pageNumber * @pageSize) *
        FROM tableName 
        ORDER BY columnName ASC
    ) AS t1 
    ORDER BY columnName DESC
) AS t2 
ORDER BY columnName ASC

这是另一种方式(SQL 2005)

;WITH results AS (
    SELECT 
        rowNo = ROW_NUMBER() OVER( ORDER BY columnName ASC )
        , *
    FROM tableName 
) 
SELECT * 
FROM results
WHERE rowNo between (@pageNumber-1)*@pageSize+1 and @pageNumber*@pageSize

【讨论】:

只是为了澄清第一个......(@pageSize)是实际值的占位符。你必须专门做“TOP 25”; SQL Server 2000 不支持 TOP 子句中的变量。这使得涉及动态 SQL 变得很痛苦。 SQL2000 的解决方案不适用于结果集中的最后一页,除非总行数恰好是页面大小的倍数。【参考方案4】:

你可以使用ROW_NUMBER()函数来得到你想要的:

SELECT *
FROM (SELECT ROW_NUMBER() OVER(ORDER BY id) RowNr, id FROM tbl) t
WHERE RowNr BETWEEN 10 AND 20

【讨论】:

【参考方案5】:

SQL Server 2012 中有OFFSET .. FETCH,但您需要指定ORDER BY 列。

如果您确实没有任何明确的列可以作为ORDER BY 列传递(正如其他人所建议的那样),那么您可以使用这个技巧:

SELECT * FROM MyTable 
ORDER BY @@VERSION 
OFFSET 50 ROWS FETCH NEXT 25 ROWS ONLY

... 或

SELECT * FROM MyTable 
ORDER BY (SELECT 0)
OFFSET 50 ROWS FETCH NEXT 25 ROWS ONLY

当用户没有明确指定订单时,我们在jOOQ 中使用它。然后,这将产生相当随机的排序,而无需任何额外费用。

【讨论】:

【参考方案6】:

对于数据列多且大的表,我更喜欢:

SELECT 
  tablename.col1,
  tablename.col2,
  tablename.col3,
  ...
FROM
(
  (
    SELECT
      col1
    FROM 
    (
      SELECT col1, ROW_NUMBER() OVER (ORDER BY col1 ASC) AS RowNum
      FROM tablename
      WHERE ([CONDITION])
    )
    AS T1 WHERE T1.RowNum BETWEEN [OFFSET] AND [OFFSET + LIMIT]
  )
  AS T2 INNER JOIN tablename ON T2.col1=tablename.col1
);

-

[CONDITION] can contain any WHERE clause for searching.
[OFFSET] specifies the start,
[LIMIT] the maximum results.

对于像 BLOB 这样的大数据表,它的性能要好得多,因为 ROW_NUMBER 函数只需要查看一列,并且只返回与所有列匹配的行。

【讨论】:

【参考方案7】:

查看我的分页器选择

SELECT TOP @limit * FROM (
   SELECT ROW_NUMBER() OVER (ORDER BY colunx ASC) offset, * FROM (

     -- YOU SELECT HERE
     SELECT * FROM mytable


   ) myquery
) paginator
WHERE offset > @offset

这解决了分页;)

【讨论】:

【参考方案8】:
SELECT TOP 75 * FROM MyTable
EXCEPT 
SELECT TOP 50 * FROM MyTable

【讨论】:

性能方面似乎不是最优的,因为查询随后会不必要地执行两次。尤其是当用户进入更高的页面时,丢弃行的查询,即除了下面的部分将花费越来越长的时间。【参考方案9】:

根据您的版本,您无法直接执行此操作,但您可以执行一些 hacky 之类的操作

select top 25 *
from ( 
  select top 75 *
  from   table 
  order by field asc
) a 
order by field desc 

其中“字段”是键。

【讨论】:

SQL2000 的解决方案不适用于结果集中的最后一页,除非总行数恰好是页面大小的倍数。【参考方案10】:

以下将显示 25 条记录,不包括 SQL Server 2012 中的前 50 条记录。

SELECT * FROM MyTable ORDER BY ID OFFSET 50 ROWS FETCH NEXT 25 ROWS ONLY;

您可以根据需要替换 ID

【讨论】:

请补充一下,这在 SQL SERVER 2012 中是可能的【参考方案11】:

使用ROW_NUMBER() OVER (ORDER BY) 语句时应该小心,因为性能很差。使用带有ROW_NUMBER() 的公用表表达式也是如此,这更糟。我正在使用以下 sn-p,它已被证明比使用具有标识的表变量来提供页码稍快。

DECLARE @Offset INT = 120000
DECLARE @Limit INT = 10

DECLARE @ROWCOUNT INT = @Offset+@Limit
SET ROWCOUNT @ROWCOUNT

SELECT * FROM MyTable INTO #ResultSet
WHERE MyTable.Type = 1

SELECT * FROM
(
    SELECT *, ROW_NUMBER() OVER(ORDER BY SortConst ASC) As RowNumber FROM
    (
        SELECT *, 1 As SortConst FROM #ResultSet
    ) AS ResultSet
) AS Page
WHERE RowNumber BETWEEN @Offset AND @ROWCOUNT

DROP TABLE #ResultSet

【讨论】:

这将返回 11 行,而不是 10 行。【参考方案12】:

我使用这种技术进行分页。我没有获取所有行。例如,如果我的页面需要显示前 100 行,我只使用 where 子句获取 100 行。 SQL 的输出应该有一个唯一的键。

该表有以下内容:

ID, KeyId, Rank

同一等级将分配给多个 KeyId。

SQL 是select top 2 * from Table1 where Rank >= @Rank and ID > @Id

我第一次为两者都传递了 0。第二次通过 1 和 14。第三次通过 2 和 6....

第10条记录Rank&Id的值传给下一条

11  21  1
14  22  1
7   11  1
6   19  2
12  31  2
13  18  2

这对系统的压力最小

【讨论】:

【参考方案13】:

在 SqlServer2005 中,您可以执行以下操作:

DECLARE @Limit INT
DECLARE @Offset INT
SET @Offset = 120000
SET @Limit = 10

SELECT 
    * 
FROM
(
   SELECT 
       row_number() 
   OVER 
      (ORDER BY column) AS rownum, column2, column3, .... columnX
   FROM   
     table
) AS A
WHERE 
 A.rownum BETWEEN (@Offset) AND (@Offset + @Limit-1) 

【讨论】:

不应该是@Offset + @Limit - 1吗?如果@Limit 为 10,这将返回 11 行。【参考方案14】:

不浪费时间订购记录的最佳方法是这样的:

select 0 as tmp,Column1 from Table1 Order by tmp OFFSET 5000000 ROWS FETCH NEXT 50 ROWS ONLY

不到一秒! 大型表的最佳解决方案。

【讨论】:

【参考方案15】:

使用 SQL Server 2012 (11.x) 及更高版本和 Azure SQL 数据库,您还可以拥有“fetch_row_count_expression”,还可以拥有 ORDER BY 子句。

USE AdventureWorks2012;  
GO  
-- Specifying variables for OFFSET and FETCH values    
DECLARE @skip int = 0  , @take int = 8;  
SELECT DepartmentID, Name, GroupName  
FROM HumanResources.Department  
ORDER BY DepartmentID ASC   
    OFFSET @skip ROWS   
    FETCH NEXT @take ROWS ONLY; 

https://docs.microsoft.com/en-us/sql/t-sql/queries/select-order-by-clause-transact-sql?view=sql-server-ver15

注意 OFFSET 指定在开始从查询表达式返回行之前要跳过的行数。它不是起始行号。因此,必须为 0 才能包含第一条记录。

【讨论】:

【参考方案16】:

我一直在寻找这个答案一段时间(对于通用查询),并找到了另一种在 SQL Server 2000+ 上使用 ROWCOUNT 和游标并且没有 TOP 或任何临时表的方法。

使用SET ROWCOUNT [OFFSET+LIMIT] 可以限制结果,并使用游标直接转到所需的行,然后循环'直到结束。

所以你的查询应该是这样的:

SET ROWCOUNT 75 -- (50 + 25)
DECLARE MyCursor SCROLL CURSOR FOR SELECT * FROM pessoas
OPEN MyCursor
FETCH ABSOLUTE 50 FROM MyCursor -- OFFSET
WHILE @@FETCH_STATUS = 0 BEGIN
    FETCH next FROM MyCursor
END
CLOSE MyCursor
DEALLOCATE MyCursor
SET ROWCOUNT 0

【讨论】:

我不想在你接近表尾时看到它的表现......【参考方案17】:

方法一:

这里的顺序似乎很重要

在偏移之前引入限制似乎有效。

SELECT *
FROM MyTable
LIMIT 25
OFFSET 50

方法二:

或者,您可以仅使用限制 LIMIT 有两个值 第一个:偏移值 第二:要显示的行数

选择 * 来自我的表 LIMIT (OffsetValue), (NoOfRows)

SELECT *
FROM MyTable
LIMIT 50, 25

【讨论】:

以上是关于SQL Server 中的行偏移量的主要内容,如果未能解决你的问题,请参考以下文章

如何从时区名称中获取时区偏移量

iOS7 在 UITableViewCell 中以适当的偏移量编辑 UITextField 时滚动到 UITableViewController 的行

维护偏移量之HBase保存Kafka偏移量

jquery animate scrollTop 位置/偏移量不起作用

java中的偏移量和偏移地址是啥

具有限制和偏移量的 CodeIgniter sql 查询未显示结果