Cursor inside cursor - sql

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

Related

Is there a way to do bitwise OR on any column using single query?

I am trying to find is there a way to calculate the bitwise OR of a column of a table using a single query. I can achieve the same using cursor
Declare #orValue int;
Declare #fieldValue int;
set #orValue = 0;
DECLARE my_cursor CURSOR FOR
SELECT MyField
FROM MyTable
WHERE SomeColumnValue=123
OPEN my_cursor
FETCH NEXT FROM my_cursor
INTO #fieldValue
WHILE ##FETCH_STATUS = 0
BEGIN
set #orValue = #orValue | #fieldValue;
FETCH NEXT FROM my_cursor INTO #fieldValue
END
CLOSE my_cursor;
DEALLOCATE my_cursor;
print #orValue;
Have done some google but didn't find any solution.
DECLARE #orv INT = 0;
SELECT #orv=#orv|MyField
FROM MyTable
WHERE SomeColumnValue=123;

Make button in form bulder to give next record

Recently I use form builder to create databases.
I want to make button when user click on it give next record and other button gives previous record.
What i shoud write in trigger.
Notice the image the bottons (next,prev).
What about this, code below can used to iterate (T-SQL):
DECLARE #MyCursor CURSOR;
DECLARE #MyField YourFieldDataType;
BEGIN
SET #MyCursor = CURSOR FOR
select top 1000 YourField from dbo.table
where StatusID = 7
OPEN #MyCursor
FETCH NEXT FROM #MyCursor
INTO #MyField
WHILE ##FETCH_STATUS = 0
BEGIN
// YOUR ALGORITHM GOES HERE
FETCH NEXT FROM #MyCursor
INTO #MyField
END;
CLOSE #MyCursor ;
DEALLOCATE #MyCursor;
END;
Or you can use while statement:
select top 1000 TableID
into #ControlTable
from dbo.table
where StatusID = 7
declare #TableID int
while exists (select * from #ControlTable)
begin
select #TableID = (select top 1 TableID
from #ControlTable
order by TableID asc)
-- Do something with your TableID
delete #ControlTable
where TableID = #TableID
end
drop table #ControlTable

Return one table from cursor

Please see the code below:
DECLARE #ID int
DECLARE #errorflag int
DECLARE Warning_Cursor CURSOR FOR
SELECT TOP 3 ID FROM Warnings
SET #errorflag = #errorflag + ##Error
OPEN Warning_cursor
SET #errorflag = #errorflag + ##Error
FETCH NEXT FROM Warning_cursor INTO #ID
WHILE ##FETCH_STATUS = 0
begin
SELECT #ID
FETCH NEXT FROM Warning_cursor INTO #ID
END
CLOSE Warning_cursor
DEALLOCATE Warning_cursor
The cursor returns three tables with one row each. How can I return one table with three rows?
Why don't you just do,
SELECT TOP 3 ID FROM Warnings
More generally, if you are using a cursor, you are probably doing it wrong.
If you really have to use a cursor for some reason that is not part of the question. You could do
DECLARE #Id int;
DECLARE #Ids TABLE (Id Int);
DECLARE Warning_Cursor CURSOR FOR SELECT TOP 3 ID FROM Warnings;
OPEN Warning_cursor;
FETCH NEXT FROM Warning_cursor INTO #Id;
WHILE ##FETCH_STATUS = 0 BEGIN
INSERT #Ids SELECT #Id;
FETCH NEXT FROM Warning_cursor INTO #Id;
END
CLOSE Warning_cursor;
DEALLOCATE Warning_cursor;
SELECT Id FROM #Ids;
The answer was to create a temporary table as follows:
DECLARE #ID int
DECLARE #errorflag int
DECLARE #CONCATRESULT TABLE (ID INT)
DECLARE Warning_Cursor CURSOR FOR
SELECT TOP 3 ID FROM Warnings
SET #errorflag = #errorflag + ##Error
OPEN Warning_cursor
SET #errorflag = #errorflag + ##Error
FETCH NEXT FROM Warning_cursor INTO #ID
WHILE ##FETCH_STATUS = 0
begin
INSERT into #CONCATRESULT (ID) VALUES (#ID)
FETCH NEXT FROM Warning_cursor INTO #ID
END
CLOSE Warning_cursor
DEALLOCATE Warning_cursor
select id from #CONCATRESULT

Passing data into nested cursors in TSQL

I have a cursor that gets data and fetches it into #data_table. Then in the while loop I want to pass that #data_table into another cursor (as the table name) to run some more processing. I keep getting a declare #data_table error. How do I fix this?
DECLARE #var_name varchar(50)
DECLARE #data_table varchar(50)
DECLARE #data_value varchar(50)
DECLARE curDiscreteVars CURSOR LOCAL
FOR SELECT DISTINCT v.var_name, v.data_table
FROM dbo.vars v
GROUP BY v.var_name, v.data_table
-- Loop through cursor, translating variable values as needed, and generate counts for each val_code for a variable
OPEN curDiscreteVars
FETCH NEXT FROM curDiscreteVars
INTO #var_name, #data_table
WHILE ##FETCH_STATUS = 0
BEGIN
--loop through all possible data values
DECLARE curValues CURSOR LOCAL
FOR SELECT DISTINCT #var_name
FROM #data_table
OPEN curValues
FETCH NEXT FROM curValues
INTO #data_value
WHILE ##FETCH_STATUS = 0
BEGIN
print #var_name
FETCH NEXT FROM curValues
INTO #data_value
END
CLOSE curValues
DEALLOCATE curValues
FETCH NEXT FROM curDiscreteVars
INTO #var_name, #data_table
END
CLOSE curDiscreteVars
DEALLOCATE curDiscreteVars
For my part, i don't like cursors! For me cursors are evil. The give you locks and such that you don't want.
What i always do is create a temp table with the values (like you normally insert into the cursor) and loop through it with a while loop
like this :
declare #currow int
, #totrow int
create table #tmp_values (id int identity(1, 1), val int)
insert
into #tmp_values
select val
from tableX
set #totrow = ##rowcount
set #currow = 1
while #totrow > 0 and #currow <= #totrow
begin
select #val = val
from #tmp_values
where id = #currow
set #currow = #currow + 1
end
That way you have more control of things and you can re-use the tmp table
I'm not sure I understand what you're talking about doing, but variables cannot be used as table names. Or, really anything that's not a field name. You'll need to use dynamic SQL. That is, assign your SQL string to a variable, an then run EXEC() command.
For example:
DECLARE #sqlcmd varchar(max)
DECLARE #table_name sysname
DECLARE cur_tables FOR
SELECT name FROM sys.tables
OPEN cur_tables
FETCH NEXT FROM cur_tables INTO #table_name
WHILE ##FETCH_STATUS = 0 BEGIN
SET #sqlcmd = 'SELECT TOP 10 * FROM ' + QUOTENAME(#table_name)
EXEC ( #sqlcmd )
FETCH NEXT from cur_tables INTO #table_name
END
CLOSE cur_tables
DEALLOCATE cur_tables
Alternately, if what you mean is that you need a location to store the data that is like a table, then create a temporary tabled for it.

How to change cursor's select statement by some condition?

Does any body know how to set different select statement for the same cursor? I need somethink like this.
DECLARE Temp_Cursor CURSOR FOR
IF(#TempVar = 1)
BEGIN
SELECT CustomerId FROM Customers
END
ELSE IF(#TempVar = 2)
BEGIN
SELECT OrderId FROM Orders
END
OPEN Temp_Cursor;
FETCH NEXT FROM TempCursor INTO #TempObjectId
WHILE ##FETCH_STATUS = 0
BEGIN
.... etc
I have found solution: DECLARE #Temp_Cursor CURSOR
IF(#TempVar = 1)
BEGIN
SET #Temp_Cursor = CURSOR FOR
SELECT CustomerId FROM Customers
END
ELSE IF(#TempVar = 2)
BEGIN
SET #Temp_Cursor = CURSOR FOR
SELECT OrderId FROM Orders
END
OPEN #Temp_Cursor;
FETCH NEXT FROM #TempCursor INTO #TempObjectId
WHILE ##FETCH_STATUS = 0
BEGIN
Put the IF outside and do two different cursors, one for each situation.
Like this:
IF(#TempVar = 1)
BEGIN
DECLARE Temp_Cursor CURSOR FOR
SELECT CustomerId FROM Customers
OPEN Temp_Cursor;
FETCH NEXT FROM TempCursor INTO #TempObjectId
WHILE ##FETCH_STATUS = 0
BEGIN
....
END
ELSE IF(#TempVar = 2)
BEGIN
DECLARE Temp_Cursor CURSOR FOR
SELECT OrderId FROM Orders
OPEN Temp_Cursor;
FETCH NEXT FROM TempCursor INTO #TempObjectId
WHILE ##FETCH_STATUS = 0
BEGIN
....
END
Another possibility is to use Dynamic SQL.
The complete query should be dynamic. That means the execution plan will come out at run time.
Example
Declare #SelectStatement Varchar(50)
Declare #SQL Varchar(50)
IF(#TempVar = 1)
BEGIN
Set #SelectStatement = SELECT CustomerId FROM Customers
END
ELSE IF(#TempVar = 2)
BEGIN
Set #SelectStatement = SELECT OrderId FROM Orders
END
Set #SQL = 'DECLARE Temp_Cursor CURSOR FOR
OPEN Temp_Cursor;
FETCH NEXT FROM TempCursor INTO ' + #TempObjectId +
'WHILE ##FETCH_STATUS = 0
BEGIN '