多种存储过程分页方法的速度对比
2009-11-17 22:04
309 查看
Generalization of Complex Queries As pointed out before, all the procedures are generalized with dynamic SQL, thus, in theory, they can work with any kind of complex query. Here is a complex query sample that works with Northwinddatabase. SELECT Customers.ContactName AS Customer, Customers.Address + ', ' + Customers.City + ', ' + Customers.Country AS Address, SUM([Order Details].UnitPrice*[Order Details].Quantity) AS [Total money spent] FROM Customers INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID WHERE Customers.Country <> 'USA' AND Customers.Country <> 'Mexico' GROUP BY Customers.ContactName, Customers.Address, Customers.City, Customers.Country HAVING (SUM([Order Details].UnitPrice*[Order Details].Quantity))>1000 ORDER BY Customer DESC, Address DESC The paging stored procedure call that returns the second page looks like this EXEC ProcedureName /* Tables */ 'Customers INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID', /* PK */ 'Customers.CustomerID', /* ORDER BY */ 'Customers.ContactName DESC, Customers.Address DESC', /* PageNumber */ 2, /* Page Size */ 10, /* Fields */ 'Customers.ContactName AS Customer, Customers.Address + '', '' + Customers.City + '', '' + Customers.Country AS Address, SUM([Order Details].UnitPrice*[Order Details].Quantity) AS [Total money spent]', /* Filter */ 'Customers.Country <> ''USA'' AND Customers.Country <> ''Mexico''', /*Group By*/ 'Customers.CustomerID, Customers.ContactName, Customers.Address, Customers.City, Customers.Country HAVING (SUM([Order Details].UnitPrice*[Order Details].Quantity))>1000' Note that in the original query, aliases are used in the ORDER BYclause. You can't do that in paging procedures, because the most time-consuming task in all of them is skipping rows preceding the starting row. This is done in various ways, but the principle is not to fetch all the required fields at first, but only the PK column(s) (in case of RowCountmethod the sorting column), which speeds up this task. All required fields are fetched only for the rows that belong to the requested page. Therefore, field aliases don't exist until the final query, and sorting columns have to be used earlier (in row skipping queries). The RowCountprocedure has another problem, it is generalized to work with only one column in the ORDER BYclause. The same goes for Asc-Descand Cursormethods, though they can work with several ordering columns, but require that only one column is included in the PK. I guess this could be solved with more dynamic SQL, but in my opinion it is not worth the fuss. Although these situations are highly possible, they are not that frequent. Even if they are, you can always write a separate paging procedure following the principles above. 具体请参看: http://www.codeproject.com/KB/aspnet/PagingLarge.aspx |
我们若在 SQL Server 2005 的 Northwind 数据库中,执行下列的 SQL 语句 (取自 SQL Server 在线丛书):
WITH 暂存表 AS
(SELECT OrderID, CustomerID, OrderDate, ROW_NUMBER() OVER(ORDER BY OrderID DESC) AS 字段编号
FROM Orders)
SELECT * FROM 暂存表 WHERE 字段编号 BETWEEN 5 AND 13;
则可由 ROW_NUMBER 函数模拟的临时数据表中,取得我们写分页时,所需要的某个范围内的数据记录笔数。以上图 2 来说,即只撷取 DESC 反向排序后、5 至 13 号的这九笔记录。且这种 WITH 的 T-SQL 新语法,又称为「一般数据表表达式 (CTE, common_table_expression)」,也是与 ROW_NUMBER 函数搭配,撰写分页程序的精要所在,在 SQL Server 在线丛书也有相关介绍。
版工还在网络上的论坛 [5],看到别人提供的语法,先用 ROW_NUMBER 函数取得数据的顺序编号,再用 WHERE 条件过滤:
SELECT TOP(分页大小) *
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY 排序条件) AS RowNo FROM 数据表
) AS T
WHERE RowNo > ((目前页数 - 1) * 分页大小)
假设我们的 GridView 每页要显示 10 笔记录,user 目前在 GridView 的第 20 页,当他单击「下一页」或第 21 页的页码时,就去 SQL Server 2005 撷取第 201 ~ 210 笔记录,在 Stored Procedure 里即执行下列 SQL 语句。撷取结果如下图 3 所示:
SELECT TOP(10) *
FROM
(
SELECT OrderID, CustomerID, OrderDate, ROW_NUMBER() OVER (ORDER BY OrderID DESC) AS 字段编号 FROM Orders
) AS 暂存表
WHERE 字段编号 > ((21 - 1) * 10)
但以 ROW_NUMBER 函数撰写分页的话,亦要考虑系统以后无法更换数据库的问题;且用 ROW_NUMBER 写好的 Stored Procedure,在其它 project 中,也无法重复使用于他牌的数据库,或旧版的 SQL Server。就如同 ADO.NET 2.0 中,有新增一些针对 SQL Server 2005 可提升 performance 的 .NET 数据处理语法,但使用前应先评估,系统日后是否有移植或维护上的问题。
/*基于SQL SERVER 2005 */
CREATE PROCEDURE [dbo].[Zhzuo_GetItemsPage2005]
@PageIndex INT, /*页面索引,0为第一页*/
@PageSize INT, /*每页记录数*/
@RecordCount INT OUT, /*总记录数*/
@PageCount INT OUT
AS /*获取记录数*/
SELECT @RecordCount = COUNT(*) FROM Production.Product
/*计算页面数据*/
SET @PageCount = CEILING(@RecordCount * 1.0 / @PageSize)
/* 基于SQL SERVER 2005 */
SELECT SerialNumber,ProductID,Name FROM
(SELECT ProductID,Name,ROW_NUMBER() OVER (ORDER BY ProductID DESC) AS SerialNumber FROM Production.Product ) AS T
WHERE T.SerialNumber > (@PageIndex * @PageSize) and T.SerialNumber <= ((@PageIndex+1) * @PageSize)
第三个存储过程使用2005下新的功能,实现的分页存储过程功能更加简单明了,而且更加容易理解。注意这里的ProductID为主键,根据ProductID进行排序生成ROW_NUMBER,通过ROW_NUMBER来确定具体的页数。
利用select top 和 select max(列键)
create procedure proc_paged_with_selectMax --利用select top and select max(列)
(
@pageIndex int, --页索引
@pageSize int --页记录数
)
as
begin
set nocount on;
declare @timediff datetime
declare @sql nvarchar(500)
select @timediff=Getdate()
set @sql='select top '+str(@pageSize)+' * From tb_TestTable where(ID>(select max(id) From (select top '+str(@pageSize*@pageIndex)+' id From tb_TestTable order by ID) as TempTable)) order by ID'
execute(@sql)
select datediff(ms,@timediff,GetDate()) as 耗时
set nocount off;
end
利用ID大于多少和SELECT TOP分页,主键必须为标识列,[ID] int IDENTITY (1,1)
SELECT TOP 10 *
FROM TestTable
WHERE (ID >
(SELECT MAX(id)
FROM (SELECT TOP 20 id
FROM TestTable
ORDER BY id) AS T))
ORDER BY ID
SELECT TOP 页大小 *
FROM TestTable
WHERE (ID >
(SELECT MAX(id)
FROM (SELECT TOP 页大小*页数 id
FROM 表
ORDER BY id) AS T))
ORDER BY ID
在使用SQL Server(SQL Server 2000)进行排序我们经常使用的是TOP关键字,但是使用TOP有一个问题,就是TOP后面只支持数值而不支持数值型的变量,这样就没有办法在存储过程中来支持这种动态排序的要求。或者使用Exec来执行构造SQL来执行,但这样执行的效率低而且不够灵活。
实际上,在SQL Server 2000中我们完全可以使用ROWCOUNT关键字解决这个问题。
ROWCOUNT关键字作用是可以直接指定需要返回记录集的行数。
1、使用ROWCOUNT查询前100行记录。
DECLARE @rc INT
SET @rc = 100
SET ROWCOUNT @rc
SELECT * FROM emp
使用TOP可以得到同样的结果
SELECT TOP 100 FROM emp
2、在INSERT INTO..SELECT中使用ROWROUNT。
DECLARE @rc INT
SET @rc = 100
SET ROWCOUNT @rc
INSERT INTO cust (cname)
SELECT cname=emp_name FROM emp
3、在执行UPDATE和DELETE时使用ROWCOUNT。
因为UPDATE和DELETE无法直接使用ORDER BY语法,如果使用ROWCOUNT,将按照主键顺序从前往后操作。
DECLARE @rc INT
SET @rc = 100
SET ROWCOUNT @rc
DELETE FROM emp
不过也有解决办法,只要能够使用ORDER BY关键字就可以了,比如说直接用含ORDER BY的子句,或者先使用ORDER BY语法把需要操作的标识列存为一个临时表或表变量,然后再操作语句中使用IN或EXISTS关键字。
DECLARE @rc INT
SET @rc = 100
SET ROWCOUNT @rc
DECLARE @tmp TABLE(ID INT)
INSERT INTO @tmp
SELECT ID FROM emp ORDER BY cid[ASC/DESC]
DELETE FROM emp WHERE ID IN (SELECT ID FROM @tmp )
4、对于ROWCOUNT的设置是与Session有关的。如果占用了一个Session,那么对应的这个数据库Session将使用最近一次设置的ROWCOUNT,直到Session结束或者修改了ROWCOUNT。
5、在用户自定义函数中不能使用ROWCOUNT。
6、取消ROWCOUNT设置。
使用这样的语句即可取消ROWCOUNT了,因为如果不取消之后所有的查询返回的结果集行数都会受此影响。
SET ROWCOUNT 0
我不知道为什么在联机帮助中说,写存储过程的时候应该注意尽量避免使用ROWCOUNT,而建议使用TOP。难道MS不知道TOP关键后面的数字不能为变量吗?也许MS是出于担心开发者忘记了取消ROWCOUNT而影响正常的实现。
8、总结
有了ROWCOUNT关键字后就可以非常方便的实现变量形式的排序问题了。
if object_id('tb') is not null
drop table tb
go
create table tb(id int identity,name varchar(10))
insert into tb select 'a'
insert into tb select 'b'
insert into tb select 'c'
insert into tb select 'd'
insert into tb select 'e'
select * from tb
select @@rowcount // 表示查询所影响的行数 5
set rowcount 2 // 设置影响的行数 2
select * from tb order by id // 查 a,b
set rowcount 0 // 复位
insert into tb select 'f'
select @@identity // 查当前会话中的表最后插入的自增列的值
SET NOCOUNT
说明:使返回的结果中不包含有关受 Transact-SQL 语句影响的行数的信息。
语法:SET NOCOUNT { ON | OFF }
注释:当 SET NOCOUNT 为 ON 时,不返回计数(表示受 Transact-SQL 语句影响的行数)。当 SET NOCOUNT 为 OFF 时,返回计数。
即使当 SET NOCOUNT 为 ON 时,也更新 @@ROWCOUNT 函数。
当 SET NOCOUNT 为 ON 时,将不给客户端发送存储过程中的每个语句的 DONE_IN_PROC 信息。当使用 Microsoft? SQL Server? 提供的实用工具执行查询时,在 Transact-SQL 语句(如 SELECT、INSERT、UPDATE 和 DELETE)结束时将不会在查询结果中显示"nn rows affected"。
如果存储过程中包含的一些语句并不返回许多实际的数据,则该设置由于大量减少了网络流量,因此可显著提高性能。
SET NOCOUNT 设置是在执行或运行时设置,而不是在分析时设置。
权限:SET NOCOUNT 权限默认授予所有用户。
示例:下例在 osql 实用工具或 SQL Server 查询分析器中执行时,可防止显示有关受影响的行数的信息。
USE pubs
GO
-- Display the count message.
SELECT au_lname
FROM authors
GO
USE pubs
GO
-- SET NOCOUNT to ON and no longer display the count message.
SET NOCOUNT ON
GO
SELECT au_lname
FROM authors
GO
-- Reset SET NOCOUNT to OFF.
SET NOCOUNT OFF
GO
@@ROWCOUNT
说明:返回受上一语句影响的行数。
语法:@@ROWCOUNT
返回类型:integer
注释:任何不返回行的语句将这一变量设置为 0 ,如 IF 语句。
示例:下面的示例执行 UPDATE 语句并用 @@ROWCOUNT 来检测是否有发生更改的行。
UPDATE authors SET au_lname = 'Jones'
WHERE au_id = '999-888-7777'
IF @@ROWCOUNT = 0
print 'Warning: No rows were updated'
SET ROWCOUNT
说明:使 Microsoft? SQL Server? 在返回指定的行数之后停止处理查询。
语法:SET ROWCOUNT { number | @number_var }
参数:number | @number_var 是在停止给定查询之前要处理的行数(整数)。
注释:建议将当前使用 SET ROWCOUNT 的 DELETE、INSERT 和 UPDATE 语句重新编写为使用 TOP 语法。有关更多信息,请参见 DELETE、INSERT 或 UPDATE。
对于在远程表和本地及远程分区视图上执行的 INSERT、UPDATE 和 DELETE 语句,忽略 SET ROWCOUNT 选项设置。
若要关闭该选项(以便返回所有的行),请将 SET ROWCOUNT 指定为 0。
说明 设置 SET ROWCOUNT 选项将使大多数 Transact-SQL 语句在已受指定数目的行影响后停止处理。这包括触发器和 INSERT、UPDATE 及 DELETE 等数据修改语句。ROWCOUNT 选项对动态游标无效,但限制键集的行集和不感知游标。使用该选项时应谨慎,它主要与 SELECT 语句一起使用。
如果行数的值较小,则 SET ROWCOUNT 替代 SELECT 语句 TOP 关键字。
SET ROWCOUNT 的设置是在执行或运行时设置,而不是在分析时设置。
权限:SET ROWCOUNT 权限默认授予所有用户。
示例:SET ROWCOUNT 在指定的行数后停止处理。在下例中,注意有 x 行满足预付款少于或等于 $5,000 的条件;但是,从更新所返回的行数中可以看出并非所有的行都得到处理。ROWCOUNT 影响所有的 Transact-SQL 语句。
USE pubs
GO
SELECT count(*) AS Cnt
FROM titles
WHERE advance >= 5000
GO
下面是结果集:
Cnt
-----------
11
(1 row(s) affected)
现在,将 ROWCOUNT 设置为 4,并更新预付款等于或大于 $5,000 的所有行。
-- SET ROWCOUNT to 4.
SET ROWCOUNT 4
GO
UPDATE titles
SET advance = 5000
WHERE advance >= 5000
GO
相关文章推荐
- 多种存储过程分页方法的速度对比
- 多种存储过程分页方法的速度对比
- 这个存储过程执行的速度还不错.500W速度分页只要2秒
- 两种SQL分页方法存储过程和游标存储过程
- SQL 简单的存储过程分页 改写方法
- 通用分页存储过程注入问题解决方案:不用存储过程,通用分页查询方法
- 邹建的分页存储过程,呵呵速度不错啊~!
- java+oracle的存储过程开发案例(包含了oracle存储过程的通用分页方法、java的工厂类)
- 【转载】Sql Server2005不同分页存储过程的性能对比
- 原创:SQL Server的通用分页存储过程,未使用游标,速度更快!
- 存储过程分页方法3种(转贴)
- SQL存储过程分页若干方法
- (转)几种常用存储过程分页方法
- SQL存储过程分页另一方法
- 常见存储过程分页PK赛——简单测试分析常见存储过程分页速度
- SQL SERVER 存储过程分页的3种通用方法
- SQL 分页存储过程 以及SQL concatenate 几种方法
- 原创:SQL Server的通用分页存储过程,未使用游标,速度更快!
- 存储过程的分页(对比)