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.
Related
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;
I wonder whether cursor for query with #variable parameter can be re-used (CLOSE + OPEN) when value of the #variable changes. To me it looks that it always needs CLOSE + DEALLOCATE + DECLARE + OPEN to take new value of the #variable into effect. Perhaps no big deal, but I wanted to know whether DEALLOCATE + DECLARE can be left out between uses.
Here you have complete simple example to try it out:
DECLARE #ta TABLE (a int);
INSERT INTO #ta (a) VALUES (1),(2),(4),(8),(16),(32),(64);
---------
DECLARE #current_a int;
DECLARE #threshold int = 12;
DECLARE crs1 CURSOR FOR SELECT a FROM #ta WHERE a < #threshold;
--- first cursor use
OPEN crs1;
FETCH NEXT FROM crs1 INTO #current_a;
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #threshold, #current_a
FETCH NEXT FROM crs1 INTO #current_a;
END;
CLOSE crs1;
DEALLOCATE crs1; -- can this be left out?
SET #threshold = 3;
DECLARE crs1 CURSOR FOR SELECT a FROM #ta WHERE a < #threshold; -- can this be left out?
--- second cursor use
OPEN crs1;
FETCH NEXT FROM crs1 INTO #current_a;
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #threshold, #current_a
FETCH NEXT FROM crs1 INTO #current_a;
END;
CLOSE crs1;
DEALLOCATE crs1;
This was linearized example, but the question applies also to nested cursors when outer cursor changes the query parameter of inner cursor.
One way is to use cursor variable:
DECLARE
{
{ #local_variable [AS] data_type | [ = value ] }
| { #cursor_variable_name CURSOR }
#cursor_variable_name
Is the name of a cursor variable. Cursor variable names must begin with an at (#) sign and conform to the rules for identifiers.
CURSOR
Specifies that the variable is a local cursor variable.
A cursor variable:
Can be the target of either a cursor type or another cursor variable. For more information, see SET #local_variable.
Can be referenced as the target of an output cursor parameter in an EXECUTE statement if the cursor variable does not have a cursor
currently assigned to it.
Should be regarded as a pointer to the cursor.
DECLARE crs1 CURSOR FOR SELECT a FROM #ta WHERE a < #threshold;
-- could be changed to
DECLARE #crs1 CURSOR;
SET #crs1 = CURSOR FOR SELECT a FROM #ta WHERE a < #threshold;
LiveDemo
in the code below when I run it in Degug mode I can see the variables contain values, however when I select them they show NULL, any ideas? I need to eventually do an Update back to the table [dbo].[HistData]
with the values where RecordID = some number. Any ideas welcome.
-- Declare the variables to store the values returned by FETCH.
DECLARE #HD_TckrPercent decimal(6,3) -- H2 in above formula
DECLARE #HD_CloseLater decimal(9,2) -- F2 in above formula
DECLARE #HD_CloseEarlier decimal(9,2) -- F3 in above formula
DECLARE #RowsNeeded INT
DECLARE #RecordCOUNT INT
SET #RowsNeeded = 2
set #RecordCOUNT = 0 -- to initialize it
DECLARE stocks_cursor CURSOR FOR
SELECT top (#RowsNeeded) [TCKR%], [Stock_Close] FROM [dbo].[HistData]
ORDER BY [RecordID]
OPEN stocks_cursor
-- Perform the first fetch and store the values in variables.
-- Note: The variables are in the same order as the columns
-- in the SELECT statement.
-- Check ##FETCH_STATUS to see if there are any more rows to fetch.
WHILE ##FETCH_STATUS = 0
BEGIN
-- Concatenate and display the current values in the variables.
-- This is executed as long as the previous fetch succeeds.
set #RecordCOUNT = (#RecordCOUNT + 1)
Print #HD_CloseLater
IF #RecordCOUNT = 1
BEGIN
FETCH NEXT FROM stocks_cursor
INTO #HD_TckrPercent, #HD_CloseLater
END
ELSE
BEGIN
FETCH NEXT FROM stocks_cursor
INTO #HD_TckrPercent, #HD_CloseEarlier
END
Select #HD_TckrPercent
Select #HD_CloseLater
Select #HD_CloseEarlier
END
CLOSE stocks_cursor
DEALLOCATE stocks_cursor
GO
I'm in the middle of writing an achievement module for a website we run. This particular bit of code will run on SQL Server at the end of a given timeframe to award the achievements (which will be notified through the client the next time the site is visited).
So I need to look at all the teams and all the "end" achievements (which are independent of the teams). Each achievement has one or more rules that must be met. Once I've determined that the achievement is passed, I award the achievement to all of the participants of that team.
I was handling this through cursors, and I got an error, so when I went to google the problem, I got endless links on forums of "YOU DUMB $&#$ WHY ARE YOU USING CURSORS" (to paraphrase). I figured while I was solving my problem, I may as well replace what I have using a set-based approach (if possible).
The alternative examples I found were all basic update scenarios using single nested loops on tables that have keys back to each other, and honestly, I wouldn't have considered using cursors in those scenarios to begin with.
What I have below is not entirely syntactically correct, but I can figure that out on my own by playing around. This should give a clear idea of what I'm trying to accomplish, however:
declare #TimeframeID int;
declare #AchievementID int;
declare #q1 int;
declare #q2 int;
declare #TeamID int;
declare #TotalReg int;
declare #TotalMin int;
declare #AvgMin decimal(10, 2);
declare #AggType varchar(50);
declare #Pass bit = 1;
declare #Email varchar(50);
declare #ParticipantID int;
select #TimeframeID = MAX(TimeframeID) from Timeframes
where IsActive = 1;
declare team_cur CURSOR FOR
select
t.TeamID,
(select COUNT(1) from Registrations r
where r.TeamID = t.TeamID and r.TimeframeID = #TimeframeID) TotalReg,
(select SUM(Minutes) from Activities a
inner join Registrations r on r.RegistrationID = a.RegistrationID
where r.TeamID = t.TeamID and r.TimeframeID = #TimeframeID) TotalMin
from Teams t
where Active = 1
group by TeamID;
declare ach_cur CURSOR FOR
select AchievementID from luAchievements
where TimeframeID = #TimeframeID and AchievementType = 'End';
declare rule_cur CURSOR for
select Quantity1, Quantity2, AggregateType
from AchievementRule_Links arl
inner join luAchievementRules ar on ar.RuleID = arl.RuleID
where arl.AchievementID = #AchievementID;
open team_cur;
fetch next from team_cur
into #TeamID, #TotalReg, #TotalMin;
while ##FETCH_STATUS = 0
begin
open ach_cur;
fetch next from ach_cur
into #AchievementID;
while ##FETCH_STATUS = 0
begin
open rule_cur;
fetch next from rule_cur
into #q1, #q2, #AggType;
while ##FETCH_STATUS = 0
begin
if #AggType = 'Total'
begin
if #q1 > #TotalReg
begin
set #Pass = 0;
end
end
else if #AggType = 'Average'
begin
print 'do this later; need to get specs';
end
fetch next from rule_cur
into #q1, #q2, #AggType;
end
close rule_cur;
deallocate rule_cur;
-- if passed, award achievement to all team members
if #Pass = 1
begin
declare reg_cursor cursor for
select max(p.Email) from Participants p
inner join Registrations reg on reg.ParticipantID = p.ParticipantID
where reg.TeamID = #TeamID;
open reg_cursor;
fetch next from reg_cursor
into #Email;
while ##FETCH_STATUS = 0
begin
exec ProcessAchievement #AchievementID, #Email, 0;
fetch next from reg_cursor
into #Email;
end
close reg_cursor;
deallocate reg_cursor;
-- award achievement to team
exec ProcessTeamAchievement #AchievementID, #TeamID;
end
fetch next from ach_cur
into #AchievementID;
end
close ach_cur;
deallocate ach_cur;
fetch next from team_cur
into #TeamID, #TotalReg, #TotalMin;
end
close team_cur;
deallocate team_cur;
Is there a set-based alternative to what I'm doing here? I need to add that this is running against a small set of records, so performance isn't my concern; best practices for future gargantuan updates are.
To make this set-based you need to fix the procs you are calling so that they either have a table-valued parameter or the code in the proc joins to a table where you have the records you want to process. In the second case you would either mark them as processsed or delete them when you are finished.
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.