SQL Server Nested Cursors and Variables Declaration - sql-server

I have a doubt regarding the variable declaration in a nested cursors scenario.
This is an small nested cursor sample that i found. In other samples I've seen I also find DECLARE clauses inside the first cursor.
DECLARE #ClientID int;
DECLARE Cur1 CURSOR FOR SELECT ClientID From Folder;
OPEN Cur1
FETCH NEXT FROM Cur1 INTO #ClientID;
SELECT #FETCH_Cur1 = ##FETCH_STATUS
WHILE #FETCH_Cur1 = 0
BEGIN
DECLARE #UID int;
DECLARE Cur2 CURSOR FOR SELECT UID FROM Attend Where ClientID=#ClientID;
OPEN Cur2;
FETCH NEXT FROM Cur2 INTO #UID;
SELECT #FETCH_Cur2 = ##FETCH_STATUS
WHILE #FETCH_Cur2 = 0
BEGIN
PRINT 'Found UID: ' + Cast(#UID as Varchar);
FETCH NEXT FROM Cur2 INTO #UID;
SELECT #FETCH_Cur2 = ##FETCH_STATUS
END;
CLOSE Cur2;
DEALLOCATE Cur2;
FETCH NEXT FROM Cur1 INTO #ClientID;
SELECT #FETCH_Cur1 = ##FETCH_STATUS
END;
PRINT 'DONE';
CLOSE Cur1;
DEALLOCATE Cur1;
The code works, but my doubt is if it's correct the DECLARATIONS inside the first cursor.
DECLARE #UID int;
Shouldn't Declarations be placed at the beginning of code, as is normally done for other programming languages?

You can DECLARE a variable inside a WHILE, yes; the latter DECLAREs will simply be ignored. If you declared the variable and assigned it a value at the time (for example DECLARE #UID int = 1; it would be assigned 1 in each iteration:
DECLARE #I int = 1;
WHILE #i < 10 BEGIN
DECLARE #W int;
SET #W = ISNULL(#W,1) + 1;
SET #I = #I + 1
END
SELECT #W; --10
GO
DECLARE #I int = 1;
WHILE #i < 10 BEGIN
DECLARE #W int = 0;
SET #W = ISNULL(#W,1) + 1;
SET #I = #I + 1
END
SELECT #W; -- 1
DB<>fiddle
Of course, I personally prefer to DECLARE the variables outside of the WHILE as I feel the code is "cleaner", but that doesn't mean you have to.

Related

how to use variable name as table name in update statement

I have a table(Lets say'A') that contains the list of all tables in a database.I have defined a cursor that iterate through the tables name in 'A'.I want to update the table column defined in a cursor.
I have created 2 cursors.One to iterate over the tables names and other one to iterate over the column names.
DECLARE #MyCursor CURSOR;
DECLARE #MyField nvarchar(255);
BEGIN
SET #MyCursor = CURSOR FOR
select distinct Table_name from DataTable where Data <>'No'
set #a=0
OPEN #MyCursor
FETCH NEXT FROM #MyCursor
INTO #MyField
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #column_cursor CURSOR;
DECLARE #columnField nvarchar(255);
SET #column_cursor = CURSOR FOR
select Column_name from DataTable where TABLE_NAME=#MyField and Data
<>'No'
OPEN #column_cursor
FETCH NEXT FROM #column_cursor
INTO #columnField
WHILE ##FETCH_STATUS = 0
BEGIN
update #MyField set #columnField=''+#MyField+'_'+#columnField+#a
FETCH NEXT FROM #column_cursor
INTO #columnField
END;
CLOSE #column_cursor ;
DEALLOCATE #column_cursor;
FETCH NEXT FROM #MyCursor
INTO #MyField
END;
CLOSE #MyCursor ;
DEALLOCATE #MyCursor;
END;
Here in the update statement #MyField is giving error :"Must declare the table variable #MyField".
You are on good way but you can't use variable on table name place , you need to build your update command in variable and then execute dynamic sql.
You can see: Creating a dynamic sql query
in your case someting like
declare #sqlCommand varchar(max)
declare #MyField varchar(255) = 'table_name'
declare #columnField varchar(255) = 'column_NAME'
declare #columnValue varchar(255) = 'column_value'
set #sqlCommand = 'update '+ #MyField +' set '+#columnField+' = ' +#columnValue+ ' where 1=1;'
--select #sqlCommand
EXEC (#sqlCommand)

##FETCH_STATUS lives outside the cursor

I have a query which processes XML data and I use a while(##FETCH_STATUS = 0) loop for data returned from the cursor.
When I run the query using Management Studio, ##FETCH_STATUS equals -1 and the code inside my loop is omitted. If I run the query using the debugger and press continue, it runs just fine and the ##FETCH_STATUS equals 0. When I run the query again, after running it in debug ##FETCH_STATUS equals 0 and changes to -1.
To sum up:
I run with SSMS - ##FETCH_STATUS = -1
I run with debugger - ##FETCH_STATUS = 0 (I want this value)
I run with SSMS once after running with debugger ##FETCH_STATUS still equals 0 but then changes to -1.
I use OPEN cursor, CLOSE cursor and DEALLOCATE cursor. Why does it work this way?
EDIT: Code you asked for:
IF (OBJECT_ID('dbo.XmlOrderResponses') IS NOT NULL)
DROP TABLE XmlOrderResponses;
CREATE TABLE XmlOrderResponses (
OrderResponseType INT
,OrderResponseNumber NVARCHAR(40)
,OrderResponseDate DATETIME
,DocumentFunctionCode NVARCHAR(40)
,Remarks INT
);
DECLARE CUR CURSOR
FOR
SELECT Subdirectory
FROM XMLFiles;
OPEN CUR
WHILE (##FETCH_STATUS = 0)
BEGIN
DECLARE #DocHandle AS INT;
DECLARE #TMP AS NVARCHAR(512);
FETCH NEXT
FROM Cur
INTO #TMP
DECLARE #XmlDocument AS NVARCHAR(MAX);
SET #XmlDocument = (
SELECT CAST(XMLSource AS NVARCHAR(max))
FROM XMLFiles
WHERE subdirectory = #TMP
);
EXEC sys.sp_xml_preparedocument #DocHandle OUTPUT
,#XmlDocument;
INSERT INTO XmlOrderResponses (
OrderResponseType
,OrderResponseNumber
,OrderResponseDate
,DocumentFunctionCode
,Remarks
)
SELECT *
FROM OPENXML(#DocHandle, '/Document-OrderResponse/*', 11) WITH (
OrderResponseType INT
,OrderResponseNumber NVARCHAR(40)
,OrderResponseDate DATETIME
,DocumentFunctionCode NVARCHAR(40)
,Remarks INT
);
EXEC sys.sp_xml_removedocument #DocHandle;
END
CLOSE CUR;
DEALLOCATE CUR;
--I know I shouldn't be doing that but I can't get rid of NULL records the other way.
DELETE
FROM XmlOrderResponses
WHERE OrderResponseType IS NULL
AND OrderResponseNumber IS NULL
AND OrderResponseDate IS NULL
AND DocumentFunctionCode IS NULL
AND Remarks IS NULL;
SELECT *
FROM XmlOrderResponses
SELECT ##FETCH_STATUS
The problem is that the first time you refer to ##FETCH_STATUS, you have not done a fetch with your cursor, so it is referring to the last cursor used. Imagine this simple example:
DECLARE C1 CURSOR
FOR
SELECT TOP 3 ID
FROM (VALUES ('1'), ('2'), ('3')) t (ID);
OPEN C1;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #c1 CHAR(1);
FETCH NEXT FROM C1 INTO #c1;
PRINT #c1;
END
CLOSE C1;
DEALLOCATE C1;
DECLARE C2 CURSOR
FOR
SELECT TOP 3 ID
FROM (VALUES ('1'), ('2'), ('3')) t (ID);
OPEN C2;
-- HERE ##FETCH_STATUS REFERS TO THE LAST FETCH FOR CURSOR `C1` NOT `C2`
SELECT ##FETCH_STATUS;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #c2 CHAR(1);
FETCH NEXT FROM C2 INTO #c2;
PRINT #c2;
END;
CLOSE C2;
DEALLOCATE C2;
At the commented line, even though you have closed, and deallocated C1, ##FETCH_STATUS is still referring to this cursor (since no other FETCH has been performed since), so you never enter your loop for C2
You should perform the Fetch before the loop, then at the end of each loop, rather than at the beginning.
DECLARE #TMP AS NVARCHAR(512);
OPEN CUR
-- DO FETCH FIRST
FETCH NEXT FROM Cur INTO #TMP
WHILE (##FETCH_STATUS = 0)
BEGIN
DECLARE #DocHandle AS INT;
-- DO ALL YOUR WORK WITH #TMP
--PERFORM THE FETCH AGAIN AT THE END OF THE LOOP
FETCH NEXT FROM Cur INTO #TMP
END
The other problem you have with doing FETCH at the start of each loop, is that the last item will be processed twice. Again a simple example (and assuming you enter the loop with ##FETCH_STATUS = 0)
DECLARE C1 CURSOR
FOR
SELECT ID = '1';
OPEN C1;
DECLARE #c CHAR(1);
WHILE (##FETCH_STATUS = 0)
BEGIN
DECLARE #c1 CHAR(1);
FETCH NEXT FROM C1 INTO #c1;
PRINT #c1;
END
This will print
1
1
Because, when ##FETCH_STATUS is -1, FETCH will just return the item at the current position.

T-SQL Cursor never ends

I already searched with Google but I didn't find answer to my question. In the internet everything says that I do it correct.
My problem is - I am trying for now write values what I saved to cursor (yes, only that). However loop which I use for it is infinity and it writes all values from cursor from start to end, after again and again and again ...
CREATE PROCEDURE CreateOrEditClient(...Parameters...) AS
DECLARE c CURSOR FOR SELECT column FROM table;
DECLARE #wrt VARCHAR(20);
DECLARE #tmp INT = 0;
BEGIN
OPEN c;
FETCH NEXT FROM c INTO #wrt;
WHILE ##FETCH_STATUS = 0
BEGIN
print CAST(#tmp AS VARCHAR(10)) + ' ' + #wrt;
SET #tmp = #tmp +1;
FETCH NEXT FROM c INTO #wrt;
END;
CLOSE c;
DEALLOCATE c;
END;
EXEC CreateOrEditClient ...;
In my opinion cursor is written correct but output is:
0 790710/1112
1 900519/5555
2 790716/7877
....
19 111111/1111
0 790710/1112
1 900519/5555
....
and in the end it writes error message "Maximum stored procedure, function, trigger, or view nesting level exceeded (limit 32)."
If I use only
SELECT column FROM table;
It writes only 20 records.
I know there is better solution for this example but I need to know why the Cursor doesn't work. It may be useful in future. Thanks for every asnwer.
Ok, in comments under question is solution.
"EXEC ProcedureName ...;" can't be in the same file like procedure's body when you compile.
CREATE PROCEDURE CreateOrEditClient(...Parameters...) AS
DECLARE c CURSOR FOR SELECT column FROM table;
DECLARE #wrt VARCHAR(20);
DECLARE #tmp INT = 0;
BEGIN
OPEN c;
FETCH NEXT FROM c INTO #wrt;
WHILE ##FETCH_STATUS = 0
BEGIN
print CAST(#tmp AS VARCHAR(10)) + ' ' + #wrt;
SET #tmp = #tmp +1;
FETCH NEXT FROM c INTO #wrt;
END;
CLOSE c;
DEALLOCATE c;
END;
The example in question will call procedure recursively.
Thanks to all.
Please Try this ...
BEGIN
SET NOCOUNT ON ;
DECLARE #wrt VARCHAR(20)
DECLARE #tmp INT = 0
DECLARE Cur_Common CURSOR FAST_FORWARD READ_ONLY FOR
SELECT iID FROM 'YourTable'
OPEN Cur_Common
FETCH NEXT FROM Cur_Common INTO #wrt
WHILE ##FETCH_STATUS = 0
BEGIN
print CAST(#tmp AS VARCHAR(10)) + ' ' + #wrt;
SET #tmp = #tmp +1;
FETCH NEXT FROM Cur_Common INTO #wrt
END
CLOSE Cur_Common
DEALLOCATE Cur_Common
END
You would probably notice if you would keep your code indented correctly. You have:
CREATE PROCEDURE CreateOrEditClient(...Parameters...) AS
DECLARE c CURSOR FOR SELECT column FROM table;
DECLARE #wrt VARCHAR(20);
DECLARE #tmp INT = 0;
BEGIN
OPEN c;
FETCH NEXT FROM c INTO #wrt;
WHILE ##FETCH_STATUS = 0
BEGIN
print CAST(#tmp AS VARCHAR(10)) + ' ' + #wrt;
SET #tmp = #tmp +1;
FETCH NEXT FROM c INTO #wrt;
END;
CLOSE c;
DEALLOCATE c;
END;
You should have:
CREATE PROCEDURE CreateOrEditClient(...Parameters...) AS
BEGIN
DECLARE #wrt VARCHAR(20);
DECLARE #tmp INT = 0;
DECLARE c CURSOR FOR SELECT column FROM table;
OPEN c;
FETCH NEXT FROM c INTO #wrt;
WHILE ##FETCH_STATUS = 0
BEGIN
print CAST(#tmp AS VARCHAR(10)) + ' ' + #wrt;
SET #tmp = #tmp +1;
FETCH NEXT FROM c INTO #wrt;
END;
CLOSE c;
DEALLOCATE c;
END;
EXEC CreateOrEditClient ...;
BTW. Did your code even run? Seems like a syntax error to me.
EDIT: Oh, and parameters are not declared in brackets AFAIK. For example:
CREATE PROCEDURE tmp_mol_cleanup_db
#param1 type,
#param2 type
AS
BEGIN
-- procedure body
END;
use
DECLARE #tmp INT
SET #tmp=0
instad of
DECLARE #tmp INT = 0

Cursor not incrementing updating row with start value

I'm very new to SQL Server. I'm using a cursor to populate a table with ids; I just discovered cursors today. The code is running but it is populating each row with the start value.
SET NOCOUNT ON
DECLARE #Irow int
declare #cheese int;
set #cheese = (select (max(balanceid) + 1) from balancetbl)
DECLARE aurum CURSOR FOR
SELECT #Irow
FROM aurumaugupload
OPEN aurum
FETCH aurum INTO #Irow
WHILE ##Fetch_Status = 0
BEGIN
update aurumaugupload set balanceid = #cheese
set #cheese = #cheese + 1;
FETCH aurum INTO #Irow
END
CLOSE aurum
DEALLOCATE aurum
RETURN
I think it's a really basic error but I can't see it due to my inexperience.
UPDATE: thanks guys for your prompts answers. I got it working after nonnb's help. Here's the final code:
SET NOCOUNT ON
DECLARE #acc int
declare #start int;
set #start = (select (max(balanceid) + 1) from balancetbl)
DECLARE aurum CURSOR FOR
SELECT accountid
FROM aurumaugupload
OPEN aurum
FETCH aurum INTO #acc
WHILE ##Fetch_Status = 0
BEGIN
update aurumaugupload set balanceid = #start where accountid = #acc
set #start = #start + 1;
FETCH aurum INTO #acc
END
CLOSE aurum
DEALLOCATE aurum
RETURN
There are at least 2 bugs here:
Bug 1
DECLARE aurum CURSOR FOR
SELECT #Irow
FROM aurumaugupload
will select the same (unitialised) constant for every row of aurumaugupload
You need something like
SELECT Irow
FROM aurumaugupload
Bug 2 - You are updating all rows within the cursor. You need a where
update aurumaugupload set balanceid = #cheese
where IRow = #IRow;
set #cheese = #cheese + 1
Your update statement doesn't have a where clause, so you are updating every row each time.
Try this solution (if the sorting/update order doesn't matter):
SET NOCOUNT ON
DECLARE #Irow int
DECLARE #cheese int;
SET #cheese = (SELECT (MAX(balanceid) ) FROM balancetbl)
UPDATE aurumaugupload
set #cheese = balanceid = #cheese+1;

Translate SQL Server 2008 Cursor Variables to SQL Azure

I'm attempting to port the database for NetSqlAzMan to Azure. I'm running into a problem with a few of the stored procedures.
SET #member_cur = CURSOR STATIC FORWARD_ONLY FOR SELECT * FROM #RESULT
OPEN #member_cur
results in error message:
Msg 16948, Level 16, State 4
Procedure
netsqlazman_GetApplicationGroupSidMembers,
Line 118
The variable '#member_cur' is
not a cursor variable, but it is used
in a place where a cursor variable is
expected.
The store procedure script was created by exporting an empty NetSQLAzMan database in SQLAzure format. Any tips for handling cursor variables in SQL Azure? I don't see much for documentation on this.
Here is the stored procedure if that helps. Please pardon the verbosity. The error references the bottom of the stored procedure.
CREATE PROCEDURE [dbo].[netsqlazman_GetApplicationGroupSidMembers]
#ISMEMBER [bit],
#GROUPOBJECTSID [varbinary](85),
#NETSQLAZMANMODE [bit],
#LDAPPATH [nvarchar](4000),
#member_cur [int] OUTPUT
WITH EXECUTE AS CALLER
AS
DECLARE #RESULT TABLE (objectSid VARBINARY(85))
DECLARE #GROUPID INT
DECLARE #GROUPTYPE TINYINT
DECLARE #LDAPQUERY nvarchar(4000)
DECLARE #sub_members_cur CURSOR
DECLARE #OBJECTSID VARBINARY(85)
SELECT #GROUPID = ApplicationGroupId, #GROUPTYPE = GroupType, #LDAPQUERY = LDapQuery FROM [netsqlazman_ApplicationGroupsTable] WHERE objectSid = #GROUPOBJECTSID
IF #GROUPTYPE = 0 -- BASIC
BEGIN
--memo: WhereDefined can be:0 - Store; 1 - Application; 2 - LDAP; 3 - Local; 4 - Database
-- Windows SIDs
INSERT INTO #RESULT (objectSid)
SELECT objectSid
FROM dbo.[netsqlazman_ApplicationGroupMembersTable]
WHERE
ApplicationGroupId = #GROUPID AND IsMember = #ISMEMBER AND
((#NETSQLAZMANMODE = 0 AND (WhereDefined = 2 OR WhereDefined = 4)) OR (#NETSQLAZMANMODE = 1 AND WhereDefined BETWEEN 2 AND 4))
-- Store Groups Members
DECLARE #MemberObjectSid VARBINARY(85)
DECLARE #MemberType bit
DECLARE #NotMemberType bit
DECLARE nested_Store_groups_cur CURSOR LOCAL FAST_FORWARD FOR
SELECT objectSid, IsMember FROM dbo.[netsqlazman_ApplicationGroupMembersTable] WHERE ApplicationGroupId = #GROUPID AND WhereDefined = 0
OPEN nested_Store_groups_cur
FETCH NEXT FROM nested_Store_groups_cur INTO #MemberObjectSid, #MemberType
WHILE ##FETCH_STATUS = 0
BEGIN
-- recursive call
IF #ISMEMBER = 1
BEGIN
IF #MemberType = 0
SET #NotMemberType = 0
ELSE
SET #NotMemberType = 1
END
ELSE
BEGIN
IF #MemberType = 0
SET #NotMemberType = 1
ELSE
SET #NotMemberType = 0
END
EXEC dbo.[netsqlazman_GetStoreGroupSidMembers] #NotMemberType, #MemberObjectSid, #NETSQLAZMANMODE, #LDAPPATH, #sub_members_cur OUTPUT
FETCH NEXT FROM #sub_members_cur INTO #OBJECTSID
WHILE ##FETCH_STATUS=0
BEGIN
INSERT INTO #RESULT VALUES (#OBJECTSID)
FETCH NEXT FROM #sub_members_cur INTO #OBJECTSID
END
CLOSE #sub_members_cur
DEALLOCATE #sub_members_cur
FETCH NEXT FROM nested_Store_groups_cur INTO #MemberObjectSid, #MemberType
END
CLOSE nested_Store_groups_cur
DEALLOCATE nested_Store_groups_cur
-- Application Groups Members
DECLARE nested_Application_groups_cur CURSOR LOCAL FAST_FORWARD FOR
SELECT objectSid, IsMember FROM dbo.[netsqlazman_ApplicationGroupMembersTable] WHERE ApplicationGroupId = #GROUPID AND WhereDefined = 1
OPEN nested_Application_groups_cur
FETCH NEXT FROM nested_Application_groups_cur INTO #MemberObjectSid, #MemberType
WHILE ##FETCH_STATUS = 0
BEGIN
-- recursive call
IF #ISMEMBER = 1
BEGIN
IF #MemberType = 0
SET #NotMemberType = 0
ELSE
SET #NotMemberType = 1
END
ELSE
BEGIN
IF #MemberType = 0
SET #NotMemberType = 1
ELSE
SET #NotMemberType = 0
END
EXEC dbo.[netsqlazman_GetApplicationGroupSidMembers] #NotMemberType, #MemberObjectSid, #NETSQLAZMANMODE, #LDAPPATH, #sub_members_cur OUTPUT
FETCH NEXT FROM #sub_members_cur INTO #OBJECTSID
WHILE ##FETCH_STATUS=0
BEGIN
INSERT INTO #RESULT VALUES (#OBJECTSID)
FETCH NEXT FROM #sub_members_cur INTO #OBJECTSID
END
CLOSE #sub_members_cur
DEALLOCATE #sub_members_cur
FETCH NEXT FROM nested_Application_groups_cur INTO #MemberObjectSid, #MemberType
END
CLOSE nested_Application_groups_cur
DEALLOCATE nested_Application_groups_cur
END
ELSE IF #GROUPTYPE = 1 AND #ISMEMBER = 1 -- LDAP QUERY
BEGIN
EXEC dbo.[netsqlazman_ExecuteLDAPQuery] #LDAPPATH, #LDAPQUERY, #sub_members_cur OUTPUT
FETCH NEXT FROM #sub_members_cur INTO #OBJECTSID
WHILE ##FETCH_STATUS=0
BEGIN
INSERT INTO #RESULT (objectSid) VALUES (#OBJECTSID)
FETCH NEXT FROM #sub_members_cur INTO #OBJECTSID
END
CLOSE #sub_members_cur
DEALLOCATE #sub_members_cur
END
SET #member_cur = CURSOR STATIC FORWARD_ONLY FOR SELECT * FROM #RESULT
OPEN #member_cur
GO
I don't think this problem is with cursors so much but more with the declaration of the output variable. If you look at the start of the stored procedure you have this:
#member_cur [int] OUTPUT
Where #member_cur is defined as being an integer. You're then trying to assign it to have the value of a cursor and it is rightly complaining. What I'm surprised about is that this is what was generated from a non-Azure SQL Server. Either way it looks like SQL Azure doesn't support this, so either change the type of the output parameter, or open your cursor in a different variable and assign #member_cur to be that value.

Resources