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
Related
I have a problem with this stored procedure.
Parameters from my application are well defined, but in this procedure, when I am trying to insert a row into table Image, it insert only the first row from #xmlImages and I sent two values. I appreciate any help!
CREATE PROCEDURE sp_AddExplanationAndImage
#Text NVARCHAR(MAX) = NULL,
#ID INT = 0
#ListOfImages NVARCHAR(MAX) = NULL
AS
BEGIN
INSERT INTO dbo.Explanation (ID, Text)
VALUES (#ID, #Text)
DECLARE #xmlImages xml
SET #xmlImages = CAST(#ListOfImages AS xml)
DECLARE #ImageExtension NVARCHAR(MAX)
DECLARE #Name NVARCHAR(MAX)
DECLARE #Content VARBINARY(MAX)
IF (#ListOfImages IS NOT NULL)
BEGIN
DECLARE cursor cursor for--local fast_forward for
SELECT
s.x.value('(Image/Extension)[1]', 'nvarchar(max)'),
s.x.value('(Image/Name)[1]', 'nvarchar(max)'),
s.x.value('(Image/Content)[1]', 'varbinary(max)')
FROM
#xmlImages.nodes('Images') AS s(x)
OPEN CURSOR
FETCH NEXT FROM cursor INTO #ImageExtension, #Name, #Content
WHILE ##FETCH_STATUS = 0
BEGIN
IF ##fetch_status <> 0
BREAK
INSERT INTO dbo.Image (Extension, Name, Content)
SELECT #ImageExtension, #Name, #Content
FETCH NEXT FROM cursorSlike INTO #ImageExtension, #Name, #Content
END
CLOSE cursor
DEALLOCATE cursor
END
The fetch next from inside the WHILE block does not reference the cursor that you're looping through. You named your cursor cursor (I recommend using a more descriptive name BTW) but you're fetching next from cursorSlike.
But I'm not sure you need the loop in the first place (in general, you should avoid loops in SQL Server unless there's no other way to accomplish the task). I haven't done a lot with XML in SQL Server but have you tried this?
INSERT INTO dbo.Image(Extension,Name,Content)
SELECT
s.x.value('(Image/Extension)[1]','nvarchar(max)'),
s.x.value('(Image/Name)[1]','nvarchar(max)'),
s.x.value('(Image/Content)[1]','varbinary(max)')
FROM #xmlImages.nodes('Images')AS s(x)
According to your sample data. you need to get Images/Image nodes. And you dont need to use cursor.
INSERT INTO dbo.Image(Extension,Name,Content)
SELECT
s.x.value('(Extension)[1]','nvarchar(max)'),
s.x.value('(Name)[1]','nvarchar(max)'),
s.x.value('(Content)[1]','nvarchar(max)')
FROM #xmlImages.nodes('Images/Image')AS s(x)
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;
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
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.
I have two simple scripts
One is:
declare #active_from date = '01.03.2014'
declare #active_to date = '01.04.2014'
declare #house_id integer = 11927
----
declare #service_id integer
declare #addendum_id integer
declare #activity_id integer
declare #session_id integer
declare #cur_active_from datetime
declare #cur_active_to datetime
declare #session_license_fee_cur cursor
-- prepare cursor
set #session_license_fee_cur = cursor static for
select activity_id
, addendum_id
, service_id
, active_from
, active_to
from dbo.bills_supp_get_activate_license_fee_for_sessions_by_house(#active_from, #active_to, #house_id)
-- open cursor
open #session_license_fee_cur
fetch next from #session_license_fee_cur into #activity_id, #addendum_id, #service_id, #cur_active_from, #cur_active_to
while (##FETCH_STATUS = 0)
begin
-- get next record
fetch next from #session_license_fee_cur into #activity_id, #addendum_id, #service_id, #cur_active_from, #cur_active_to
end
--
close #session_license_fee_cur
deallocate #session_license_fee_cur
It works less then one second.
The second one is the same, but instead
set #session_license_fee_cur = cursor static for
I use
set #session_license_fee_cur = cursor for
Without "static". It works more than 1 minute.
Why such difference in performance?
The count record in query is about 3000
Static cursor, query is run, result stored in tempdb and then you iterate through it.
So basically it's a readonly copy, no need to synchronise with the underlying data, so no need for locks and such.
Didn't realise it had that much of an overhead, there again I put a lot of effort into not using cursors at all for anything except one off admin tasks.