Nested T-SQL Cursors Not Executing Correctly - tsql

Before I get any militant cursor-bashing, let me say that I'm trying to use nested cursors to do something I only need to do once, but if I ran the operative stored procedure once for each user and agency I have to do it a few hundred times.
I thought a nested cursor in this case would save me some work, however, when I run this script it goes through the outer cursor only once, while the inner works just fine for that run. In the test case, the outer cursor set consists of two rows, and the inner one has about fifty. It goes through the first row of the outer cursor, and all fifty of the inner, but then it's done.
As you can see, I am saving off the result of the outer fetch (the '##fetch_status') so it doesn't interfere with the inner cursor.
I can't see what the problem is (obviously). Can anyone see what I can't?
declare #fetch_user int
declare #fetch_agency int
declare user_cursor cursor for
select upn from #users
open user_cursor
fetch next from user_cursor into #upn
select #fetch_user = ##fetch_status
while #fetch_user = 0
begin
declare agency_cursor cursor for
select agency, subagency from agency_system where system_id = 1
open agency_cursor
fetch next from agency_cursor into #agency, #subagency
select #fetch_agency = ##fetch_status
while #fetch_agency = 0
begin
select #upn, #agency, #subagency
EXEC AddUserToAgencyInRole
#upn
, #agency
, #subagency
, #system_id
, #role_id
, #response output
fetch next from agency_cursor into #agency, #subagency
select #fetch_agency = ##fetch_status
end
close agency_cursor
deallocate agency_cursor
fetch next from user_cursor into #upn
select #fetch_user = ##fetch_status
end
close user_cursor
deallocate user_cursor

The code looks like it should work. Throw in a count at the start:
select count(*) from #users
to double check the number of rows in #users ?

I'm not sure about troubleshooting the nested cursor other than that there is a way to get rid of it and have only one cursor.
Make this select statement using a cross-join:
SELECT u.upn, a.agency, a.subagency
FROM #users u, agency_system a
WHERE a.system_id = 1
Use that as your cursor definition. It should have every combination of user and agency/subagency.

I agree with Andomar it should work. This test case goes through the outer loop 4 times and the inner loop twice per iteration. (Which matches the number of rows in the respective tables)
set nocount on
DECLARE #upn INT, #agency INT, #subagency INT
CREATE TABLE #users (upn INT)
insert into #users select 1 union select 2 UNION select 3 UNION select 4
CREATE TABLE #agency_system(
agency INT,
subagency INT,
system_id INT)
insert into #agency_system
select 1,1,1 UNION select 2,2,1
declare #fetch_user int
declare #fetch_agency int
declare user_cursor cursor for
select upn from #users
open user_cursor
fetch next from user_cursor into #upn
select #fetch_user = ##fetch_status
while #fetch_user = 0
begin
PRINT 'In Outer While Loop'
declare agency_cursor cursor for
select agency, subagency from #agency_system where system_id = 1
open agency_cursor
fetch next from agency_cursor into #agency, #subagency
select #fetch_agency = ##fetch_status
while #fetch_agency = 0
begin
PRINT 'In Inner While Loop'
fetch next from agency_cursor into #agency, #subagency
select #fetch_agency = ##fetch_status
end
close agency_cursor
deallocate agency_cursor
fetch next from user_cursor into #upn
select #fetch_user = ##fetch_status
end
close user_cursor
deallocate user_cursor
drop TABLE #users
drop TABLE #agency_system

I appreciate all the responses.
I ended up eliminating the outer cursor and just manually running the inner one. This saved me from manually entering 393 separate entries. I just had to run the script three times.

Related

Select / Delete of Three self referencing many to many relationships SQL Query

I have a table ProjectLayerContent designed as following:
ProjectLayerContentID [INT]
ProjectLayerContentName [NVARCHAR(200)]
ProjectLayerContentParentID [INT]
ScopeIsProjectLayerContent [BIT]
ScopeIsProjectLayerContentID [INT]
DataTypeIsContentOfContent [BIT]
ContentOfContentID [INT]
The project layer content might have a parent, and might not, also the same for the children.
The project layer content scope might include another project layer content.
The project layer content data type might be a type of another content.
What I am trying to do is in the delete process, I want to show a list includes the count of what's is going to be deleted if you will delete this project layer content.
Pretty much this is what I got into, but I know those results isn't right
DECLARE #ProjectLayerContentID INT = 1;
DECLARE #resTable TABLE (
ProjectLayerContentIDP int,
ProjLContentType nvarchar(100));
DECLARE #ProjectLayerContentIDRecursionParam int;
DECLARE #Type NVARCHAR(MAX);
DECLARE #resultString NVARCHAR(MAX) = '';
-- SELECT Project layer content children with recursion
-- SELECT the count of the result from the recursion of the children of the project layer content id passed with the sql parameter in the query
DECLARE ProjectLayerContentChildren_Cursor CURSOR FOR
SELECT ProjectLayerContentID, 'Child(ren)'
FROM ProjectLayerContent
WHERE ProjectLayerContentParentID = #ProjectLayerContentID
OPEN ProjectLayerContentChildren_Cursor
FETCH NEXT FROM ProjectLayerContentChildren_Cursor INTO #ProjectLayerContentIDRecursionParam, #Type
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #resTable SELECT #ProjectLayerContentIDRecursionParam, #Type;
;WITH ProjectLayerContentTotalScopeChildren AS(
SELECT ProjectLayerContentID,CAST('Content(s) of Child(ren)' as varchar(259)) AS ProjLContentType
FROM ProjectLayerContent
WHERE ContentOfContentID = #ProjectLayerContentIDRecursionParam
UNION ALL
SELECT Scopechildren.ProjectLayerContentID, CAST('Children Content Of Content of Child(ren)' as varchar(259)) AS ProjLContentType
FROM ProjectLayerContent Scopechildren INNER JOIN
ProjectLayerContentTotalScopeChildren projLContents ON Scopechildren.ContentOfContentID = projLContents.ProjectLayerContentID
UNION ALL
SELECT children.ProjectLayerContentID, CAST('Child(ren) of Child(ren) of Child(ren)' as varchar(259)) AS ProjLContentType
FROM ProjectLayerContent children INNER JOIN
ProjectLayerContentTotalScopeChildren projLContents ON children.ProjectLayerContentParentID = projLContents.ProjectLayerContentID
)
INSERT INTO #resTable SELECT ProjectLayerContentID, ProjLContentType from ProjectLayerContentTotalScopeChildren
FETCH NEXT FROM ProjectLayerContentChildren_Cursor INTO #ProjectLayerContentIDRecursionParam, #Type
END
-- close the cursor
CLOSE ProjectLayerContentChildren_Cursor
DEALLOCATE ProjectLayerContentChildren_Cursor
-----------------------------------------------------------------------------------------------------------
-- SELECT Project layer content Scope children with recursion
DECLARE ProjectLayerContent_Cursor CURSOR FOR
SELECT ProjectLayerContentID, 'Content Of Content'
FROM ProjectLayerContent
WHERE ContentOfContentID = #ProjectLayerContentID
OPEN ProjectLayerContent_Cursor
FETCH NEXT FROM ProjectLayerContent_Cursor INTO #ProjectLayerContentIDRecursionParam, #Type
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #resTable SELECT #ProjectLayerContentIDRecursionParam, #Type;
;WITH ProjectLayerContentTotalScopeContentOfContent AS(
SELECT ProjectLayerContentID,CAST('Children Content Of Content' as varchar(259)) AS ProjLContentType
FROM ProjectLayerContent
WHERE ContentOfContentID = #ProjectLayerContentIDRecursionParam
UNION ALL
SELECT Scopechildren.ProjectLayerContentID, CAST('Children Content Of Content of Children Content Of Content' as varchar(259)) AS ProjLContentType
FROM ProjectLayerContent Scopechildren INNER JOIN
ProjectLayerContentTotalScopeContentOfContent projLContents ON Scopechildren.ContentOfContentID = projLContents.ProjectLayerContentID
UNION ALL
SELECT children.ProjectLayerContentID, CAST('Children of Children Content Of Content' as varchar(259)) AS ProjLContentType
FROM ProjectLayerContent children INNER JOIN
ProjectLayerContentTotalScopeContentOfContent projLContents ON children.ProjectLayerContentParentID = projLContents.ProjectLayerContentID
)
INSERT INTO #resTable SELECT ProjectLayerContentID, ProjLContentType from ProjectLayerContentTotalScopeContentOfContent
FETCH NEXT FROM ProjectLayerContent_Cursor INTO #ProjectLayerContentIDRecursionParam, #Type
END
-- close the cursor
CLOSE ProjectLayerContent_Cursor
DEALLOCATE ProjectLayerContent_Cursor
--select the result to present it.
SELECT * FROM #resTable;
Thanks in advance.
I found that this solution to get the needed functionality as needed.
I created the stored procedure to get all children for this project layer first, and created a table to add the result in. Because one of the first error to apply this solution was INSERT INTO EXEC wouldn't work if you have a chain of stored procedures calling each other. It only works for the first time, but the other times will show an error.
DECLARE #ProjectLayerContentID INT = 1;
DECLARE #ProjectLayerContentIDRecursionParam INT;
DECLARE #Type NVARCHAR(MAX);
DECLARE #resultString NVARCHAR(MAX) = '';
-------Create temp table to insert the results into it
CREATE TABLE #ResTable(
ProjectLayerContentIDP int,
ProjLContentType nvarchar(100));
Then I created a cursor for the select statement of all children who have that relationship of being child of this project layer, or being content of it.
-- SELECT Project layer content children with recursion
-- SELECT the count of the result from the recursion of the children of the project layer content id passed with the sql parameter in the query
DECLARE ProjectLayerContent_Cursor CURSOR FOR
SELECT ProjectLayerContentID, CASE WHEN ProjectLayerContentParentID = #ProjectLayerContentID THEN 'Child(ren)' ELSE 'Content(s) of Content(s)' END
FROM ProjectLayerContent
WHERE ProjectLayerContentParentID = #ProjectLayerContentID
OR ContentOfContentID = #ProjectLayerContentID
OPEN ProjectLayerContent_Cursor
FETCH NEXT FROM ProjectLayerContent_Cursor INTO #ProjectLayerContentIDRecursionParam, #Type
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #ResTable SELECT #ProjectLayerContentIDRecursionParam, #Type;
Then I will call the recursion stored procedure to get each of the current project layer content chlidren, and content of the children.
Exec DeleteCheck_ProjectLayerContentChildren #ProjectLayerContentIDRecursionParam;
FETCH NEXT FROM ProjectLayerContent_Cursor INTO #ProjectLayerContentIDRecursionParam, #Type
END
-- close the cursor
CLOSE ProjectLayerContent_Cursor
DEALLOCATE ProjectLayerContent_Cursor
At this part here, we will select the results, and then drop the temp table from the database server.
--select the result to present it.
SELECT #resultString = #resultString + '<br/><u>' + CONVERT(NVARCHAR(MAX), Count(ProjectLayerContentIDP)) + '</u> ' + ProjLContentType
FROM #ResTable
GROUP BY ProjLContentType;
SELECT #resultString;
DROP TABLE #ResTable
Here is the recursion stored procedure that we called already in the previous query
CREATE PROCEDURE [dbo].[DeleteCheck_ProjectLayerContentChildren]
#ProjectLayerContentID INT
AS
BEGIN
DECLARE #ProjectLayerContentIDRecursionParam INT;
DECLARE #Type NVARCHAR(MAX);
-- SELECT Project layer content children with recursion
-- SELECT the count of the result from the recursion of the children of the project layer content id passed with the sql parameter in the query.
Notice here we added Local into the deceleration of the cursor, because it will show an error of creating the cursor with the same name
DECLARE ProjectLayerContentChildren_Cursor CURSOR LOCAL FOR
SELECT ProjectLayerContentID, CASE WHEN ProjectLayerContentParentID = #ProjectLayerContentID THEN 'Child(ren) of Child(ren)' ELSE 'Child(ren) Content(s) of Content(s)' END
FROM ProjectLayerContent
WHERE ProjectLayerContentParentID = #ProjectLayerContentID
OR ContentOfContentID = #ProjectLayerContentID
Pretty much most of this query is the same, as the previous one, but this is for the project layer content children of children counts, and we don't create the result table again because it will be already created, then we just add the result, and we have the needed functionality, and you can apply it to more than two or three self relationships.
OPEN ProjectLayerContentChildren_Cursor
FETCH NEXT FROM ProjectLayerContentChildren_Cursor INTO #ProjectLayerContentIDRecursionParam, #Type
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #ResTable SELECT #ProjectLayerContentIDRecursionParam, #Type;
Exec DeleteCheck_ProjectLayerContentChildren #ProjectLayerContentIDRecursionParam
FETCH NEXT FROM ProjectLayerContentChildren_Cursor INTO #ProjectLayerContentIDRecursionParam, #Type
END
-- close the cursor
CLOSE ProjectLayerContentChildren_Cursor
DEALLOCATE ProjectLayerContentChildren_Cursor
END

rollback a nested transaction in trigger

I have a following situation.
I have a table with trigger for insert.
When I insert a row in it, from this trigger I want to insert some rows into a second table.
For each of these rows I want to do it in it's own transaction in case something go wrong.
I want to have original row in first table and all rows (these withous errors) in the second.
A little code to reproduce:
create table test(id int primary key identity(1,1),name nvarchar(10))
create table test2(id int primary key identity(1,1),
state char(1) check (state in ('a','b')))
go
create trigger test_trigger on test for insert
as
begin
declare #char char(1)
declare curs cursor for (
select 'a'
union
select 'c'
union
select 'b')
open curs
fetch next from curs into #char
while ##FETCH_STATUS = 0
begin
begin try
begin transaction
insert into test2(state)
select #char
commit
end try
begin catch
print 'catch block'
rollback
end catch
fetch next from curs into #char
end
close curs
deallocate curs
end
go
insert into test(name) values('test')
select * from test
select * from test2
go
drop table test
drop table test2
So for the sample data from this snippet, I want to have a row with 'test' in test table and two rows in the test2 table ('a' and 'b').
How can I write a code for that?
Looks like finally I got it to work.
Corrected trigger code:
create trigger test_trigger on test for insert
as
begin
declare #char char(1)
set xact_abort off
declare curs cursor for (
select 'a'
union
select 'c'
union
select 'b')
open curs
fetch next from curs into #char
while ##FETCH_STATUS = 0
begin
save transaction t
begin try
insert into test2(state)
select #char
end try
begin catch
print 'catch block'
rollback transaction t
end catch
fetch next from curs into #char
end
close curs
deallocate curs
end

TSQL Cursor Infinite Loop without TOP xxx in SELECT Statement

I have a curious problem with an infinite loop in a TSQL cursor. The cursor loops infinitely when I do not add TOP 300 to the defining select statement of the cursor. The following is an example of the code: Any assistance to this issue is much appreciated.
DECLARE #Done BIT
SET #Done = 0
DECLARE cursOut CURSOR LOCAL FAST_FORWARD
FOR
SELECT
--TOP 300
FirstName FirstName
,LastName LastName
,MiddleName MiddleName
,Email Email
,Address1 Address1
,Address2 Address2
,City City
,[State] [State]
FROM StagedUsers
OPEN cursOut;
WHILE (#Done = 0)
BEGIN
--Fetch next row
FETCH NEXT
FROM cursOut
INTO ,#v_FirstName
,#v_LastName
,#v_MiddleName
,#v_Email
,#v_Address1
,#v_Address2
,#v_City
,#v_State
IF (##FETCH_STATUS <> 0)
BEGIN
SET #Done = 1
BREAK
END
--if #batch = 0
BEGIN TRANSACTION
--process statements
--updates or insert statements
--Commit transaction
COMMIT TRANSACTION
--End While
END
--CleanUp:
CLOSE cursOut
DEALLOCATE cursOut
Thanks,
Renegrin
First I think you do not need transaction here, I presume it is only one statement executed, so remove transaction from code.
Second do it without #done flag, it is confusing (it is probably not problem here).
DECLARE #v_FirstName VARCHAR(500),#v_LastName VARCHAR(500),#v_MiddleName VARCHAR(500),#v_Email VARCHAR(500),#v_Address1 VARCHAR(500),#v_Address2 VARCHAR(500),#v_City VARCHAR(500),#v_State VARCHAR(500)
DECLARE cursOut CURSOR FAST_FORWARD FOR
SELECT FirstName FirstName,LastName LastName,MiddleName MiddleName,Email Email,Address1 Address1,Address2 Address2,City City,[State] [State]
FROM StagedUsers
OPEN cursOut;
FETCH NEXT FROM cursOut INTO #v_FirstName,#v_LastName,#v_MiddleName,#v_Email,#v_Address1,#v_Address2,#v_City,#v_State
declare #i int = 1
WHILE ##FETCH_STATUS = 0
BEGIN
print cast(#i as varchar(10))
--> statement here
FETCH NEXT FROM cursOut INTO #v_FirstName,#v_LastName,#v_MiddleName,#v_Email,#v_Address1,#v_Address2,#v_City,#v_State
set #i = #i + 1
END
CLOSE cursOut
DEALLOCATE cursOut
Do you want to update current row where cursor point? If so, there is way to do it.
Is there any indexes on table? Can you post an update query?

How to use WITH table AS result within cursor loop to run stored procedure

How to get result in WITH table AS into CURSOR loop? I have previously asked about how to get recursive results from my table
How to read all records recursively and show by level depth TSQL
;with C as
(
definition ...
)
I have created CURSOR loop where I want to run specific stored procedure for all results in table
declare #id int, #parent int
declare cur cursor local fast_forward
for
select id, parent from C
open cur
fetch next from cur into #id, #parent
while ##fetch_status = 0
begin
exec storedProcedure #id=#id, #parent=#parent
fetch next from cur into #id, #parent
end
close cur
deallocate cur
Problem is that CURSOR doesnt know table from WITH AS result.
Invalid object name 'C'.
You can create a temp table or a table variable to hold the rows returned by you CTE query and then you use that table as the source for your cursor.
declare #T table
(
id int,
parent int
)
;with C as
(
select 1 as id, 2 as parent
)
insert into #T
select id, parent
from C
declare cur cursor for select id, parent from #T

##FETCH_STATUS returns -1 when trying to use cursor based on query on INFORMATION_SCHEMA

I try to create a SP that do some processing on every table that contains a specific field name
So my plan was to create a cursor to get all those tables, then prepare a dynamic sql for processing.
I have the following code
declare schema_cursor cursor
for
SELECT INFORMATION_SCHEMA.TABLES.TABLE_SCHEMA, INFORMATION_SCHEMA.TABLES.TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES INNER JOIN
INFORMATION_SCHEMA.COLUMNS ON INFORMATION_SCHEMA.TABLES.TABLE_SCHEMA = INFORMATION_SCHEMA.COLUMNS.TABLE_SCHEMA AND
INFORMATION_SCHEMA.TABLES.TABLE_NAME = INFORMATION_SCHEMA.COLUMNS.TABLE_NAME
WHERE (INFORMATION_SCHEMA.TABLES.TABLE_TYPE = 'BASE TABLE') AND (INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME = N'TargetFieldName')
open schema_cursor
fetch next from schema_cursor into #schema, #table
while (##FETCH_STATUS=0)
begin
If I run the query for cursor directly I get all table names I need
But when I run code above, ##FETCH_STATUS returns -1 and no row is retrieved.
What am I doing wrong?
Thanks
This works fine for me. I doubt your assertion that the SELECT does return rows. Are you running the cursor code under a different login that doesn't have permissions to view the metadata perhaps?
SET NOCOUNT ON
DECLARE #schema SYSNAME,
#table SYSNAME
DECLARE schema_cursor CURSOR FOR
SELECT INFORMATION_SCHEMA.TABLES.TABLE_SCHEMA,
INFORMATION_SCHEMA.TABLES.TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
INNER JOIN INFORMATION_SCHEMA.COLUMNS
ON INFORMATION_SCHEMA.TABLES.TABLE_SCHEMA =
INFORMATION_SCHEMA.COLUMNS.TABLE_SCHEMA
AND INFORMATION_SCHEMA.TABLES.TABLE_NAME =
INFORMATION_SCHEMA.COLUMNS.TABLE_NAME
WHERE ( INFORMATION_SCHEMA.TABLES.TABLE_TYPE = 'BASE TABLE' )
--AND (INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME = N'TargetFieldName')
OPEN schema_cursor
FETCH NEXT FROM schema_cursor INTO #schema, #table
WHILE ( ##FETCH_STATUS = 0 )
BEGIN
RAISERROR('%s %s', 0, 1, #schema, #table)
FETCH NEXT FROM schema_cursor INTO #schema, #table
END
CLOSE schema_cursor
DEALLOCATE schema_cursor
Actually I found the problem.
The code I posted was actually a simplified version of my actual code. My real select used by the cursor selected three fields as below, but fetch attempted to fetch only two fields.
SELECT
INFORMATION_SCHEMA.TABLES.TABLE_SCHEMA,
INFORMATION_SCHEMA.TABLES.TABLE_NAME,
INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME
-- ....
When I post the question, I thought to simplify it, but that actually fixed the issue :)
That's why my statement worked for Martin, but didn't for me - because by "mistake" the code I posted was correct, unlike the code I actually used.
Thanks, Martin, for taking time to post your answer which finally led me to discover the problem.