Get Multiple Values in SQL Server Cursor - sql

I have a cursor containing several columns from the row it brings back that I would like to process at once. I notice most of the examples I've seeing on how to use cursors show them assigning a particular column from the cursor to a scalar value one at a time, then moving to the next row,
e.g.
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #name
WHILE ##FETCH_STATUS = 0
BEGIN
--Do Stuff with #name scalar value, then get next row from cursor
FETCH NEXT FROM db_cursor INTO #name
END
What I want to know is if it's possible to do something like the following:
OPEN db_cursor
FETCH NEXT FROM db_cursor;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #myName = db_cursor.name;
SET #myAge = db_cursor.age;
SET #myFavoriteColor = db_cursor.favoriteColor;
--Do stuff with scalar values
FETCH NEXT FROM db_cursor;
END
Help is always appreciated.

This should work:
DECLARE db_cursor CURSOR FOR SELECT name, age, color FROM table;
DECLARE #myName VARCHAR(256);
DECLARE #myAge INT;
DECLARE #myFavoriteColor VARCHAR(40);
OPEN db_cursor;
FETCH NEXT FROM db_cursor INTO #myName, #myAge, #myFavoriteColor;
WHILE ##FETCH_STATUS = 0
BEGIN
--Do stuff with scalar values
FETCH NEXT FROM db_cursor INTO #myName, #myAge, #myFavoriteColor;
END;
CLOSE db_cursor;
DEALLOCATE db_cursor;

Do not use ##fetch_status - this will return status from the last cursor in the current connection. Use the example below:
declare #sqCur cursor;
declare #data varchar(1000);
declare #i int = 0, #lastNum int, #rowNum int;
set #sqCur = cursor local static read_only for
select
row_number() over (order by(select null)) as RowNum
,Data -- you fields
from YourIntTable
open #cur
begin try
fetch last from #cur into #lastNum, #data
fetch absolute 1 from #cur into #rowNum, #data --start from the beginning and get first value
while #i < #lastNum
begin
set #i += 1
--Do your job here
print #data
fetch next from #cur into #rowNum, #data
end
end try
begin catch
close #cur --|
deallocate #cur --|-remove this 3 lines if you do not throw
;throw --|
end catch
close #cur
deallocate #cur

Related

Delete multiple records from table through cursor in sql server

there are number of test IP's which I would like to remove through system defined sp
exec sp_delete_firewall_rule from sys.firewall_rules table in sql server
I am using below cursor but its not working
declare #name nvarchar(max)
declare cur CURSOR LOCAL for
select #name from sys.firewall_rules where [name] like '%TestIP%'
open cur
fetch next from cur into #name
while ##FETCH_STATUS = 0 BEGIN
exec sp_delete_firewall_rule #name
fetch next from cur into #name
END
close cur
deallocate cur
It worked for me, you just need to change a couple of things in your code.
In the select list include the table ColumnName [name] instead of variable. You did not pass any value to the variable so this gives a NULL result.
Include SP parameter while executing exec sp_delete_firewall_rule #name = #name1;
I have these IP’s in my firewall rules:
With the below code I am deleting the IP’s which has a name like TestIP1.
DECLARE #name1 nvarchar(128);
DECLARE MyCursor CURSOR FOR
SELECT [name] from sys.firewall_rules where [name] like '%TestIP1%';
OPEN MyCursor;
FETCH FROM MyCursor into #name1
WHILE ##FETCH_STATUS = 0 BEGIN
EXEC sp_delete_firewall_rule #name = #name1 ;
FETCH next from MyCursor into #name1
END
CLOSE MyCursor;
DEALLOCATE MyCursor;
GO
Now the result shows only 1 IP which is not included in the above delete list.

Cursor not working in stored procedure

I am using cursor in ms sql server
DECLARE #LineOfAuthoritySubString varchar(100);
declare #tbProductId int;
declare #tbLineOfAuthorityId int;
DECLARE #MyCursor CURSOR
SET #MyCursor = CURSOR FAST_FORWARD
FOR
Select TempLineOfAuthority FROM #tbTempLineOfAuthority
OPEN #MyCursor
FETCH NEXT FROM #MyCursor
INTO #LineOfAuthoritySubString
WHILE ##FETCH_STATUS = 0
BEGIN
Set #tbLineOfAuthorityId = (Select LineOfAuthority
from tbLineOfAuthority where LineOfAuthorityX = #LineOfAuthorityNameSubString);
INSERT INTO tbProductLineOfAuthority(ProductId, LineOfAuthortyId)
VALUES(#tbProductId, #tbLineOfAuthorityId);
FETCH NEXT FROM #MyCursor
INTO #LineOfAuthoritySubString
END
CLOSE #MyCursor
DEALLOCATE #MyCursor
It's not working. It is assigning null value to the variable #tbLineOfAuthorityId.
You are inserting cursor value in #LineOfAuthoritySubString and in where condition #LineOfAuthorityNameSubString variable is used
Both variable name is mismatched.
FETCH NEXT FROM #MyCursor INTO #LineOfAuthoritySubString
IN above you are fetching value in variable #LineOfAuthoritySubString .
Set #tbLineOfAuthorityId = (Select LineOfAuthority from tbLineOfAuthority where LineOfAuthorityX = #LineOfAuthorityNameSubString);
But when you are selecting from table tbLineOfAuthority, observe the variable you are using in where query.
It should be #LineOfAuthoritySubString and not #LineOfAuthorityNameSubString
pleae try below edited query if it works.
DECLARE #LineOfAuthoritySubString varchar(100);
declare #tbProductId int;
declare #tbLineOfAuthorityId int;
DECLARE #MyCursor CURSOR
SET #MyCursor = CURSOR FAST_FORWARD
FOR
Select TempLineOfAuthority FROM #tbTempLineOfAuthority
OPEN #MyCursor
FETCH NEXT FROM #MyCursor
INTO #LineOfAuthoritySubString
WHILE ##FETCH_STATUS = 0
BEGIN
Set #tbLineOfAuthorityId = (Select LineOfAuthority
from tbLineOfAuthority where LineOfAuthorityX = #LineOfAuthoritySubString);
INSERT INTO tbProductLineOfAuthority(ProductId, LineOfAuthortyId)
VALUES(#tbProductId, #tbLineOfAuthorityId);
FETCH NEXT FROM #MyCursor
INTO #LineOfAuthoritySubString
END
CLOSE #MyCursor
DEALLOCATE #MyCursor

Use "WHERE CURRENT OF" clause to update only the specific row on which the CURSOR is positioned in SQL

I have the following cursor in SQL:
DECLARE #Script varchar(max)
DECLARE #getScript CURSOR
SET #getScript = CURSOR FOR
SELECT [Script]
FROM ScriptTable
OPEN #getScript
FETCH NEXT
FROM #getScript INTO #Script
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
EXEC(#Script) --Working part. This executes the query stored in the Script column.
--For example INSERT INTO zTest VALUES(VAL1, VAL2, etc etc..)
UPDATE ScriptTable
SET DateDone = GETDATE(), IsDone = 1, Err = NULL
FETCH NEXT
FROM #getScript INTO #Script
END TRY
BEGIN CATCH
DECLARE #Err varchar(max)
SET #Err = ERROR_MESSAGE()
UPDATE ScriptTable
SET DateDone = GETDATE(), Err = #Err
END CATCH
END
CLOSE #getScript
DEALLOCATE #getScript
Q1:
Currently, I am getting the values inserted into the "zTest" table specified in my comments when I execute EXEC(#Script).
However, the second part where the "Update ScriptTable" is, updates all the rows in my Script Table. I know I need to specify the ID for the appropriate row that the cursor is moving through. Question is, how can I do that? I wan't to only update the appropriate row, move to the next then update that one.
Q2:
My next question is, in the CATCH block, I think I am creating an infinite loop as soon as there is an error in one of the queries in the Script Column of the ScriptTable as when I look at results, it just keeps going and going. I don't want to BREAK; the procedure as I want to write an error to the Err column and continue with the next rows till it reaches the end of #Script, then stop.
IDENT_CURRENT, Scope_Identity etc doesn't work because I haven't inserted anything into the Scripts Table.
Please help.
Regarding Q1, you have to have a primary key in order to use the cursor for updating (though there are workarounds).
In general you'll want syntax something like this:
update ScriptTable
SET DateDone = GETDATE(), IsDone = 1, Err = NULL
where ID of #getScript
Regarding Q2, it make sense that it's an infinite loop. When you use the TRY and CATCH clauses and it fails it doesn't execute any of the syntax in the TRY "area".
Therefor the FETCH NEXT gets skipped, and in the next loop the same error happens again.
Try to make sure there is always a FETCH NEXT in the loop.
Hope this helps you out a bit.
Here is my final code if anyone is interested:
DECLARE #Script varchar(max)
DECLARE #getScript CURSOR
SET #getScript = CURSOR FOR
SELECT [Script]
FROM ScriptControl
OPEN #getScript
FETCH NEXT
FROM #getScript INTO #Script
DECLARE #Counter int = 1
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
EXEC(#Script)
UPDATE ScriptControl
SET DateDone = GETDATE(), IsDone = 1, Error = NULL WHERE ID = #Counter
FETCH NEXT
FROM #getScript INTO #Script
SET #Counter = (#Counter + 1)
END TRY
BEGIN CATCH
DECLARE #Err varchar(max)
SET #Err = ERROR_MESSAGE()
UPDATE ScriptControl
SET CSC_EOD_DateDone = NULL, CSC_EOD_Err = #Err, CSC_EOD_IsDone = 0 WHERE CURRENT OF #getScript
FETCH NEXT
FROM #getScript INTO #Script
SET #Counter = (#Counter + 1)
END CATCH
END
CLOSE #getScript
DEALLOCATE #getScript
This:
DECLARE #ScriptControlId INT, #Script VARCHAR(MAX)
DECLARE #getScript CURSOR
SET #getScript = CURSOR FOR
SELECT [ID], [Script]
FROM ScriptControl
OPEN #getScript
FETCH NEXT
FROM #getScript INTO #ScriptControlId, #Script
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
EXEC(#Script)
UPDATE ScriptControl
SET DateDone = GETDATE(), IsDone = 1, Error = NULL
WHERE ID = #ScriptControlId
FETCH NEXT FROM #getScript INTO #ScriptControlId, #Script
END TRY
BEGIN CATCH
DECLARE #Err VARCHAR(MAX) = ERROR_MESSAGE()
UPDATE ScriptControl
SET DateDone = NULL, Error = #Err, IsDone = 0
WHERE CURRENT OF #getScript
FETCH NEXT FROM #getScript INTO #ScriptControlId, #Script
END CATCH
END
CLOSE #getScript
DEALLOCATE #getScript
Or this:
DECLARE #Script VARCHAR(MAX)
DECLARE #getScript CURSOR
SET #getScript = CURSOR FOR
SELECT [Script]
FROM ScriptControl
OPEN #getScript
FETCH NEXT
FROM #getScript INTO #Script
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
EXEC(#Script)
UPDATE ScriptControl
SET DateDone = GETDATE(), IsDone = 1, Error = NULL
WHERE CURRENT OF #getScript
FETCH NEXT FROM #getScript INTO #Script
END TRY
BEGIN CATCH
DECLARE #Err VARCHAR(MAX) = ERROR_MESSAGE()
UPDATE ScriptControl
SET DateDone = NULL, Error = #Err, IsDone = 0
WHERE CURRENT OF #getScript
FETCH NEXT FROM #getScript INTO #Script
END CATCH
END
CLOSE #getScript
DEALLOCATE #getScript

Continue from top in SQL SERVER Cursor?

In C# language we use continue statement in a loop to move to next iteration. But in using Cursor in TSQL how can I perform the same. Let say I have,
DECLARE db_cursor CURSOR FOR SELECT age, name, color FROM table;
DECLARE #myName VARCHAR(256);
DECLARE #myAge INT;
DECLARE #myFavoriteColor VARCHAR(40);
OPEN db_cursor;
FETCH NEXT FROM db_cursor INTO #myName, #myAge, #myFavoriteColor;
WHILE ##FETCH_STATUS = 0
BEGIN
--Do stuff
IF #myAge=1
BEGIN
-- Use continue here
END
--Do stuff
FETCH NEXT FROM db_cursor INTO #myName, #myAge, #myFavoriteColor;
END;
CLOSE db_cursor;
DEALLOCATE db_cursor;
CONTINUE does go back to the start of the WHILE loop, but it's not exactly like in C#, since the cursor looping idiom in T-SQL is broken into two separate statements, and the WHILE is the second of them - the cleanest, requiring the least repetition, may be our old friend GOTO:
DECLARE db_cursor CURSOR FOR SELECT age, name, color FROM table;
DECLARE #myName VARCHAR(256);
DECLARE #myAge INT;
DECLARE #myFavoriteColor VARCHAR(40);
OPEN db_cursor;
FETCH NEXT FROM db_cursor INTO #myName, #myAge, #myFavoriteColor;
WHILE ##FETCH_STATUS = 0
BEGIN
--Do stuff
IF #myAge=1
BEGIN
Goto Cont
END
--Do stuff
Cont:
FETCH NEXT FROM db_cursor INTO #myName, #myAge, #myFavoriteColor;
END;
CLOSE db_cursor;
DEALLOCATE db_cursor;
you can use CONTINUE in this manner
DECLARE db_cursor CURSOR FOR SELECT age, name, color FROM table;
DECLARE #myName VARCHAR(256);
DECLARE #myAge INT;
DECLARE #myFavoriteColor VARCHAR(40);
OPEN db_cursor;
FETCH NEXT FROM db_cursor INTO #myName, #myAge, #myFavoriteColor;
WHILE ##FETCH_STATUS = 0
BEGIN
--Do stuff
IF #myAge=1
BEGIN
FETCH NEXT FROM db_cursor INTO #myName, #myAge, #myFavoriteColor;
CONTINUE;
END
--Do stuff
FETCH NEXT FROM db_cursor INTO #myName, #myAge, #myFavoriteColor;
END;
CLOSE db_cursor;
DEALLOCATE db_cursor;
Try this one -
DECLARE
#myName VARCHAR(256)
, #myAge INT
, #myFavoriteColor VARCHAR(40)
DECLARE cursor_name CURSOR FAST_FORWARD READ_ONLY FOR
SELECT age, name, color
FROM [table]
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO
#myName
, #myAge
, #myFavoriteColor
WHILE ##FETCH_STATUS = 0 BEGIN
IF #myAge = 1 BEGIN
FETCH NEXT FROM db_cursor INTO
#myName
, #myAge
, #myFavoriteColor
END
FETCH NEXT FROM db_cursor INTO
#myName
, #myAge
, #myFavoriteColor
END
CLOSE db_cursor
DEALLOCATE db_cursor

Cursor inside cursor

Main problem is about changing the index of rows to 1,2,3.. where contact-id and type is the same. but all columns can contain exactly the same data because of some ex-employee messed up and update all rows by contact-id and type. somehow there are rows that aren't messed but index rows are same. It is total chaos.
I tried to use an inner cursor with the variables coming from the outer cursor.
But It seems that its stuck in the inner cursor.
A part of the query looks like this:
Fetch NEXT FROM OUTER_CURSOR INTO #CONTACT_ID, #TYPE
While (##FETCH_STATUS <> -1)
BEGIN
IF (##FETCH_STATUS <> -2)
DECLARE INNER_CURSOR Cursor
FOR
SELECT * FROM CONTACTS
where CONTACT_ID = #CONTACT_ID
and TYPE = #TYPE
Open INNER_CURSOR
Fetch NEXT FROM INNER_CURSOR
While (##FETCH_STATUS <> -1)
BEGIN
IF (##FETCH_STATUS <> -2)
What can be the problem? Is ##FETCH_STATUS ambiguous or something?
EDIT: everything looks fine if i don't use this code inside inner cursor:
UPDATE CONTACTS
SET INDEX_NO = #COUNTER
where current of INNER_CURSOR
EDIT: here is the big picture:
BEGIN TRAN
DECLARE #CONTACT_ID VARCHAR(15)
DECLARE #TYPE VARCHAR(15)
DECLARE #INDEX_NO SMALLINT
DECLARE #COUNTER SMALLINT
DECLARE #FETCH_STATUS INT
DECLARE OUTER_CURSOR CURSOR
FOR
SELECT CONTACT_ID, TYPE, INDEX_NO FROM CONTACTS
WHERE
CONTACT_ID IN (SELECT CONTACT_ID FROM dbo.CONTACTS
WHERE CONTACT_ID IN(...)
GROUP BY CONTACT_ID, TYPE, INDEX_NO
HAVING COUNT(*) > 1
OPEN OUTER_CURSOR
FETCH NEXT FROM OUTER_CURSOR INTO #CONTACT_ID, #TYPE, #INDEX_NO
WHILE (##FETCH_STATUS <> -1)
BEGIN
IF (##FETCH_STATUS <> -2)
SET #COUNTER = 1
DECLARE INNER_CURSOR CURSOR
FOR
SELECT * FROM CONTACTS
WHERE CONTACT_ID = #CONTACT_ID
AND TYPE = #TYPE
FOR UPDATE
OPEN INNER_CURSOR
FETCH NEXT FROM INNER_CURSOR
WHILE (##FETCH_STATUS <> -1)
BEGIN
IF (##FETCH_STATUS <> -2)
UPDATE CONTACTS
SET INDEX_NO = #COUNTER
WHERE CURRENT OF INNER_CURSOR
SET #COUNTER = #COUNTER + 1
FETCH NEXT FROM INNER_CURSOR
END
CLOSE INNER_CURSOR
DEALLOCATE INNER_CURSOR
FETCH NEXT FROM OUTER_CURSOR INTO #CONTACT_ID, #TYPE, #INDEX_NO
END
CLOSE OUTER_CURSOR
DEALLOCATE OUTER_CURSOR
COMMIT TRAN
You have a variety of problems. First, why are you using your specific ##FETCH_STATUS values? It should just be ##FETCH_STATUS = 0.
Second, you are not selecting your inner Cursor into anything. And I cannot think of any circumstance where you would select all fields in this way - spell them out!
Here's a sample to go by. Folder has a primary key of "ClientID" that is also a foreign key for Attend. I'm just printing all of the Attend UIDs, broken down by Folder ClientID:
Declare #ClientID int;
Declare #UID int;
DECLARE Cur1 CURSOR FOR
SELECT ClientID From Folder;
OPEN Cur1
FETCH NEXT FROM Cur1 INTO #ClientID;
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT 'Processing ClientID: ' + Cast(#ClientID as Varchar);
DECLARE Cur2 CURSOR FOR
SELECT UID FROM Attend Where ClientID=#ClientID;
OPEN Cur2;
FETCH NEXT FROM Cur2 INTO #UID;
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT 'Found UID: ' + Cast(#UID as Varchar);
FETCH NEXT FROM Cur2 INTO #UID;
END;
CLOSE Cur2;
DEALLOCATE Cur2;
FETCH NEXT FROM Cur1 INTO #ClientID;
END;
PRINT 'DONE';
CLOSE Cur1;
DEALLOCATE Cur1;
Finally, are you SURE you want to be doing something like this in a stored procedure? It is very easy to abuse stored procedures and often reflects problems in characterizing your problem. The sample I gave, for example, could be far more easily accomplished using standard select calls.
You could also sidestep nested cursor issues, general cursor issues, and global variable issues by avoiding the cursors entirely.
declare #rowid int
declare #rowid2 int
declare #id int
declare #type varchar(10)
declare #rows int
declare #rows2 int
declare #outer table (rowid int identity(1,1), id int, type varchar(100))
declare #inner table (rowid int identity(1,1), clientid int, whatever int)
insert into #outer (id, type)
Select id, type from sometable
select #rows = count(1) from #outer
while (#rows > 0)
Begin
select top 1 #rowid = rowid, #id = id, #type = type
from #outer
insert into #innner (clientid, whatever )
select clientid whatever from contacts where contactid = #id
select #rows2 = count(1) from #inner
while (#rows2 > 0)
Begin
select top 1 /* stuff you want into some variables */
/* Other statements you want to execute */
delete from #inner where rowid = #rowid2
select #rows2 = count(1) from #inner
End
delete from #outer where rowid = #rowid
select #rows = count(1) from #outer
End
Do you do any more fetches? You should show those as well. You're only showing us half the code.
It should look like:
FETCH NEXT FROM #Outer INTO ...
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #Inner...
OPEN #Inner
FETCH NEXT FROM #Inner INTO ...
WHILE ##FETCH_STATUS = 0
BEGIN
...
FETCH NEXT FROM #Inner INTO ...
END
CLOSE #Inner
DEALLOCATE #Inner
FETCH NEXT FROM #Outer INTO ...
END
CLOSE #Outer
DEALLOCATE #Outer
Also, make sure you do not name the cursors the same... and any code (check your triggers) that gets called does not use a cursor that is named the same. I've seen odd behavior from people using 'theCursor' in multiple layers of the stack.
This smells of something that should be done with a JOIN instead. Can you share the larger problem with us?
Hey, I should be able to get this down to a single statement, but I haven't had time to play with it further yet today and may not get to. In the mean-time, know that you should be able to edit the query for your inner cursor to create the row numbers as part of the query using the ROW_NUMBER() function. From there, you can fold the inner cursor into the outer by doing an INNER JOIN on it (you can join on a sub query). Finally, any SELECT statement can be converted to an UPDATE using this method:
UPDATE [YourTable/Alias]
SET [Column] = q.Value
FROM
(
... complicate select query here ...
) q
Where [YourTable/Alias] is a table or alias used in the select query.
I had the same problem,
what you have to do is declare the second cursor as:
DECLARE [second_cursor] Cursor LOCAL For
You see"CURSOR LOCAL FOR" instead of "CURSOR FOR"
I don't fully understand what was the problem with the "update current of cursor" but it is solved by using the fetch statement twice for the inner cursor:
FETCH NEXT FROM INNER_CURSOR
WHILE (##FETCH_STATUS <> -1)
BEGIN
UPDATE CONTACTS
SET INDEX_NO = #COUNTER
WHERE CURRENT OF INNER_CURSOR
SET #COUNTER = #COUNTER + 1
FETCH NEXT FROM INNER_CURSOR
FETCH NEXT FROM INNER_CURSOR
END