Why is my cursor recording 1 extra count? - sql-server

I created a function with a cursor to count all the entrees in my other table.
When I do PRINT dbo.cursorEnroll ();
I get 11 as an output when I only had 10 entrees in my table.
##FETCH = 0 should mean fetch is successful, and thus should only SET studentsEnrolled 10 times. I am confused where this extra count comes from.
DISCLAIMER: I know this isn't the best way to count the number of entries in a table. However, I am just learning and practicing the use of cursors.
CREATE FUNCTION dbo.cursorEnroll ()
RETURNS INT AS
BEGIN
DECLARE #studentsEnrolled INT
SET #studentsEnrolled = 0
DECLARE myCursor CURSOR FOR
SELECT enrollementID
FROM courseEnrollment
OPEN myCursor;
FETCH NEXT FROM myCursor INTO #studentsEnrolled
WHILE ##FETCH_STATUS = 0
BEGIN
SET #studentsEnrolled = #studentsEnrolled+1
FETCH NEXT FROM myCursor INTO #studentsEnrolled
END;
CLOSE myCursor
RETURN #studentsEnrolled
END;

Because you fetch enrollementID into #studentsEnrolled and then add 1. For last row enrollementID = 10 then you get 11 as result.

Related

T-SQL While Infinite Loop

The goal of the below script is to delete all records in a table for all the distinct users on it except the two first records for each user.
The thing is that the script goes into an infinite loop between these two lines
WHILE ##FETCH_STATUS = 0
SET #Event = 0;
The complete script is
DECLARE #Event int, #User int;
DECLARE cUsers CURSOR STATIC LOCAL FOR SELECT DISTINCT(UserID) FROM Identifications;
OPEN cUsers
FETCH NEXT FROM cUsers INTO #User;
WHILE ##FETCH_STATUS = 0
SET #Event = 0;
BEGIN
DECLARE cRows CURSOR STATIC LOCAL FOR
SELECT EventIdentificacionId FROM Identifications WHERE UserId = #User AND EventIdentificacionId NOT IN
(SELECT TOP 2 EventIdentificacionId FROM Identifications WHERE UserId = #User ORDER BY EventIdentificacionId);
OPEN cRows
FETCH NEXT FROM cRows INTO #Event;
WHILE ##FETCH_STATUS = 0
BEGIN
DELETE FROM Identifications WHERE EventIdentificacionId = #Event;
FETCH NEXT FROM cRows INTO #Event;
END
CLOSE cRows;
DEALLOCATE cRows;
FETCH NEXT FROM cUsers INTO #User;
END
CLOSE cUsers;
DEALLOCATE cUsers;
Can anybody give me some solution/explanation please?
As I wrote in my comment, There are far better ways to do such a thing than using a cursor, let alone a couple of nested cursors.
One such better option is to use a common table expression and row_number, and then delete the rows directly from the common table expression.
I'm not entirely sure this code is correct because I have no real way to test it as you didn't provide sample data or desired results, but I came up with that based on the code in the question:
;WITH CTE AS
(
SELECT UserId,
EventIdentificacionId,
ROW_NUMBER() OVER(PARTITION BY UserId ORDER BY EventIdentificacionId) As Rn
FROM Identifications
)
DELETE
FROM CTE
WHERE Rn > 2 -- Delete all but the first two rows
Change this line as shown:
DECLARE #Event int = 0, #User int = 0;
And remove this line
SET #Event = 0;
The reason you have an infinite loop is that this code:
WHILE ##FETCH_STATUS = 0
SET #Event = 0;
BEGIN
Is actually this:
-- A loop of a single instruction, with no exit criteria
WHILE ##FETCH_STATUS = 0 SET #Event = 0;
-- begin a new code block, with no condition or loop
BEGIN

Getting multiple columns selected into variables

I am having problems with the following code:
/* Cursor */
DECLARE #RelationCursor CURSOR
SET #RelationCursor = (SELECT [fms].[dbo].[Relation].[RELATIONCODE], [fms].[dbo].[Relation].[COMPANYNAME] INTO #RelationCode, #CompanyName FROM [fms].[dbo].[Relation])
OPEN #RelationCursor
FETCH NEXT FROM #RelationCursor INTO #RelationCode, #CompanyName
WHILE ##FETCH_STATUS = 0
BEGIN
print(#RelationCode)
print(#CompanyName)
FETCH NEXT FROM #RelationCursor INTO #RelationCode, #CompanyName
END
CLOSE #RelationCursor
I am trying to get RelationCode and Companyname into #RelationCode and #Companyname so I can use them in the cursor loop. But I get an error in the SELECT query:
Msg 156, Level 15, State 1, Procedure spLoadProfits, Line 21
Incorrect syntax near the keyword 'INTO'.
But the query seems completely fine to me and I can't seem to figure out the problem about this. Does anyone have an idea on how to fix this?
A cursor name should not start with #, and also you need to deallocate the cursor when you are done with it.
Try this instead:
DECLARE #RelationCode int, -- I guessed the data type, change if needed
#CompanyName varchar(100) -- I guessed the data type, change if needed
DECLARE RelationCursor CURSOR FOR
SELECT [fms].[dbo].[Relation].[RELATIONCODE], [fms].[dbo].[Relation].[COMPANYNAME]
FROM [fms].[dbo].[Relation]
OPEN RelationCursor
FETCH NEXT FROM RelationCursor INTO #RelationCode, #CompanyName
WHILE ##FETCH_STATUS = 0
BEGIN
print(#RelationCode)
print(#CompanyName)
FETCH NEXT FROM RelationCursor INTO #RelationCode, #CompanyName
END
CLOSE RelationCursor
DEALLOCATE RelationCursor;

SQL Server Error The multi-part identifier .. could not be bound

Below is a portion of a stored procedure I'm working on (the snippet can be executed) which returns the error
The multi-part identifier "CountCursor.ID" could not be bound.
But why?
DECLARE #MANTECCount int
DECLARE #ThirdPartyCount int
DECLARE #MemberNo nchar(4)
SET #MANTECCount = 0
SET #ThirdPartyCount = 0
SET #MemberNo = NULL
DECLARE CountCursor CURSOR FOR
SELECT ID
FROM CIF
OPEN CountCursor
FETCH NEXT FROM CountCursor
WHILE ##FETCH_STATUS = 0
BEGIN
SET #MemberNo = CountCursor.ID
FETCH NEXT FROM CountCursor;
END;
CLOSE CountCursor;
DEALLOCATE CountCursor;
You need to assign the fetched value from Cursor(CountCursor) into a variable to use it inside Cursor. More info on Cursor's can be found here
Declare #id int
......
FETCH NEXT FROM CountCursor into #id
WHILE ##FETCH_STATUS = 0
BEGIN
SET #MemberNo = #id
FETCH NEXT FROM CountCursor into #id;
END;
....
Note: Cursor's can have awful performance. If you add the original code we can try and change it to SET Based Approach code.
You need to fetch the value into something. At first, I was wondering about the code structure, then I realized that Python (and probably other languages) treat cursors the same way.
In any case, you can put the value directly into #MemberNo:
DECLARE #MANTECCount int;
DECLARE #ThirdPartyCount int;
DECLARE #MemberNo nchar(4);
SET #MANTECCount = 0;
SET #ThirdPartyCount = 0;
SET #MemberNo = NULL;
DECLARE CountCursor CURSOR FOR
SELECT ID
FROM CIF;
OPEN CountCursor;
FETCH NEXT FROM CountCursor INTO #MemberNo;
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM CountCursor INTO #MemberNo;
END;
CLOSE CountCursor;
DEALLOCATE CountCursor;
GO
I'm not sure what the code should be doing. Presumably, you have more interesting code than this.

how to solve cursor work only first time in sql server

i have declare a cursor in one procedure and make a loop inside it. but when i execute procedure cursor
occur only first time what happen to this i want it occur everytime when procedure is executed.
DECLARE SUP_CUR CURSOR SCROLL DYNAMIC FOR SELECT * FROM #saleSup
DECLARE #SUP_TEMP AS INT
OPEN SUP_CUR
WHILE ##FETCH_STATUS =0
BEGIN
FETCH NEXT FROM SUP_CUR INTO #SUP_TEMP
SELECT COUNT (DISTINCT saleRepId),userId FROM #tabletemp WHERE userId = #SUP_TEMP GROUP BY userId
END
CLOSE SUP_CUR;
DEALLOCATE SUP_CUR;
Put FETCH NEXT to end of loop and add first FETCH
DECLARE SUP_CUR CURSOR SCROLL DYNAMIC FOR SELECT * FROM #saleSup
DECLARE #SUP_TEMP AS INT
OPEN SUP_CUR
FETCH NEXT FROM SUP_CUR INTO #SUP_TEMP
WHILE ##FETCH_STATUS =0
BEGIN
SELECT COUNT (DISTINCT saleRepId),userId FROM #tabletemp WHERE userId = #SUP_TEMP GROUP BY userId
FETCH NEXT FROM SUP_CUR INTO #SUP_TEMP
END
CLOSE SUP_CUR;
DEALLOCATE SUP_CUR;
Change your cursor like this. You need to add fetch before the start of while loop to fetch the first row and at the end of while loop add FETCH NEXT to fetch the next row
DECLARE #SUP_TEMP AS INT
DECLARE SUP_CUR CURSOR SCROLL DYNAMIC FOR
SELECT *
FROM #saleSup
OPEN SUP_CUR
-- Perform the first fetch.
FETCH NEXT FROM SUP_CUR INTO #SUP_TEMP
-- Check ##FETCH_STATUS to see if there are any more rows to fetch.
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT Count (DISTINCT saleRepId),
userId
FROM #tabletemp
WHERE userId = #SUP_TEMP
GROUP BY userId
-- This is executed as long as the previous fetch succeeds.
FETCH NEXT FROM SUP_CUR INTO #SUP_TEMP
END
CLOSE SUP_CUR;
DEALLOCATE SUP_CUR;
now i know what problem with it. i just put
FETCH NEXT FROM SUP_CUR INTO #SUP_TEMP
before start loop and move FETCH NEXT to below select statement in loop and that work properly
I don't think you need to use a cursor (they are bad practice anyway), try:
SELECT COUNT (DISTINCT saleRepId),userId
FROM #tabletemp
GROUP BY userId

Using fetch next with where in cursor

Is there any option to search inside cursor?
For example: I have a table(MyTable) with row number and value,
that I want to copy to another table(TestTable),
but let's say that if there was a value >= 5 then the next value,
that I want to copy should be <= 3.
I can use something like this:
create table TestTable
(row tinyint,
value tinyint)
declare #row tinyint, #value tinyint, #trigger bit
declare test_cursor cursor fast_forward for
select row,value from MyTable order by row
open test_cursor
fetch next from test_cursor into #row,#value
set #trigger = 0
while ##FETCH_STATUS = 0
begin
if #trigger = 0
begin
insert into TestTable values (#row,#value)
if #value >= 5 set #trigger = 1
end
else if #value <= 3
begin
insert into TestTable values (#row,#value)
set #trigger = 0
end
fetch next from test_cursor into #row,#value
end
close test_cursor
deallocate test_cursor
That will work, but my question is: is there an any way to search inside cursor
for the next falue that <= 3 once trigger = 1,
instead of fetching next row over and over every time?
No, cursors don't support the kind of querying that you're after. You will have to visit each value and check it in the loop.

Resources