Why is my T-SQL cursor executing twice? - sql

SQL-Server Code:
Declare #offers VARCHAR(100)
Declare #offers_seq VARCHAR(100)
Declare Result Cursor
For Select Top 1 Offers,Offers_seq From [REZJQWB01]..ActiveBooking_OffersDetails_Seq
Open Result
While ##fetch_status=0
Begin
Fetch Next From Result Into #offers, #offers_seq
Declare #value VARCHAR(100) = #offers
While len(#value) >= 1
Begin
Set #value = substring(#value,charindex(';',#value)+1,len(#value))
Print #value
End
End
Close Result
Deallocate Result
What I'm trying to accomplish here is to split a set of delimited values present in one cell and then creating a cursor for the complete column. The first time I run this code it gives the below output:
2;6;7;8;9;12;13;14;17;19;21;
6;7;8;9;12;13;14;17;19;21;
7;8;9;12;13;14;17;19;21;
8;9;12;13;14;17;19;21;
9;12;13;14;17;19;21;
12;13;14;17;19;21;
13;14;17;19;21;
14;17;19;21;
17;19;21;
19;21;
21;
2;6;7;8;9;12;13;14;17;19;21;
6;7;8;9;12;13;14;17;19;21;
7;8;9;12;13;14;17;19;21;
8;9;12;13;14;17;19;21;
9;12;13;14;17;19;21;
12;13;14;17;19;21;
13;14;17;19;21;
14;17;19;21;
17;19;21;
19;21;
21;
Ideally It should Print only once but I'm not sure why the loop runs twice. The second time I run this, it gives the output as : 'Command(s) completed successfully'.
Kindly help.
Thanks.

The reason it is running twice is that you aren't doing the fetch until after you've already checked the ##fetch_status.
The steps look like this:
Check ##fetch_status, which is zero, since nothing has been fetched.
Fetch a result
Run your substring code
Check ##fetch_status again, which is still zero, because a record was fetched in the previous step
Fetch another result, which fails but the cursor is still pointing at the same row as before
Run your substring code again, same result
Check ##fetch_status again, which now returns -1 because the previous fetch failed.
For the same reason as you get two results from running it once, you get nothing the second time, because ##fetch_status is -1 from the previous execution. To fix both issues, you need to fetch before checking the status. Usually you'll see one of the following methods employed (psuedocode left as an exercise for you to implement). Typically I use the first option, but some find the second is easier to read:
-- (declare and open cursor)
while 1=1 begin
fetch next from cursor
if ##fetch_status <> 0 break;
-- (do stuff)
end
or
-- (declare and open cursor)
fetch next from cursor
while ##fetch_status = 0 begin
-- (do stuff)
fetch next from cursor
end

Correct Code:
Declare #offers VARCHAR(100)
Declare #offers_seq VARCHAR(100)
Declare Result Cursor
For Select Top 1 Offers,Offers_seq From [REZJQWB01]..ActiveBooking_OffersDetails_Seq
Open Result
While 1=1
Begin
Fetch Next From Result Into #offers, #offers_seq
If ##fetch_status <> 0 Break;
Declare #value VARCHAR(100) = #offers
While len(#value) > 1
Begin
Set #value = substring(#value,charindex(';',#value,2)+1,len(#value))
Set #value = ';'+#value
If len(#value) <= 1 Break;
Print #value
End
End
Close Result
Deallocate Result
Result:
;6;7;8;9;12;13;14;17;19;21;
;7;8;9;12;13;14;17;19;21;
;8;9;12;13;14;17;19;21;
;9;12;13;14;17;19;21;
;12;13;14;17;19;21;
;13;14;17;19;21;
;14;17;19;21;
;17;19;21;
;19;21;
;21;

Related

How to compare each string from first column with each string in second column in TSQL?

Here is the code I tried:
declare #o1 nvarchar(255)
declare #o2 nvarchar(255)
declare data cursor for
select o1.Name, o2.Name from MyDB.dbo.Table1 as o1, MyDB.dbo.MyTable2 as o2;
OPEN data;
-- Perform the first fetch.
FETCH NEXT FROM data into #o1, #o2;
-- Check ##FETCH_STATUS to see if there are any more rows to fetch.
WHILE ##FETCH_STATUS = 0
BEGIN
-- This is executed as long as the previous fetch succeeds.
FETCH NEXT FROM data INTO #o1, #o2;
Print 'Name1: ' + #o1
WHILE ##FETCH_STATUS = 0
BEGIN
-- This is executed as long as the previous fetch succeeds.
FETCH NEXT FROM data INTO #o1, #o2;
Print 'Name2: ' + #o2
END
END
CLOSE data;
DEALLOCATE data;
GO
I am getting two columns in my query and both are nvarchar(255).
I want to compare each value from the first column with each value of the second column. That can be achieved with loop inside a loop, but I don't know what should I do with the cursor part.
Should I put a variable and keep fetch status separately?
Or somethings else will do the trick?
I think that you don't need a cursor you can use select :
Select o1, O2
from table1
where o1 in (select o2 from table1)

SQL Server : sum by cursor

This SQL Server 2012 function, and I want it to return all values by the cursor, but this SUM only last record from base.
When I want to SUM by:
SET #AllSalary = #salarya + #AllSalary
it shows NULL.
I don't know what is the problem, it could be syntax error, fact is it doesn't display the desired result.
(param #montha INT, will by used later)
CREATE FUNCTION allCasR
(
#montha INT
)
RETURNS INT
AS
BEGIN
-- Declare the return variable here
DECLARE #AllSalary INT;
DECLARE #salarya FLOAT;
DECLARE #tymC FLOAT;
-- Add the T-SQL statements to compute the return value here
DECLARE kursor_pensja CURSOR FOR
SELECT contracts.salary
FROM dbo.contracts ;
OPEN kursor_pensja;
FETCH NEXT FROM kursor_pensja INTO #salarya
WHILE ##FETCH_STATUS=0
BEGIN
SET #AllSalary =+ #salarya
FETCH NEXT FROM kursor_pensja INTO #salarya
END
CLOSE kursor_pensja
DEALLOCATE kursor_pensja
RETURN #AllSalary;
END
WHY on earth would you want to use a cursor for this??
Just use a SUM and since you're seeing NULL values, use ISNULL to convert NULL values into 0 (zero):
CREATE FUNCTION allCasR (#montha INT)
RETURNS INT
AS
BEGIN
DECLARE #AllSalary INT;
SELECT #AllSalary = SUM(ISNULL(salary, 0))
FROM dbo.Contracts
RETURN #AllSalary;
END
Update: if you must use a cursor as an exercise, then you need to make sure
that you properly initialize your value for #AllSalary to 0 (otherwise it's NULL from the beginning and will never get any other value!)
that you take care of the NULL values in the database table while iterating over the rows that would cause your entire SUM to be NULL in the end (either by excluding those values from the cursor with a WHERE salary IS NOT NULL or by applying a ISNULL(....) to the value being summed up):
Code should be:
CREATE FUNCTION allCasR (#montha INT)
RETURNS INT
AS
BEGIN
-- Declare the return variable here
DECLARE #AllSalary INT;
DECLARE #salarya FLOAT;
DECLARE #tymC FLOAT;
-- Add the T-SQL statements to compute the return value here
DECLARE kursor_pensja CURSOR FOR
SELECT contracts.salary
FROM dbo.contracts ;
-- you need to INITIALIZE this value to 0 !!!!!
SET #AllSalary = 0;
OPEN kursor_pensja;
FETCH NEXT FROM kursor_pensja INTO #salarya
WHILE ##FETCH_STATUS=0
BEGIN
-- you need to make sure to use ISNULL(.., 0) to avoid a NULL value in the SUM
SET #AllSalary += ISNULL(#salarya, 0);
FETCH NEXT FROM kursor_pensja INTO #salarya
END
CLOSE kursor_pensja
DEALLOCATE kursor_pensja
RETURN #AllSalary;
END
SET #AllSalary =+ #salarya
Your + sign is after the =, that's why it doesn't work as expected.
It should be:
SET #AllSalary += #salarya;
EDIT:
"That return NULL"
If it returns NULL that means some of your values are NULL.
Use SELECT ISNULL(contracts.salary, 0) rather than SELECT contracts.salary.

How to get the first row details using rowcount?

I have a temp variable called #rows having nearly 10000 records in a stored procedure like this
Create Procedure input
as
begin
declare #input_data table(......)
insert into (.....) from ....
#rows= select ##rowcount
while(#rows > o)
begin
--- I need to process each row like
select ... where #row=1 --like this repeatedly upto #rows = 10000
end
How should I achieve this.Please help me
Thanks in advance
You can directly update the table based on some conditiuons and using CASE statement insted of using while loop.
You may achieve your goal using a CURSOR
DECLARE #ID AS INT
DECLARE TestCursor CURSOR
FOR (SELECT ID FROM Test)
OPEN TestCursor
FETCH NEXT FROM TestCursor INTO #ID
WHILE ##Fetch_Status = 0
BEGIN
--Your Code Here
PRINT #ID --Print For Testing
FETCH NEXT FROM TestCursor INTO #ID
END
CLOSE TestCursor
DEALLOCATE TestCursor
Remember : using cursors will lead to a performance loss
Instead use CASE statements in queries for conditional selections/updates, as described in another answer

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.

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