您的位置:首页 > 数据库

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游标提供了一下非常有用并且强大的功能来实现基于行的操作,但是这种强大功能是依性能为代价的。上面我们列举了一种不使用游标来实现类似于游标。这在很多场合可以有效提高性能。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: