sql中游标的替代方案
2011-11-10 17:32
148 查看
首先,先看一下简单的游标,此游标在循环一个表,然后我们看一下如何不使用游标来达到相同的目标。
使用游标:
ifexists(select*fromsysobjectswherename=N'prcCursorExample')
dropprocedureprcCursorExample
go
CREATEPROCEDUREprcCursorExample
AS
/*
**putyourcommenthere
**
*/
SETNOCOUNTON
--declareallvariables!
DECLARE@iRowIdint,
@vchCustomerNamenvarchar(255),
@vchCustomerNmbrnvarchar(10)
--declarethecursor
DECLARECustomerCURSORFOR
SELECTiRowId,
vchCustomerNmbr,
vchCustomerName
FROMCustomerTable
OPENCustomer
FETCHCustomerINTO@iRowId,
@vchCustomerNmbr,
@vchCustomerName
--startthemainprocessingloop.
WHILE@@Fetch_Status=0
BEGIN
--Thisiswhereyouperformyourdetailedrow-by-row
--processing.
--Getthenextrow.
FETCHCustomerINTO@iRowId,
@vchCustomerNmbr,
@vchCustomerName
END
CLOSECustomer
DEALLOCATECustomer
RETURN
不使用游标:
ifexists(select*fromsysobjectswherename=N'prcLoopExample')
dropprocedureprcLoopExample
go
CREATEPROCEDUREprcLoopExample
AS
/*
**putyourcommenthere
*/
SETNOCOUNTON
--declareallvariables!
DECLARE@iReturnCodeint,
@iNextRowIdint,
@iCurrentRowIdint,
@iLoopControlint,
@vchCustomerNamenvarchar(255),
@vchCustomerNmbrnvarchar(10)
@chProductNumbernchar(30)
--Initializevariables!
SELECT@iLoopControl=1
SELECT@iNextRowId=MIN(iRowId)
FROMCustomerTable
--Makesurethetablehasdata.
IFISNULL(@iNextRowId,0)=0
BEGIN
SELECT'Nodatainfoundintable!'
RETURN
END
--Retrievethefirstrow
SELECT@iCurrentRowId=iRowId,
@vchCustomerNmbr=vchCustomerNmbr,
@vchCustomerName=vchCustomerName
FROMCustomerTable
WHEREiRowId=@iNextRowId
--startthemainprocessingloop.
WHILE@iLoopControl=1
BEGIN
--Thisiswhereyouperformyourdetailedrow-by-row
--processing.
--Resetloopingvariables.
SELECT@iNextRowId=NULL
--getthenextiRowId
SELECT@iNextRowId=MIN(iRowId)
FROMCustomerTable
WHEREiRowId>@iCurrentRowId
--didwegetavalidnextrowid?
IFISNULL(@iNextRowId,0)=0
BEGIN
BREAK
END
--getthenextrow.
SELECT@iCurrentRowId=iRowId,
@vchCustomerNmbr=vchCustomerNmbr,
@vchCustomerName=vchCustomerName
FROMCustomerTable
WHEREiRowId=@iNextRowId
END
RETURN
我们现在来看一下上面的游标,一般来说,为了性能的因素,我们的表上都有一个类似于RowID的列,此列可以被用来做循环,并得到相关的数据。一般来说此列是IDENTITY列,主键并有clustered索引。
但是很多情况下,我们的表中并不包括可以被用来循环的行ID,比如,可能在一个具有uniqueindentifier属性的列上创建了主键索引,这时候可以为这个表增加一个自增列并创建相应的索引,来实现此功能。
上面例子使用了MIN函数和”>”来得到下一行我们需要的数据,当然我们也可以使用MAX函数然后使用”<”达到相同的功能。
例子:
SELECT@iNextRowId=MAX(iRowId)
FROMCustomerTable
WHEREiRowId<@iCurrentRowId
有一个比较重要的地方需要注意:要在取得下一次要循环的行ID之前设置此ID为NULL,是因为当此循环实现所有的行循环后SELECT语句并不会为此ID设置为NULL,从而造成一个死循环。当循环变量为NULL后,意味着这个循环已经完成了它需要实现的功能,此时我们可能使用BREAK来退出WHILE循环,当然也有其它的途径来推出循环。
你可以在上面的存储过程中的如下注释处加上自己的基于行的操作。
--Thisiswhereyouperformyourdetailedrow-by-row
--processing.
可以看出,基于行的操作对性能非常有影响。举例来说,如果你有一个非常复杂的任务需要进行嵌套的循环,此时你会使用嵌套的游标,内层的游标根据外层游标的条件进行相应的操作,这时候如果我们使用游标来进行处理的话对服务器会有非常大的压力。
例子:
ifexists(select*fromsysobjectswherename=N'prcNestedLoopExample')
dropprocedureprcNestedLoopExample
go
CREATEPROCEDUREprcNestedLoopExample
AS
/*
**putyourcommenthere
*/
SETNOCOUNTON
--declareallvariables!
DECLARE@iReturnCodeint,
@iNextCustRowIdint,
@iCurrentCustRowIdint,
@iCustLoopControlint,
@iNextProdRowIdint,
@iCurrentProdRowIdint,
@vchCustomerNamenvarchar(255),
@chProductNumbernchar(30),
@vchProductNamenvarchar(255)
--Initializevariables!
SELECT@iCustLoopControl=1
SELECT@iNextCustRowId=MIN(iCustId)
FROMCustomer
--Makesurethetablehasdata.
IFISNULL(@iNextCustRowId,0)=0
BEGIN
SELECT'Nodatainfoundintable!'
RETURN
END
--Retrievethefirstrow
SELECT@iCurrentCustRowId=iCustId,
@vchCustomerName=vchCustomerName
FROMCustomer
WHEREiCustId=@iNextCustRowId
--Startthemainprocessingloop.
WHILE@iCustLoopControl=1
BEGIN
--Beginthenested(inner)loop.
--Getthefirstproductidforcurrentcustomer.
SELECT@iNextProdRowId=MIN(iProductId)
FROMCustomerProduct
WHEREiCustId=@iCurrentCustRowId
--Makesuretheproducttablehasdatafor
--currentcustomer.
IFISNULL(@iNextProdRowId,0)=0
BEGIN
SELECT'Noproductsfoundforthiscustomer.'
END
ELSE
BEGIN
--retrievethefirstfullproductrowfor
--currentcustomer.
SELECT@iCurrentProdRowId=iProductId,
@chProductNumber=chProductNumber,
@vchProductName=vchProductName
FROMCustomerProduct
WHEREiProductId=@iNextProdRowId
END
WHILEISNULL(@iNextProdRowId,0)<>0
BEGIN
--Dotheinnerlooprow-levelprocessinghere.
--Resettheproductnextrowid.
SELECT@iNextProdRowId=NULL
--GetthenextProductidforthecurrentcustomer
SELECT@iNextProdRowId=MIN(iProductId)
FROMCustomerProduct
WHEREiCustId=@iCurrentCustRowId
ANDiProductId>@iCurrentProdRowId
--Getthenextfullproductrowforcurrentcustomer.
SELECT@iCurrentProdRowId=iProductId,
@chProductNumber=chProductNumber,
@vchProductName=vchProductName
FROMCustomerProduct
WHEREiProductId=@iNextProdRowId
END
--Resetinnerloopvariables.
SELECT@chProductNumber=NULL
SELECT@vchProductName=NULL
SELECT@iCurrentProdRowId=NULL
--Resetouterloopingvariables.
SELECT@iNextCustRowId=NULL
--GetthenextiRowId.
SELECT@iNextCustRowId=MIN(iCustId)
FROMCustomer
WHEREiCustId>@iCurrentCustRowId
--Didwegetavalidnextrowid?
IFISNULL(@iNextCustRowId,0)=0
BEGIN
BREAK
END
--Getthenextrow.
SELECT@iCurrentCustRowId=iCustId,
@vchCustomerName=vchCustomerName
FROMCustomer
WHEREiCustId=@iNextCustRowId
END
RETURN
在上面的例子中,我们从一个表中进行循环,对于每个ID,我们从再从产品表中得到客户的相关产品信息。
SQLServer游标提供了一下非常有用并且强大的功能来实现基于行的操作,但是这种强大功能是依性能为代价的。上面我们列举了一种不使用游标来实现类似于游标。这在很多场合可以有效提高性能。
相关文章推荐
- 解决超出打开游标的最大数异常ORA-01000 递归SQL 级别1 出现错误 最全方案-最全方案
- 解决超出打开游标的最大数异常ORA-01000 递归SQL 级别1 出现错误 最全方案-最全方案
- SQLServer 优化SQL语句 in 和not in的替代方案
- SQL 临时表或表变量替代游标
- SQLServer 优化SQL语句:in 和not in的替代方案
- SQLServer 优化SQL语句 in 和not in的替代方案
- SQL2000 视图不支持UNION SQL 构造 替代方案 [ SQL | View | UNION ]
- SQL2000 视图不支持UNION SQL 构造 替代方案 [ SQL | View | UNION ]
- SQL 游标中 WHILE 替代方法,减少SQL服务器压力
- T-SQL中替代游标遍历结果级的方法 - 来自http://support.microsoft.com/kb/111401
- SQLServer 优化SQL语句:in 和not in的替代方案
- 优化SQL语句:in 和not in的替代方案
- 优化SQL语句:in 和not in的替代方案
- SQLServer 优化SQL语句 in 和not in的替代方案
- SQLServer 优化SQL语句 in 和not in的替代方案
- 优化SQL语句:in 和not in的替代方案
- 解决超出打开游标的最大数异常ORA-01000 递归SQL 级别1 出现错误 最全方案-最全方案
- SQLServer 优化SQL语句 in 和not in的替代方案
- SQLServer 优化SQL语句:in 和not in的替代方案
- 解决超出打开游标的最大数异常ORA-01000 递归SQL 级别1 出现错误 最全方案-最全方案