Difference declaring cursor like variable with # or not - sql

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).

Related

Best way to replace cursor in SQL Server 2008

I want to replace the cursor code from my stored procedure
DECLARE CursorXD CURSOR FOR
SELECT
IDOrdre, Quantity,fabnum
FROM prod_ordreplanificationdetail
WHERE fab = #num
AND ordre = #ord
OPEN CursorXD
FETCH NEXT FROM CursorXD INTO #correctionnumsap, #correctionquantite, #correctionnumfabrication
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC INSERT#prod #idordre = #correctionnumsap,
#quantite = #correctionquantiteneg,
#fabnum = #correctionnumfabrication
FETCH NEXT FROM CursorXD INTO #correctionnumsap, #correctionquantite, #correctionnumfabrication
END
CLOSE CursorXD
DEALLOCATE CursorXD
What is the best way to replace this cursor to increase the performance??
Any suggestion?
Here's a option, but I made a couple of assumptions
INSERT INTO prod
SELECT IDOrdre, Quantity,fabnum
FROM prod_ordreplanificationdetail
WHERE fab=#num
AND ordre=#ord
Assumptions:
SP INSERT#prod only does an INSERT and no other data manipulation
SP INSERT#prod inserts into a table called prod and there are only three columns in the table
You can use WHILE loop instead of CURSOR by maintaining the nth row value. It is too good performance wise compare to CURSOR. If you tell clearly what you need, so we can walk into same road.

How to avoid duplicated FETCH in T-SQL when using a cursor?

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 to check if cursor exists (open status)

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

sql server cursor stuck in loop

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.

FOR UPDATE cursor is returning a result set for each row!

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