SQL - 使用CTE并有效地选择特定row_number()的行

时间:2014-12-05 22:16:27

标签: sql sql-server tsql where row-number

在这种情况下,CTE中需要SELECT TOP(微观优化,我知道......)。

DECLARE @pageSize INT, @currentPage INT, @top INT
SET @pageSize = 10
SET @currentPage = 150
SET @top = @pageSize * @currentPage + @pageSize

WITH t AS
(
    SELECT TOP(***@top***) ID, name
    ROW_NUMBER() OVER (ORDER BY ID) AS _row,
    FROM dbo.User
)
SELECT TOP(@pageSize) *
FROM t
WHERE t._row > (@top-@pageSize)
ORDER BY t.ID

以上以具有行号列的特定顺序从起始编号(@ top- @ pageSize)返回10(@pageSize)行。 CTE声明是否知道" SELECT TOP" CTE和WHERE子句之外,也是CTE之外的,因此CTE永远不会按特定顺序返回比所需更多的行?

基本上只是谈论ROW_NUMBER函数,它不计算未返回的行的行号(如果我有数百万行...),如果我要选择CTE中的前100名,还是会为所选表格中的所有百万行计算row_number吗?

我曾尝试使用和不使用" SELECT TOP(@top)"在CTE语句中,在一个运行10.000次的循环中,没有看到时间使用的任何差异。虽然,目前我的表中只有38.000行。

编辑: 结果如下:

    WITH t AS
(
    **DO A TOP() WITH AN ORDER BY IN THE CTE**
    SELECT TOP(@top) ID, name 
    ROW_NUMBER() OVER (ORDER BY ID) AS _row,
    FROM dbo.User
    ORDER BY ID
)
SELECT TOP(@pageSize) * 
**SELECTING TOP N FROM THE CTE, WHERE ROW-NUMBER IS ... DUE TO THE CTE IS IN ORDER ALREADY**
FROM t
WHERE t._row > (@top-@pageSize)

如果我对它们进行排序"倒退",选择" bottom @ pageSize"这可能会更有效率。 CTE,这将省略where子句...但如果它实际上更快,那将需要一些测试......

2 个答案:

答案 0 :(得分:2)

不建议在没有top的情况下使用order by。无法保证您将获得所需的行,因此不应包含top。或者,您应该包含order by id,如果这是您想要的顺序。

top的用户不会影响row_number()计算,因为该计算将在应用top之前完成。您可以想象在那里有另一个窗口函数,例如sum() over (),以便了解top通常不能在row_number()之前应用,并且找到安全的环境将会很困难。< / p>

答案 1 :(得分:1)

如果您在ID上有支持索引,则无需阅读和枚举整个表格。 SQL Server必须读取表格,包括您想要的页面。因此,如果您想要第1页(第11行到第20行),则查询将只获取20行。即使你不在CTE中使用顶部也是如此。

要测试一些数据的表格:

create table dbo.[User]
(
  ID int identity primary key,
  Name nvarchar(128)
)

go

insert into dbo.[User](Name)
select top(1000) Name
from sys.all_objects

没有冗余顶部表达式的查询。

DECLARE @pageSize INT, @currentPage INT, @top INT;
SET @pageSize = 10;
SET @currentPage = 1;
SET @top = @pageSize * @currentPage + @pageSize;
with C as
(
  select U.ID,
         U.Name,
         row_number() over(order by U.ID) as rn
  from dbo.[User] as U
)
select C.ID,
       C.Name
from C
where C.rn > @pageSize * @currentPage and 
      C.rn <= @pageSize * (@currentPage + 1);

这将为您提供如下查询计划:

enter image description here

每个运算符的数字是实际获取的行数。聚集索引扫描读取ID排序的20行。 Segment和Sequence Project枚举行。 Top是运算符,用于确保提取的行数不超过20行。过滤器删除行1到10并让行11到20通过。

如果我们尝试获取第5页(@currentPage = 5以获取第51至60行),则该计划将如下所示:

enter image description here

Top运算符确保从聚簇索引中只读取60行,并且筛选器筛选出前50行以返回最后10行。

使用带有额外顶部表达式的上一个查询不会添加任何有价值的内容。只有一个额外的冗余顶级操作符。

enter image description here

了解查询计划中发生的事情的关键是要知道执行是从左到右完成的,一次要求一行。这就是顶级操作符在返回足够的行时停止聚簇索引扫描的方式。