在存储过程中使用带有动态SQL的游标

时间:2009-06-25 19:51:49

标签: tsql stored-procedures dynamic-sql database-cursor

我有一个我在存储过程中创建的动态SQL语句。我需要使用游标迭代结果。我很难搞清楚正确的语法。这就是我正在做的事情。

SELECT @SQLStatement = 'SELECT userId FROM users'

DECLARE @UserId

DECLARE users_cursor CURSOR FOR
EXECUTE @SQLStatment --Fails here. Doesn't like this

OPEN users_cursor
FETCH NEXT FROM users_cursor
INTO @UserId

WHILE @@FETCH_STATUS = 0
BEGIN

EXEC asp_DoSomethingStoredProc @UserId

END
CLOSE users_cursor
DEALLOCATE users_cursor

这样做的正确方法是什么?

8 个答案:

答案 0 :(得分:109)

游标只接受select语句,因此如果SQL确实需要是动态的,则声明游标是您正在执行的语句的一部分。要使以下工作,您的服务器必须使用全局游标。

Declare @UserID varchar(100)
declare @sqlstatement nvarchar(4000)
--move declare cursor into sql to be executed
set @sqlstatement = 'Declare  users_cursor CURSOR FOR SELECT userId FROM users'

exec sp_executesql @sqlstatement


OPEN users_cursor
FETCH NEXT FROM users_cursor
INTO @UserId

WHILE @@FETCH_STATUS = 0
BEGIN
Print @UserID
EXEC asp_DoSomethingStoredProc @UserId

FETCH NEXT FROM users_cursor --have to fetch again within loop
INTO @UserId

END
CLOSE users_cursor
DEALLOCATE users_cursor

如果您需要避免使用全局游标,您还可以将动态SQL的结果插入临时表,然后使用该表填充游标。

Declare @UserID varchar(100)
create table #users (UserID varchar(100))

declare @sqlstatement nvarchar(4000)
set @sqlstatement = 'Insert into #users (userID) SELECT userId FROM users'
exec(@sqlstatement)

declare users_cursor cursor for Select UserId from #Users
OPEN users_cursor
FETCH NEXT FROM users_cursor
INTO @UserId

WHILE @@FETCH_STATUS = 0
BEGIN

EXEC asp_DoSomethingStoredProc @UserId

FETCH NEXT FROM users_cursor
INTO @UserId

END
CLOSE users_cursor
DEALLOCATE users_cursor

drop table #users

答案 1 :(得分:18)

这段代码是带光标的动态列的一个很好的例子,因为你不能在@STATEMENT中使用'+':

ALTER PROCEDURE dbo.spTEST
AS
    SET NOCOUNT ON
    DECLARE @query NVARCHAR(4000) = N'' --DATA FILTER
    DECLARE @inputList NVARCHAR(4000) = ''
    DECLARE @field sysname = '' --COLUMN NAME
    DECLARE @my_cur CURSOR
    EXECUTE SP_EXECUTESQL
        N'SET @my_cur = CURSOR FAST_FORWARD FOR
            SELECT
                CASE @field
                    WHEN ''fn'' then fn
                    WHEN ''n_family_name'' then n_family_name
                END
            FROM
                dbo.vCard
            WHERE
                CASE @field
                    WHEN ''fn'' then fn
                    WHEN ''n_family_name'' then n_family_name
                END
                LIKE ''%''+@query+''%'';
            OPEN @my_cur;',
        N'@field sysname, @query NVARCHAR(4000), @my_cur CURSOR OUTPUT',
        @field = @field,
        @query = @query,
        @my_cur = @my_cur OUTPUT
    FETCH NEXT FROM @my_cur INTO @inputList
    WHILE @@FETCH_STATUS = 0
    BEGIN
        PRINT @inputList
        FETCH NEXT FROM @my_cur INTO @inputList
    END
    RETURN

答案 2 :(得分:4)

通过ODBC连接使用非关系数据库(IDMS任何人?)可以作为游标和动态SQL似乎是唯一路径的时间之一。

select * from a where a=1 and b in (1,2)

需要45分钟才能响应,同时重新编写使用keysets而没有in子句将在1秒内运行:

select * from a where (a=1 and b=1)
union all
select * from a where (a=1 and b=2)

如果列B的in语句包含1145行,则使用游标创建indidivudal语句并将其作为动态SQL执行,这比使用in子句要快得多。傻嘿?

是的,在关系数据库中没有时间应该使用游标。我简直无法相信我遇到了一个光标循环速度快几倍的实例。

答案 3 :(得分:3)

首先,尽可能避免使用光标。这里有一些资源可以帮助你解决这个问题:

There Must Be 15 Ways To Lose Your Cursors... part 1, Introduction

Row-By-Row Processing Without Cursor

尽管如此,毕竟你可能会遇到一个问题 - 我对你的问题不够了解,以确保其中任何一个适用。如果是这种情况,则会遇到另一个问题 - 游标的select语句必须是实际 SELECT语句,而不是EXECUTE语句。你被困住了。

但是看看cmsjr(在我写作的时候出现的)关于使用临时表的答案。我会避免使用 global 游标,而不仅仅是“普通”游标......

答案 4 :(得分:1)

最近从Oracle切换到SQL Server(雇主偏好)后,我注意到SQL Server中的游标支持滞后。游标并不总是邪恶的,有时需要,有时要快得多,有时比通过重新安排或添加优化提示来调整复杂查询更清晰。 “游标是邪恶的”意见在SQL Server社区中更为突出。

所以我猜这个答案是切换到Oracle或给MS一个线索。

答案 5 :(得分:1)

我还想与你分享另一个例子:D http://www.sommarskog.se/dynamic_sql.html#cursor0

答案 6 :(得分:0)

SQL Server中的另一个选项是对存储的proc中的表变量进行所有动态查询,然后使用游标进行查询和处理。关于可怕的游标辩论:),我看到的研究表明,在某些情况下,如果正确设置游标,实际上可以更快。当要求的查询过于复杂,或者对人类而言(对我自己;)不可行时,我自己使用它们。

答案 7 :(得分:-3)

此代码对您有用。

在sql server中使用游标的示例

DECLARE sampleCursor CURSOR FOR 
      SELECT K.Id FROM TableA K WHERE ....;
OPEN sampleCursor
FETCH NEXT FROM sampleCursor INTO @Id
WHILE @@FETCH_STATUS <> -1
BEGIN

UPDATE TableB
   SET 
      ...