SELECT TOP (@pageSize)
*
FROM ( SELECT TOP ( @pageSize * @pageIndex )
*
FROM [Northwind].[dbo].[Products]
ORDER BY UnitPrice DESC
) AS product
ORDER BY product.UnitPrice
但是这个代码是有问题的,不知道各位发现了没有。当符合条件的纪录集小于每页记录数时,没有问题,但是当大于就有问题了,比如,在实例数据库中Products中有 77 条记录,当每页10条记录,第8页只应该返回7条记录,第9页应该返回空,但是使用如上的方法,每次都会返回10条记录。
沿用上面的思路,把代码修改为了如下采用三层Select,最内一层查询所有记录之前的数据,然后第二层选择Top PageSize个所有NOT IN 第一层数据中的数据即可,因为使用了NOT IN所以不存在第一种方法中的bug
SELECT *
FROM dbo.Products
WHERE ProductID IN (
SELECT TOP ( @pageSize )
ProductID
FROM dbo.Products
WHERE ProductID NOT IN ( SELECT TOP ( @pageSize * (@pageIndex-1) )
ProductID
FROM dbo.Products
ORDER BY UnitPrice DESC )
ORDER BY dbo.Products.UnitPrice DESC )
ORDER BY dbo.Products.UnitPrice ASC
SELECT ROW_NUMBER() OVER ( ORDER BY dbo.Products.ProductID DESC ) AS rownum ,
*
FROM dbo.Products
WHERE rownum BETWEEN 1 AND 10
会提示错误:
Invalid column name 'rownum'.
SELECT ROW_NUMBER() OVER ( ORDER BY dbo.Products.ProductID DESC ) AS rownum ,
*
FROM dbo.Products
WHERE ( ROW_NUMBER() OVER (ORDER BY City) AS rown ) BETWEEN 1 AND 10
会提示错误:
Incorrect syntax near the keyword 'AS'.
正确的做法是,把查询的结果作为一个内查询,再在外面套上一个外查询语句:
SELECT *
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY dbo.Products.ProductID DESC ) AS rownum ,
*
FROM dbo.Products
) AS temp
WHERE temp.rownum BETWEEN 1 AND 10
有了以上基础之后,我们就可以利用ROW_NUMBER这个特性来进行排序了。
SELECT *
FROM ( SELECT TOP ( @pageSize * @pageIndex )
ROW_NUMBER() OVER ( ORDER BY dbo.Products.UnitPrice DESC ) AS rownum ,
*
FROM dbo.Products
) AS temp
WHERE temp.rownum > ( @pageSize * ( @pageIndex - 1 ) )
ORDER BY temp.UnitPrice
WITH ProductEntity
AS ( SELECT TOP ( @pageSize * @pageIndex )
ROW_NUMBER() OVER ( ORDER BY dbo.Products.UnitPrice DESC ) AS rownum ,
*
FROM dbo.Products
)
SELECT *
FROM ProductEntity
WHERE ProductEntity.rownum > ( @pageSize * ( @pageIndex - 1 ) )
ORDER BY ProductEntity.UnitPrice
WITH ProductEntity
AS ( SELECT TOP ( @pageSize * @pageIndex )
ROW_NUMBER() OVER ( ORDER BY dbo.Products.UnitPrice DESC ) AS rownum ,
ProductID ,--主键,
UnitPrice--待排序字段
FROM dbo.Products
)
SELECT *
FROM ProductEntity
INNER JOIN dbo.Products ON dbo.Products.ProductID = ProductEntity.ProductID
WHERE ProductEntity.rownum > ( @pageSize * ( @pageIndex - 1 ) )
ORDER BY ProductEntity.UnitPrice
使用ROW_NUMBER来进行分页是一种使用很广的分页方式, 在本文开头讲到在LINQ中可以采用的TAKE 和 SKIP语句,但是与数据库交互只能使用SQL语句,LINQ在内部会帮我们转化为合适的SQL语句,语句里面其实也是采用ROW_NUMBER这一函数,为了演示,我们新建一个Console程序,然后在里面添加一个LINQ To SQL的类,使用方法非常简单,如下:
List product;
int pageSize = 10;
int pageIndex = 8;
using (ProductsDataContext context = new ProductsDataContext())
{
product = context.Products.OrderByDescending(x => x.UnitPrice)//排序
.Skip(pageSize * (pageIndex-1))//跳过前面的记录
.Take(pageSize)//选取每一页个数
.ToList();
}
寥寥几句就实现了分页。
我们知道LINQ其实是将C#表达式树转换成了SQL语言,通过SQLServer Profile 工具,我们可以看到程序发送给SQLServer的请求,如下:
我把下面的语句拷贝出来,可以看到
EXEC sp_executesql N'SELECT [t1].[ProductID], [t1].[ProductName], [t1].[SupplierID], [t1].[CategoryID], [t1].[QuantityPerUnit], [t1].[UnitPrice], [t1].[UnitsInStock], [t1].[UnitsOnOrder], [t1].[ReorderLevel], [t1].[Discontinued]
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [t0].[UnitPrice] DESC) AS [ROW_NUMBER], [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock], [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
FROM [dbo].[Products] AS [t0]
) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1
ORDER BY [t1].[ROW_NUMBER]', N'@p0 int,@p1 int', @p0 = 70, @p1 = 10
这正是我们之前手写的采用ROW_NUMBER 的分页程序。可见,简简单单的一句SKIP和TAKE,LINQ在后面帮我们做了很多工作。
使用OFFSET FETCH子句分页
既然LINQ这么简单的搞定了分页,那么SQLServer中有没有类似的简单的语句就能搞定分页了,答案是有的,那就是SQL Server Compact 4.0中引入的OFFSET FETCH子句。
SELECT *
FROM dbo.Products
ORDER BY UnitPrice DESC
OFFSET ( @pageSize * ( @pageIndex - 1 )) ROWS
FETCH NEXT @pageSize ROWS ONLY;
是不是和LINQ很像,OFFSEET相当于SKIP,FETCH NEXT相当于TAKE。
可以在官网上下载SQL Server CE 4.0,目前仅支持SQL Server 2012及SQL Server 2014,不过可以使用Microsoft Webmatrix这个工具来用这一新功能。
比较
在讨论性能之前,首先需要明确的是,我们在编写SQL语句的时候,尽量要减少不必要字段的输出,文中出于演示,所以都用的*,在实际中不要这样。还有就是要根据业务逻辑,比如查询条件,建立合适的聚合索引和非聚合索引,索引对于查找的效率影响非常大,SQL中的索引其实就是建立某种平衡查找树,如B树来进行,这方面的知识可以看我之前写的算法中的文章,再有就是了解一下SQL Server 的一些特性比如CTE,IN 和Exist的区别等等,有些小的地方对性能可能有一定的影响。
在上面这些处理好了之后,我们现在来讨论那种分页方案更好。
采用Top – Not In - Top方案比较复杂,里面包含了in语句,效率不高,但是兼容个版本的SQL Server。
FFSET FETCH 方法是SQL Server CE 4.0 中才引入的,由于本文没有SQL Server 2012以及测试数据,从comparing-performance-for-different-sql-server-paging-methods这篇文章来以及园子里的Sql Server 2012 分页方法分析(offset and fetch),性能应该是比较好的。