Ok, I'm totally at a loss - this bit of code used to work, and now suddenly doesn't....
declare GetAllCodes cursor local for
select ac.activationCodePKID from ActivationCodes ac
left join OrdersLineItems od on ac.activationCodePKID = od.activationCodePKID
where od.activationCodePKID is null and ac.internalorderPKID is not null
and ac.campaignID is not null
and ac.enteredBy like 'ProcessRequest|Entitlement%'
RAISERROR ('step completed', 0, 1) WITH NOWAIT
open GetAllCodes
while (1=1)
begin
RAISERROR ('entering cursor', 0, 1) WITH NOWAIT
fetch next from GetAllCodes into #activationCodePKID
if (##fetch_status <> 0)
begin
DEALLOCATE GetAllCodes
break
end
exec fixOrder #activationCodePKID, 6, 7
end
It seems to just get stuck in a loop...I've commented out the exec statement, and just put in a print statment, but it just keeps going. The 'step completed' print statement gets printed, and so does the 'entering cursor' statement. And then nothing.... just hangs. The query itself returns 192 rows, so to just loop over, it should loop over 192 times and then break out and end. Ideas?
EDIT:
I added this:
declare #var varchar(10)
set #var = 'here: ' + cast(##fetch_status as varchar(10))
RAISERROR (#var, 0, 1) WITH NOWAIT
...right after the fetch next from GetAllCodes into #activationCodePKID statement - still nothing. The 'entering cursor' still gets printed, but then it just hangs...
EDIT 2:
I stripped out a lot of stuff, and added this right after the 'declare cursor' statement, to see if I can output ANYTHING...
RAISERROR ('query done', 0, 1) WITH NOWAIT
open GetAllCodes
fetch next from GetAllCodes into #activationCodePKID
close GetAllCodes
deallocate GetAllCodes
Still hangs... so then I took out the 'fetch statement', and it seemed to not hang anymore. It didn't do anything, obviously, because I don't have it do anything, but it completed execution.
Which makes me think as to why the fetch statement is hanging. Is there some server setting that could impact this? Some memory issues? Hardware issues?
Is there a reason the cursor is structured like that?
Usually cursors are built like so:
declare #dbName varchar(max);
declare myCursor cursor for select [name] from sys.databases;
open myCursor;
fetch next from myCursor into #dbName;
while (##fetch_status = 0)
begin
print #dbName;
fetch next from myCursor into #dbName;
end;
close myCursor;
deallocate myCursor;
Whenever they get stuck in a look will be because the fetch status isn't been set to zero (usual cause for me, is missing the "fetch next").
--
EDIT:
Can you check in the Activity monitor and see if that piece of SQL is running? Is there a deadlock?
My guess is, ##fetch_status is returning something other than 0, e.g. -1 or -2
MSDN Link
Maybe check to see what exactly is in there; that should give you a clue about what is wrong.
Related
I got this question that I can't find an answer:
In SQL Server, what's the difference between
DECLARE CRS_RQS INSENSITIVE CURSOR FOR
SELECT pRqsMtr FROM dtRqsMtr
WHERE pRqs = #pRqs
OPEN CRS_RQS
FETCH NEXT FROM CRS_RQS INTO #pRqsMtr
WHILE (##Fetch_Status = 0) AND (##Error = 0)
BEGIN
-- do some stuff...
FETCH NEXT FROM CRS_RQS INTO #pRqsMtr
END
CLOSE CRS_RQS
DEALLOCATE CRS_RQS
and this?
DECLARE #CRS_RQS CURSOR
SET #CRS_RQS = VOID
SET #CRS_RQS = CURSOR FOR
SELECT pRqsMtr FROM dtRqsMtr
WHERE pRqs = #pRqs
OPEN #CRS_RQS
FETCH NEXT FROM #CRS_RQS INTO #pRqsMtr
WHILE (##Fetch_Status = 0) AND (##Error = 0)
BEGIN
-- do some stuff...
FETCH NEXT FROM #CRS_RQS INTO #pRqsMtr
END
CLOSE #CRS_RQS
DEALLOCATE #CRS_RQS
and.. what's the best of the 2 above?
thanks in advance
If you are sure you actually need a cursor (it is the wrong choice more often than not), best is #CRS_RQS because:
it automatically leaves out some of the heavier default options from a "normal" cursor; and,
you don't have to close/declare; it does that automatically.
See this post from Itzik Ben-Gan on why he's switched to local cursor variables:
Overlooked T-SQL Gems
And see some of my performance research on the best options to use for a "real" cursor:
What impact can different cursor options have?
Follow-up on cursor options
The most salient point is to use LOCAL FAST_FORWARD unless you know you need different options (all the options are described in the documentation).
SQL-Server Code:
Declare #offers VARCHAR(100)
Declare #offers_seq VARCHAR(100)
Declare Result Cursor
For Select Top 1 Offers,Offers_seq From [REZJQWB01]..ActiveBooking_OffersDetails_Seq
Open Result
While ##fetch_status=0
Begin
Fetch Next From Result Into #offers, #offers_seq
Declare #value VARCHAR(100) = #offers
While len(#value) >= 1
Begin
Set #value = substring(#value,charindex(';',#value)+1,len(#value))
Print #value
End
End
Close Result
Deallocate Result
What I'm trying to accomplish here is to split a set of delimited values present in one cell and then creating a cursor for the complete column. The first time I run this code it gives the below output:
2;6;7;8;9;12;13;14;17;19;21;
6;7;8;9;12;13;14;17;19;21;
7;8;9;12;13;14;17;19;21;
8;9;12;13;14;17;19;21;
9;12;13;14;17;19;21;
12;13;14;17;19;21;
13;14;17;19;21;
14;17;19;21;
17;19;21;
19;21;
21;
2;6;7;8;9;12;13;14;17;19;21;
6;7;8;9;12;13;14;17;19;21;
7;8;9;12;13;14;17;19;21;
8;9;12;13;14;17;19;21;
9;12;13;14;17;19;21;
12;13;14;17;19;21;
13;14;17;19;21;
14;17;19;21;
17;19;21;
19;21;
21;
Ideally It should Print only once but I'm not sure why the loop runs twice. The second time I run this, it gives the output as : 'Command(s) completed successfully'.
Kindly help.
Thanks.
The reason it is running twice is that you aren't doing the fetch until after you've already checked the ##fetch_status.
The steps look like this:
Check ##fetch_status, which is zero, since nothing has been fetched.
Fetch a result
Run your substring code
Check ##fetch_status again, which is still zero, because a record was fetched in the previous step
Fetch another result, which fails but the cursor is still pointing at the same row as before
Run your substring code again, same result
Check ##fetch_status again, which now returns -1 because the previous fetch failed.
For the same reason as you get two results from running it once, you get nothing the second time, because ##fetch_status is -1 from the previous execution. To fix both issues, you need to fetch before checking the status. Usually you'll see one of the following methods employed (psuedocode left as an exercise for you to implement). Typically I use the first option, but some find the second is easier to read:
-- (declare and open cursor)
while 1=1 begin
fetch next from cursor
if ##fetch_status <> 0 break;
-- (do stuff)
end
or
-- (declare and open cursor)
fetch next from cursor
while ##fetch_status = 0 begin
-- (do stuff)
fetch next from cursor
end
Correct Code:
Declare #offers VARCHAR(100)
Declare #offers_seq VARCHAR(100)
Declare Result Cursor
For Select Top 1 Offers,Offers_seq From [REZJQWB01]..ActiveBooking_OffersDetails_Seq
Open Result
While 1=1
Begin
Fetch Next From Result Into #offers, #offers_seq
If ##fetch_status <> 0 Break;
Declare #value VARCHAR(100) = #offers
While len(#value) > 1
Begin
Set #value = substring(#value,charindex(';',#value,2)+1,len(#value))
Set #value = ';'+#value
If len(#value) <= 1 Break;
Print #value
End
End
Close Result
Deallocate Result
Result:
;6;7;8;9;12;13;14;17;19;21;
;7;8;9;12;13;14;17;19;21;
;8;9;12;13;14;17;19;21;
;9;12;13;14;17;19;21;
;12;13;14;17;19;21;
;13;14;17;19;21;
;14;17;19;21;
;17;19;21;
;19;21;
;21;
In T-SQL, when iterating results from a cursor, it seems to be common practice to repeat the FETCH statement before the WHILE loop. The below example from Microsoft:
DECLARE Employee_Cursor CURSOR FOR
SELECT EmployeeID, Title FROM AdventureWorks2012.HumanResources.Employee
WHERE JobTitle = 'Marketing Specialist';
OPEN Employee_Cursor;
FETCH NEXT FROM Employee_Cursor;
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM Employee_Cursor;
END;
CLOSE Employee_Cursor;
DEALLOCATE Employee_Cursor;
GO
(Notice how FETCH NEXT FROM Employee_Cursor; appears twice.)
If the FETCH selects into a long list of variables, then we have a large duplicated statement which is both ugly and of course, "non-DRY" code.
I'm not aware of a post-condition control-of-flow T-SQL statement so it seems I'd have to resort to a WHILE(TRUE) and then BREAK when ##FETCH_STATUS is not zero. This feels clunky to me.
What other options do I have?
There's a good structure posted online by Chris Oldwood which does it quite elegantly:
DECLARE #done bit = 0
WHILE (#done = 0)
BEGIN
-- Get the next author.
FETCH NEXT FROM authors_cursor
INTO #au_id, #au_fname, #au_lname
IF (##FETCH_STATUS <> 0)
BEGIN
SET #done = 1
CONTINUE
END
--
-- stuff done here with inner cursor elided
--
END
This is what I've resorted to (oh the shame of it):
WHILE (1=1)
BEGIN
FETCH NEXT FROM C1 INTO
#foo,
#bar,
#bufar,
#fubar,
#bah,
#fu,
#foobar,
#another,
#column,
#in,
#the,
#long,
#list,
#of,
#variables,
#used,
#to,
#retrieve,
#all,
#values,
#for,
#conversion
IF (##FETCH_STATUS <> 0)
BEGIN
BREAK
END
-- Use the variables here
END
CLOSE C1
DEALLOCATE C1
You can see why I posted a question. I don't like how the control of flow is hidden in an if statement when it should be in the while.
The first Fetch shouldn't be a Fetch next, just a fetch.
Then you're not repeating yourself.
I'd spend more effort getting rid of the cursor, and less on DRY dogma, (but if it really matters, you could use a GOTO :) - Sorry, M. Dijkstra)
GOTO Dry
WHILE ##FETCH_STATUS = 0
BEGIN
--- stuff here
Dry:
FETCH NEXT FROM Employee_Cursor;
END;
Here is my humble contribution. Single FETCH statement, no GOTO, no BREAK, no CONTINUE.
-- Sample table
DROP TABLE IF EXISTS #tblEmployee;
CREATE TABLE #tblEmployee(ID int, Title varchar(100));
INSERT INTO #tblEmployee VALUES (1, 'First One'), (2, 'Second Two'), (3, 'Third Three'), (3, '4th Four');
-- Cursor with one FETCH statement
DECLARE #bEOF bit=0, #sTitle varchar(200), #nID int;
DECLARE cur CURSOR LOCAL FOR SELECT ID, Title FROM #tblEmployee;
OPEN cur;
WHILE #bEOF=0
BEGIN
FETCH NEXT FROM cur INTO #nID, #sTitle;
IF ##FETCH_STATUS<>0
SET #bEOF=1;
ELSE
BEGIN
PRINT Str(#nID)+'. '+#sTitle;
END;
END;
CLOSE cur;
DEALLOCATE cur;
-- Cleanup
DROP TABLE IF EXISTS #tblEmployee;
It is obvious that a cursor is the pointer to the current row in the recordset. But mere pointing isn't gonna make sense unless it can be used. Here comes the Fetch statement into the scene. This takes data from the recordset, stores it in the variable(s) provided. so if you remove the first fetch statement the while loop won't work as there is not "FETCHED" record for manipulation, if you remove the last fetch statement, the "while" will not loop-through.
So it is necessary to have both the fetch statement to loop-through the complete recordset.
Simply said you can't... that's just how most where statements in SQL work. You need to get the first line before the loop and then do it again in the while statement.
The better question how to get rid of the cursor and try to solve your query without it.
How do I check if a cursor is open or not? Because many times I am encountering the error 'Cursor already exists'. Please let me know how can I check whether a cursor is already in open status.
In fact I have closed as well as Deallocated it at the end (CLOSE ppm_cursor; DEALLOCATE ppm_cursor;) But Still i am getting the same error what could be the reason.
You can use the CURSOR_STATUS function to determine its state.
IF CURSOR_STATUS('global','myCursor')>=-1
BEGIN
DEALLOCATE myCursor
END
Close the cursor, if it is empty then deallocate it:
IF CURSOR_STATUS('global','myCursor') >= -1
BEGIN
IF CURSOR_STATUS('global','myCursor') > -1
BEGIN
CLOSE myCursor
END
DEALLOCATE myCursor
END
Just Small change to what Gary W mentioned, adding 'SELECT':
IF (SELECT CURSOR_STATUS('global','myCursor')) >= -1
BEGIN
DEALLOCATE myCursor
END
http://social.msdn.microsoft.com/Forums/en/sqlgetstarted/thread/eb268010-75fd-4c04-9fe8-0bc33ccf9357
I rarely employ cursors, but I just discovered one other item that can bite you here, the scope of the cursor name.
If the database CURSOR_DEFAULT is global, you will get the "cursor already exists" error if you declare a cursor in a stored procedure with a particular name (eg "cur"), and while that cursor is open you call another stored procedure which declares and opens a cursor with the same name (eg "cur"). The error will occur in the nested stored procedure when it attempts to open "cur".
Run this bit of sql to see your CURSOR_DEFAULT:
select is_local_cursor_default from sys.databases where name = '[your database name]'
If this value is "0" then how you name your nested cursor matters!
This happened to me when a stored procedure running in SSMS encountered an error during the loop, while the cursor was in use to iterate over records and before the it was closed. To fix it I added extra code in the CATCH block to close the cursor if it is still open (using CURSOR_STATUS as other answers here suggest).
Expanding on a previous answer, this proc is useful to call if you are worried that the cursor may have been left open or allocated
CREATE OR ALTER PROCEDURE dbo.CloseAndDeallocateCursor
#cursorName NVARCHAR(80)
AS
BEGIN
IF CURSOR_STATUS('global', #cursorName) >= -1
BEGIN
DECLARE #SQL NVARCHAR(91)
IF CURSOR_STATUS('global', #cursorName) > -1
BEGIN
SET #SQL = N'CLOSE ' + #cursorName
EXEC sp_executeSQL #SQL
END
SET #SQL = N'DEALLOCATE ' + #cursorName
EXEC sp_executeSQL #SQL
END
END
GO
... and then sample usage ...
EXEC dbo.CloseAndDeallocateCursor 'myCursor'
DECLARE myCursor STATIC
FOR SELECT * FROM blah
I'm using an UPDATE cursor as follows on SQL 2005:
DECLARE myCursor CURSOR FOR
SELECT RowID, Value FROM myTable
FOR UPDATE OF Value;
OPEN myCursor;
FETCH NEXT FROM myCursor
WHILE (##FETCH_STATUS <> -1)
UPDATE myTable SET Value = 42
WHERE CURRENT OF myCursor
FETCH NEXT FROM myCursor
END
CLOSE myCursor
DEALLOCATE myCursor
(Thanks to Matt for his correct answer on my prior question concerning this cursor syntax. And yes, I do need a cursor, because each row's new value is actually based on a complicated calculation that depends on the prior rows.)
This works correctly, updating all the Values. The problem is that it returns a result set for each row updated, consisting of RowID, Value (interestingly, its showing the result from before the row is updated). Eventually, I get the following error:
The query has exceeded the maximum
number of result sets that can be
displayed in the results grid. Only
the first 100 result sets are
displayed in the grid.
Any way to suppress these result sets? SET NOCOUNT ON doesn't do the trick.
Is this just an issue I see running it directly in SSMS? Or will it actually try to return hundreds of result sets when I put this cursor inside a stored proc?
EDIT: Looks like it has nothing to do with the UPDATE.
Using FETCH NEXT FROM myCURSOR the way I am actually does return a result set of the next row from the cursor.
If I change it to FETCH NEXT FROM myCURSOR INTO #variables, then it doesn't return a result set.
So I guess the question now is: Since I'm using WHERE CURRENT OF, I don't really need the variable. I guess I can put them in just to suppress the result set, but is there a better way to do it?
Note while begin ... end
and Fetch into
Declare #row int
Declare #value int
DECLARE myCursor CURSOR FOR
SELECT RowID, Value FROM myTable
FOR UPDATE OF Value;
OPEN myCursor;
FETCH NEXT FROM myCursor into #row, #value
WHILE (##FETCH_STATUS <> 1)
begin
UPDATE myTable SET Value = 42
WHERE CURRENT OF myCursor
FETCH NEXT FROM myCursor into #row, #value
END
CLOSE myCursor
DEALLOCATE myCursor