我试图使用分页,我在SO中得到了完美的链接
https://stackoverflow.com/a/109290/1481690
SELECT *
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum, *
FROM Orders
WHERE OrderDate >= '1980-01-01'
) AS RowConstrainedResult
WHERE RowNum >= 1
AND RowNum < 20
ORDER BY RowNum
我尝试在内部查询中使用少量表的其他连接时使用完全相同的查询。
在以下情况中遇到的性能问题很少
WHERE RowNum >= 1
AND RowNum < 20 ==>executes faster approx 2 sec
WHERE RowNum >= 1000
AND RowNum < 1010 ==> more time approx 10 sec
WHERE RowNum >= 30000
AND RowNum < 30010 ==> more time approx 17 sec
每次我选择10行但时差很大。有什么想法或建议吗?
我选择这种方法是动态绑定列并形成Query。还有其他更好的方法可以在SQl Server 2008中组织分页查询。
有没有办法可以提高查询效果?
由于
答案 0 :(得分:4)
我总是检查我在查询中访问了多少数据,并尝试消除不必要的列和行。 那么这些只是你可能已经检查过的明显点,但只是想指出以防你还没有。 在您的查询中,性能缓慢可能是因为您正在执行“选择*”。从表中选择所有列不允许有良好的执行计划。 检查您是否只需要选定的列,并确保在表订单上有正确的覆盖索引。
因为SQL 2008版本中没有显式的SKIPP或OFFSET函数,我们需要创建一个,我们可以通过INNER JOIN创建。 在一个查询中,我们将首先使用OrderDate生成ID,并且该查询中不会包含任何其他内容。 我们在第二个查询中也这样做,但是如果你需要ALL列,我们还可以从表ORDER或ALL中选择一些其他感兴趣的列。 然后我们通过ID和OrderDate以及ADD SKIPP行过滤来查询结果,以进行第一次查询,其中数据集的最小尺寸是所需的。 试试这个代码。
SELECT q2.*
FROM
(
SELECT ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum, OrderDate
FROM Orders
WHERE OrderDate >= '1980-01-01'
)q1
INNER JOIN
(
SELECT ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum, *
FROM Orders
WHERE OrderDate >= '1980-01-01'
)q2
ON q1.RowNum=q2.RowNum AND q1.OrderDate=q2.OrderDate AND q1.rownum BETWEEN 30000 AND 30020
为了给你估算,我尝试了以下测试数据,无论你查询什么窗口,结果都回到了不到2 秒,并注意表是HEAP(没有索引)表总共2M 行。 test select正在查询从50,000到50,010
的10行以下插页大约需要8分钟。
IF object_id('TestSelect','u') IS NOT NULL
DROP TABLE TestSelect
GO
CREATE TABLE TestSelect
(
OrderDate DATETIME2(2)
)
GO
DECLARE @i bigint=1, @dt DATETIME2(2)='01/01/1700'
WHILE @I<=2000000
BEGIN
IF @i%15 = 0
SELECT @DT = DATEADD(DAY,1,@dt)
INSERT INTO dbo.TestSelect( OrderDate )
SELECT @dt
SELECT @i=@i+1
END
选择50,000到50,010的窗口花了不到3秒。
选择最后一行2,000,000到2,000,000也需要3秒。
SELECT q2.*
FROM
(
SELECT ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum
,OrderDate
FROM TestSelect
WHERE OrderDate >= '1700-01-01'
)q1
INNER JOIN
(
SELECT ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum
,*
FROM TestSelect
WHERE OrderDate >= '1700-01-01'
)q2
ON q1.RowNum=q2.RowNum
AND q1.OrderDate=q2.OrderDate
AND q1.RowNum BETWEEN 50000 AND 50010
答案 1 :(得分:2)
ROW_NUMBER
是一种蹩脚的分页方式,因为操作成本会大幅增加。
相反,您应该使用双ORDER BY
子句。
假设您想获得ROW_NUMBER between 1200 and 1210
的记录。而不是使用ROW_NUMBER() OVER (...)
以及稍后将结果绑定到WHERE
,而不是:
SELECT TOP(11) *
FROM (
SELECT TOP(1210) *
FROM [...]
ORDER BY something ASC
) subQuery
ORDER BY something DESC.
请注意,此查询将以相反的顺序给出结果。这不应该 - 一般来说 - 是一个问题,因为很容易在UI中反转集合,即C#,特别是因为结果集应该相对较小。
后者通常要快得多。请注意,后一个解决方案将通过CLUSTERING(CREATE CLUSTERED INDEX ...
)在用于对查询进行排序的列上进行大大改进。
希望有所帮助。
答案 2 :(得分:1)
即使您始终选择相同数量的行,但如果要在数据窗口末尾选择行,性能也会下降。要获得前10行,引擎只能获取10行;要获得下一个10,它必须获取20,丢弃前10,然后返回10.要获得30000 - 30010,它必须读取所有30010,先跳过30k,然后返回10.
提高性能的一些技巧(不是完整列表,完全跳过构建OLAP)。
你提到过加入;如果可能加入不在内部查询内部,而是结果。您还可以尝试向ORDER BY OrderDate
- ASC
或DESC
添加一些逻辑,具体取决于您要检索的存储桶。假如你想要抓住“最后”10,ORDER BY ... DESC
将更快地运作。需要说的是,它必须是一个索引orderDate
。
答案 3 :(得分:0)
declare @pageOffset int
declare @pageSize int
-- set variables at some point
declare @startRow int
set @startRow = @pageOffset * @pageSize
declare @endRow int
set @endRow + @pageSize - 1
SELECT
o.*
FROM
(
SELECT
ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum
, OrderId
FROM
Orders
WHERE
OrderDate >= '1980-01-01'
) q1
INNER JOIN Orders o
on q1.OrderId = o.OrderId
where
q1.RowNum between @startRow and @endRow
order by
o.OrderDate
答案 4 :(得分:0)
@ peru,关于是否有更好的方法并以@ a1ex07提供的解释为基础,请尝试以下操作 -
如果表具有唯一标识符,例如数字(order-id)或(order-date,order-index),可以在其上执行比较(大于,小于)操作,则将其用作偏移而不是行号。
例如,如果表格订单的'order_id'为主键,那么 -
获得前十个结果 -
1.
select RowNum, order_id from
( select
ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum,
o.order_id
from orders o where o.order_id > 0 ;
)
tmp_qry where RowNum between 1 and 10 order by RowNum; // first 10
假设返回的最后一个order-id是17,那么
选择下一个10,
2.
select RowNum, order_id from
( select
ROW_NUMBER() OVER ( ORDER BY OrderDate ) AS RowNum,
o.order_id
from orders o where o.order_id > 17 ;
)
tmp_qry where RowNum between 1 and 10 order by RowNum; // next 10
请注意,row-num值尚未更改。它已被更改的order-id值进行比较。
如果没有这样的密钥,请考虑添加一个!
答案 5 :(得分:0)
您的查询的主要缺点是它对整个表进行排序并为每个查询计算Row_Number。通过在排序阶段使用较少的列(例如Anup Shah建议),您可以使SQL Server的生活更轻松。但是,您仍然可以对每个查询进行读取,排序和计算行号。
动态计算的替代方法是读取之前计算的值。
根据数据集的易变性和排序和过滤的列数,您可以考虑:
添加rownumber列(或2-3列)并将其作为聚簇索引中的第一列包含或创建非聚集的inde)。
为最常见的组合创建视图,然后为这些视图编制索引。它被称为索引(物化)视图。
这将允许读取rownumber并且性能几乎不依赖于音量。虽然保留theese会,但不会为每个查询排序整个表。
请注意,这是一次性查询并且与所有其他查询相比不常运行,最好只坚持使用查询优化:创建额外列/视图的努力可能无法获得回报。
答案 6 :(得分:0)
令人难以置信的是,没有其他答案提到最快的方式在所有SQL Server版本中进行分页,特别是关于OP的问题,其中偏移对于大页面数量来说可能非常慢{{{ 3}}。
在SQL中执行分页有一种完全不同的,快得多的方法。这通常被称为“寻找方法”,如benchmarked here中所述。
SELECT TOP 10 *
FROM Orders
WHERE OrderDate >= '1980-01-01'
AND ((OrderDate > @previousOrderDate)
OR (OrderDate = @previousOrderDate AND OrderId > @previousOrderId))
ORDER BY OrderDate ASC, OrderId ASC
@previousOrderDate
和@previousOrderId
值是上一页中最后一条记录的相应值。这允许您获取“下一页”。如果ORDER BY
方向为DESC
,则只需使用<
。
使用上述方法,您无法在未先读取前40条记录的情况下立即跳转到第4页。但通常情况下,你不想跳得那么远。相反,您可以获得更快的查询,该查询可能能够在固定时间内获取数据,具体取决于您的索引。此外,无论基础数据是否发生变化,您的页面都将保持“稳定”状态(例如,在第1页上,当您在第4页时)。
例如,这是在网络应用程序中延迟加载更多数据时实现分页的最佳方式。
注意,“搜索方法”也称为this blog post here。