Using a cursor with dynamic SQL in a stored procedure
Asked Answered
O

7

62

I have a dynamic SQL statement I've created in a stored procedure. I need to iterate over the results using a cursor. I'm having a hard time figuring out the right syntax. Here's what I'm doing.

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

What's the right way to do this?

Overnice answered 25/6, 2009 at 19:51 Comment(5)
The "right" way is to not use dynamic sql or cursors. Can you give a real example of what you are trying to accomplish?Predigestion
Exactly - two big NO-NOs of SQL server in a single stored proc :-)Jessabell
Dynamic SQL is not necessarily evil, especially if you use sp_executesql instead of EXEC. Cursors, though are cursed.Dor
If you parametrize the target database name for example, is there an alternative to dynamic SQL? E.g. Procs talking to Production vs Test environment databases.Spiral
There are certain problems though that are inexpressible without the use of dynamic sql or even cursors for that matter.Plumule
V
140

A cursor will only accept a select statement, so if the SQL really needs to be dynamic make the declare cursor part of the statement you are executing. For the below to work your server will have to be using global cursors.

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

If you need to avoid using the global cursors, you could also insert the results of your dynamic SQL into a temporary table, and then use that table to populate your cursor.

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
Vierno answered 25/6, 2009 at 20:1 Comment(0)
K
27

This code is a very good example for a dynamic column with a cursor, since you cannot use '+' in @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
Klina answered 7/7, 2011 at 19:42 Comment(4)
This works for me, and avoids global cursors and temporary tables. ThanksElma
Perfect, very useful thanks!. As a note, it seems that the cursor must also be opened within the dynamic sql -- SQL Server seems to think the cursor is not initialized, and was throwing an error for me if done after the dynamic stuff.Overtone
I can't see how concatenating @query into the sql is going to work here. I think you're going to get LIKE '%'xx'%';, assuming @query is set to 'xx', which is invalid sql. and the docs for SP_EXECUTESQL state: "@stmt must be either a Unicode constant or a Unicode variable.", meaning you can't build the string and execute it immediately without a temporary variable.Ottillia
Would be even more useful if it explained why the OPEN CURSOR statement needs to be inside the dynamic SQL, where one would not expect to have to put it.Quietly
K
4

Working with a non-relational database (IDMS anyone?) over an ODBC connection qualifies as one of those times where cursors and dynamic SQL seems the only route.

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

takes 45 minutes to respond while re-written to use keysets without the in clause will run in under 1 second:

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

If the in statement for column B contains 1145 rows, using a cursor to create indidivudal statements and execute them as dynamic SQL is far faster than using the in clause. Silly hey?

And yes, there's no time in a relational database that cursor's should be used. I just can't believe I've come across an instance where a cursor loop is several magnitudes quicker.

Kampmann answered 22/9, 2010 at 21:44 Comment(0)
D
3

First off, avoid using a cursor if at all possible. Here are some resources for rooting it out when it seems you can't do without:

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

Row-By-Row Processing Without Cursor

That said, though, you may be stuck with one after all--I don't know enough from your question to be sure that either of those apply. If that's the case, you've got a different problem--the select statement for your cursor must be an actual SELECT statement, not an EXECUTE statement. You're stuck.

But see the answer from cmsjr (which came in while I was writing) about using a temp table. I'd avoid global cursors even more than "plain" ones....

Dor answered 25/6, 2009 at 20:12 Comment(2)
Just an FYI - Both those articles require you to register to view now.Buckshee
The 2nd example is really bad. He creates a stored procedure and a trigger just to avoid using cursors. I doubt that his approach is better/more performant than just a simple forward cursor.Sungsungari
D
1

After recently switching from Oracle to SQL Server (employer preference), I notice cursor support in SQL Server is lagging. Cursors are not always evil, sometimes required, sometimes much faster, and sometimes cleaner than trying to tune a complex query by re-arranging or adding optimization hints. The "cursors are evil" opinion is much more prominent in the SQL Server community.

So I guess this answer is to switch to Oracle or give MS a clue.

Dosage answered 24/5, 2011 at 16:14 Comment(0)
M
0

Another option in SQL Server is to do all of your dynamic querying into table variable in a stored proc, then use a cursor to query and process that. As to the dreaded cursor debate :), I have seen studies that show that in some situations, a cursor can actually be faster if properly set up. I use them myself when the required query is too complex, or just not humanly (for me ;) ) possible.

Meshwork answered 9/5, 2019 at 22:19 Comment(0)
H
-3

this code can be useful for you.

example of cursor use in 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 
      ...
Hainaut answered 30/11, 2012 at 8:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.