Scope of table variable within SQL cursor - sql

If I run the below in MS SQL 2008 R2 I get an unexpected result.
create table #DataTable (someID varchar(5))
insert into #DataTable
values ('ID1'),('ID2'),('ID3'),('ID4'),('ID5')
declare #data varchar(8);
declare myCursor cursor for
select someID from #DataTable
open myCursor
FETCH NEXT FROM myCursor INTO
#data
WHILE(##Fetch_Status >=0)
BEGIN
declare #tempTable table (someValue varchar(10))
insert into #tempTable select #data + '_ASDF'
select * from #tempTable
FETCH NEXT FROM myCursor INTO
#data
END
close myCursor
deallocate myCursor
drop table #DataTable
Result of the last iteration:
someValue
ID1_ASDF
ID2_ASDF
ID3_ASDF
ID4_ASDF
ID5_ASDF
I haved expected only to see
someValue
ID5_ASDF
It seems that the table variable #tempTable is kept in scope between cursor iterations - but how is it then possible to re-declare the variable in each iteration ? Makes no sense to me.
I solved it by
delete #tempTable
in each iteration - which also backs up my assumption about it still being in scope.
Can anyone explain this behavior ?

Yes, it does - the scope isn't defined by the begin / end statements, but by the end of a stored procedure, or a go
The scope of a variable is the range of Transact-SQL statements that
can reference the variable. The scope of a variable lasts from the
point it is declared until the end of the batch or stored procedure in
which it is declared.
http://msdn.microsoft.com/en-us/library/ms187953(v=sql.105).aspx

Variable declarations in T-SQL are a bit of an odd beast - variable declarations ignore control flow.
This produces an error:
set #a = 2
This runs without issue, and doesn't print "Never":
if 1=0
begin
print 'Never'
declare #a int
end
set #a = 2
The lifetime of a variable is from the point of declaration until the batch completes.

Related

TSQL not causing infinite loop

Please see the code below:
declare #crimeurn varchar(20)
DECLARE #finalresults TABLE (crime_urn varchar(20))
DECLARE #potentialresults TABLE (crime_urn varchar(20))
insert into #finalresults values ('1')
DECLARE finalresults_cursor CURSOR FOR
SELECT crime_urn FROM #finalresults
OPEN finalresults_cursor
FETCH NEXT FROM finalresults_cursor INTO #crimeurn
WHILE ##FETCH_STATUS = 0
BEGIN
print #crimeurn
INSERT INTO #finalresults
values ('2')
FETCH NEXT FROM finalresults_cursor INTO #crimeurn
END
select * from #finalresults --line 16
CLOSE finalresults_cursor
DEALLOCATE finalresults_cursor
Line 16 displays 5137 or 12,342 rows in SQL studio manager (it randomly varies). I expected the TSQL to cause an infinite loop because there is an insert into the table variable on every iteration of the cursor.
Why does it not cause an infinite loop? i.e. why are there 5,137 or 12,342 rows returned.
You are inserting into a heap.
A heap is unordered. There is no particular guarantee that the row will be inserted after the current row and picked up on the next fetch.
I made a slight amend to your test framework and added an IDENTITY column. In my case it got to row 592,353 before terminating.
As you can see from the results below this final row happened to be inserted on an earlier page in the file (jumped from 1623 to 184) so an allocation ordered scan starting from the penultimate row wouldn't find it.
Code to reproduce.
declare #crimeurn varchar(20)
DECLARE #finalresults TABLE (crime_urn varchar(20), ID int identity)
DECLARE #potentialresults TABLE (crime_urn varchar(20))
insert into #finalresults values ('1')
DECLARE finalresults_cursor CURSOR FOR
SELECT crime_urn FROM #finalresults
OPEN finalresults_cursor
FETCH NEXT FROM finalresults_cursor INTO #crimeurn
WHILE ##FETCH_STATUS = 0
BEGIN
print #crimeurn
INSERT INTO #finalresults
--OUTPUT INSERTED.ID
values ('2')
FETCH NEXT FROM finalresults_cursor INTO #crimeurn
END
select *, sys.fn_PhysLocFormatter(%%physloc%%) from #finalresults --line 16
ORDER BY ID
CLOSE finalresults_cursor
DEALLOCATE finalresults_cursor
Edit: The information below is wrong, but I've left it because that's how I believe it's supposed to work.
By default, cursors do not run in INSENSITIVE or STATIC mode. By default cursors are DYNAMIC and OPTIMISTIC. The documentation on cursors doesn't mention how dynamic cursors behave with respect to INSERTS. INSERT behavior appears to be undocumented.
You may be able to fix this with the SCROLL_LOCKS option, which guarantees order preservation.
Because the cursor's definition is fixed when you run
DECLARE finalresults_cursor CURSOR FOR
SELECT crime_urn FROM #finalresults
It's static after that point. Updating the table variable #finalresults doesn't change the cursor finalresults_cursor.
It's like this:
X = 10
Y = X
X = 20
PRINT X, Y
Outputs this:
20 10
However, if you do not care or know the type of the cursor, you can use the ##CURSOR_ROWS inside your loop to do some "cursor" logic :) .
Here is some documentation on the possible values the ##CURSOR_ROWS variable can have, depending on the cursor's type: .

Is it possible to do an Insert Into stored procedure?

Basically I have a table of data and I would like to take the values of each row and feed it to the stored procedure.
In my perfect world, I'd do this
insert into StoredProcA #var1 #var2
select testdesc, testoption
from tableA
where testoption = 1
Well, I don't think that's going to work. So how, if possible, can I take the all data from a table/query and pass it to the stored procedure?
EDIT: The stored procedure already exists and does quite a bit of processing to the incoming data. The amount of data from the source table itself is only 300 rows.
You will need to do a couple of things to get this going, since your parameter is getting multiple values you need to create a Table Type and make your store procedure accept a parameter of that type.
Since you are passing a TABLE as a parameter you will need to create a TABLE TYPE something as follows
TABLE TYPE
CREATE TYPE dbo.Prco_Table AS TABLE
(
[Val1] Data Type
[Val2] Data Type
)
GO
Stored Procedure to Accept That Type Param
CREATE PROCEDURE mainValues
#TableParam Prco_Table READONLY --<-- Accepts a parameter of that type
AS -- Note it is ReadOnly
BEGIN
SET NOCOUNT ON;
/* do your insert from this parameter or other cool stuff */
INSERT INTO Target_Table (Col1, Col2)
SELECT [Val1] , [Val2]
FROM #TableParam --<-- Table Type variable
END
EXECUTE PROC
Declare a variable of that type and populate it with your values.
DECLARE #Table ClaimData( --<-- Declare a variable of your type
[Val1] Data Type
[Val2] Data Type
);
-- Populate the variable
INSERT INTO #Table ([Val1],[Val2])
SELECT testdesc, testoption
FROM tableA
WHERE testoption = 1
EXECUTE mainValues #Table --<-- Pass this variable of Table Type
I ended up using #logixologist suggestion and used a cursor.
It worked quite well.
declare testCursor cursor
for
select testdesc, testoption
from tableA
where testoption = 1
open testCursor
declare #p1 varchar(max), #p2 varchar(8)
fetch next from testCursor into #p1, #p2
while (##fetch_status <> -1)
begin
if (#fetch_status <> -2)
exec db1.dbo.usr_storedproc #p1, #p2
fetch next from testCursor into #p1, #p2
end
close testCursor
deallocate testCursor
go
If anyone has any improvements, better way, or different way of doing this, please post solution as an answer.

Get/Use the Select value from a Stored Procedure

I have to use a Stored Procedure - that I cannot change/modify. While it is a bit complicated, it can be simplified to be a SELECT statement i.e. with no RETURN or OUTPUT parameter. For the purpose of this discussion assume it to be something like:
SELECT [URL] as imgPath
FROM [mydatasource].[dbo].[DigitalContent]
I need to execute this Stored Procedure passing in the Row ID (SKU) of each row in a Table.
I use a cursor for this as below:
DECLARE #sku varchar(100)
DECLARE #imgPath varchar(500)
DECLARE c CURSOR FOR
SELECT [SKU]
FROM [mydatasource].[dbo].[PROD_TABLE]
OPEN c
FETCH NEXT FROM c INTO #sku
WHILE ##FETCH_STATUS = 0 BEGIN
EXEC #imgPath = [mydatasource].[dbo].[getImage] #sku
--UPDATE PROD_TABLE SET ImgPath=#imgPath WHERE SKU=#sku
SELECT #imgPath AS ImgPath
FETCH NEXT FROM c INTO #sku
END
CLOSE c
DEALLOCATE c
Unfortunately, the return value #imgPath comes back as 0 i.e. success. This results in 0s being inserted into my PROD_TABLE or dumped on the Console. However, as the getImage Stored Procedure executes, it dumps the correct values of imgPath to the console.
How do I get this correct value (i.e. the result of the SELECT in the Stored Procedure) in the Loop above, so that I can correctly update my PROD_TABLE?
Answer
Thanks to RBarryYoung suggestion, my final code looks like:
DECLARE #sku varchar(100)
DECLARE #imgPath varchar(500)
DECLARE c CURSOR FOR
SELECT [SKU]
FROM [mydatasource].[dbo].[PROD_TABLE]
OPEN c
FETCH NEXT FROM c INTO #sku
WHILE ##FETCH_STATUS = 0 BEGIN
CREATE TABLE #OutData ( imgPath varchar(500) )
INSERT INTO #OutData EXEC [mydatasource].[dbo].[getImage] #sku
--UPDATE PROD_TABLE SET ImgPath=(SELECT * FROM #OutData) WHERE SKU=#sku
SELECT * FROM #OutData
DROP TABLE #OutData
FETCH NEXT FROM c INTO #sku
END
CLOSE c
DEALLOCATE c
The performance may not be the best, but at least it works :-).
First, create a temp table (#OutData) whose definition matches the output dataset being returned by getImage.
Then, change your EXEC .. statement to this:
INSERT INTO #OutData EXEC [mydatasource].[dbo].[getImage] #sku
Response to the question: "Is it possible to insert the Key/Row ID into the Temp Table, that way I will not have to TRUNCATE it after each loop iteration?"
First, as a general rule you shouldn't use TRUNCATE on #temp tables as there are some obscure locking problems with that. If you need to do that, just DROP and CREATE them again (they're optimized for that anyway).
Secondly, you cannot modify the dataset returned by a stored procedure in any way. Of course once its in the #temp table you can do what you want with it. So you could add a KeyId column to #OutData. Then inside the loop make a second #temp table (#TmpData), and use INSERT..EXEC to dump into that table instead. Then INSERT..SELECT into #OutData by selecting from #TmpData, adding your KeyID column. Finally, DROP TABLE #TmpData as the last statement in your loop.
This should perform fairly well.
Sometimes executing code entirely inside SQL Server can be more difficult than doing so directly client-side, sending multiple queries calling the SProc (ideally batched in a single round-trip) and processing the results there directly.
Otherwise, the INSERT-EXEC method seems the easier if you absolutely can't modify the called procedure. There are a few alternative methods, all with some additional problems, shown here: http://www.sommarskog.se/share_data.html

user defined type variable is not clearing the value inside the transaction

I have scenario to use the Temporary user defined type variable in transaction, but the temporary variable is not clearing the value inside the transaction.
CREATE TYPE [int_list_table] AS TABLE([item_id] [int] NULL)
GO
DECLARE #int_val BIGINT
-- Create a Temp table with 5 rows
;WITH TEMP(int_val)
AS
(SELECT 1
UNION ALL
SELECT int_val = 1 + int_val FROM TEMP WHERE int_val < 5
)SELECT * INTO #int FROM TEMP;
SET NOCOUNT ON
DECLARE IntCursor CURSOR FAST_FORWARD FOR
SELECT int_val FROM #int
OPEN IntCursor
FETCH NEXT FROM IntCursor INTO #int_val
WHILE(##FETCH_STATUS = 0)
BEGIN
BEGIN TRY
BEGIN TRANSACTION
-- a temprory table to store the integer value
DECLARE #table [int_list_table]
INSERT INTO #table
SELECT 1 WHERE 2 = #int_val
-- Actually #table should have resultset only at #int_val = 2, But once it filled with values even for 3 and 4
SELECT
'User Table Type Value' = item_id,
'Loop Integer Value' = #int_val
FROM
#table
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK
END CATCH
FETCH NEXT FROM IntCursor INTO #int_val
END
CLOSE IntCursor
DEALLOCATE IntCursor
SET NOCOUNT OFF
For the above query the output is
Actually it has to return only the record for 2, but it is not clearing the value once it is initialized. Please guide me to proceed further.
The declaration of the data type does not clear the content of the table. Once the table variable is there it will have whatever you have put into it. The execution of the code does not even have to pass the place where the variable is declared. This will work as well.
if 0 = 1
begin
DECLARE #table [int_list_table]
end
You only have to make sure that the declaration is before any references to the variable.
To fix your code so it does what you want you can of course add delete from #table just before the insert statement.
From the documentation on TSQL variables;
The scope of a variable lasts from the point it is declared until the
end of the batch or stored procedure in which it is declared.
Since your whole example as far as I can tell is a single batch, your #table variable never goes out of scope, and therefore keeps its values until the batch ends.

How do I execute a stored procedure once for each row returned by query?

I have a stored procedure that alters user data in a certain way. I pass it user_id and it does it's thing. I want to run a query on a table and then for each user_id I find run the stored procedure once on that user_id
How would I write query for this?
use a cursor
ADDENDUM: [MS SQL cursor example]
declare #field1 int
declare #field2 int
declare cur CURSOR LOCAL for
select field1, field2 from sometable where someotherfield is null
open cur
fetch next from cur into #field1, #field2
while ##FETCH_STATUS = 0 BEGIN
--execute your sproc on each row
exec uspYourSproc #field1, #field2
fetch next from cur into #field1, #field2
END
close cur
deallocate cur
in MS SQL, here's an example article
note that cursors are slower than set-based operations, but faster than manual while-loops; more details in this SO question
ADDENDUM 2: if you will be processing more than just a few records, pull them into a temp table first and run the cursor over the temp table; this will prevent SQL from escalating into table-locks and speed up operation
ADDENDUM 3: and of course, if you can inline whatever your stored procedure is doing to each user ID and run the whole thing as a single SQL update statement, that would be optimal
try to change your method if you need to loop!
within the parent stored procedure, create a #temp table that contains the data that you need to process. Call the child stored procedure, the #temp table will be visible and you can process it, hopefully working with the entire set of data and without a cursor or loop.
this really depends on what this child stored procedure is doing. If you are UPDATE-ing, you can "update from" joining in the #temp table and do all the work in one statement without a loop. The same can be done for INSERT and DELETEs. If you need to do multiple updates with IFs you can convert those to multiple UPDATE FROM with the #temp table and use CASE statements or WHERE conditions.
When working in a database try to lose the mindset of looping, it is a real performance drain, will cause locking/blocking and slow down the processing. If you loop everywhere, your system will not scale very well, and will be very hard to speed up when users start complaining about slow refreshes.
Post the content of this procedure you want call in a loop, and I'll bet 9 out of 10 times, you could write it to work on a set of rows.
You can do it with a dynamic query.
declare #cadena varchar(max) = ''
select #cadena = #cadena + 'exec spAPI ' + ltrim(id) + ';'
from sysobjects;
exec(#cadena);
Something like this substitutions will be needed for your tables and field names.
Declare #TableUsers Table (User_ID, MyRowCount Int Identity(1,1)
Declare #i Int, #MaxI Int, #UserID nVarchar(50)
Insert into #TableUser
Select User_ID
From Users
Where (My Criteria)
Select #MaxI = ##RowCount, #i = 1
While #i <= #MaxI
Begin
Select #UserID = UserID from #TableUsers Where MyRowCount = #i
Exec prMyStoredProc #UserID
Select
#i = #i + 1, #UserID = null
End
Use a table variable or a temporary table.
As has been mentioned before, a cursor is a last resort. Mostly because it uses lots of resources, issues locks and might be a sign you're just not understanding how to use SQL properly.
Side note: I once came across a solution that used cursors to update
rows in a table. After some scrutiny, it turned out the whole thing
could be replaced with a single UPDATE command. However, in this case,
where a stored procedure should be executed, a single SQL-command
won't work.
Create a table variable like this (if you're working with lots of data or are short on memory, use a temporary table instead):
DECLARE #menus AS TABLE (
id INT IDENTITY(1,1),
parent NVARCHAR(128),
child NVARCHAR(128));
The id is important.
Replace parent and child with some good data, e.g. relevant identifiers or the whole set of data to be operated on.
Insert data in the table, e.g.:
INSERT INTO #menus (parent, child)
VALUES ('Some name', 'Child name');
...
INSERT INTO #menus (parent,child)
VALUES ('Some other name', 'Some other child name');
Declare some variables:
DECLARE #id INT = 1;
DECLARE #parentName NVARCHAR(128);
DECLARE #childName NVARCHAR(128);
And finally, create a while loop over the data in the table:
WHILE #id IS NOT NULL
BEGIN
SELECT #parentName = parent,
#childName = child
FROM #menus WHERE id = #id;
EXEC myProcedure #parent=#parentName, #child=#childName;
SELECT #id = MIN(id) FROM #menus WHERE id > #id;
END
The first select fetches data from the temporary table. The second select updates the #id. MIN returns null if no rows were selected.
An alternative approach is to loop while the table has rows, SELECT TOP 1 and remove the selected row from the temp table:
WHILE EXISTS(SELECT 1 FROM #menuIDs)
BEGIN
SELECT TOP 1 #menuID = menuID FROM #menuIDs;
EXEC myProcedure #menuID=#menuID;
DELETE FROM #menuIDs WHERE menuID = #menuID;
END;
Can this not be done with a user-defined function to replicate whatever your stored procedure is doing?
SELECT udfMyFunction(user_id), someOtherField, etc FROM MyTable WHERE WhateverCondition
where udfMyFunction is a function you make that takes in the user ID and does whatever you need to do with it.
See http://www.sqlteam.com/article/user-defined-functions for a bit more background
I agree that cursors really ought to be avoided where possible. And it usually is possible!
(of course, my answer presupposes that you're only interested in getting the output from the SP and that you're not changing the actual data. I find "alters user data in a certain way" a little ambiguous from the original question, so thought I'd offer this as a possible solution. Utterly depends on what you're doing!)
I like the dynamic query way of Dave Rincon as it does not use cursors and is small and easy. Thank you Dave for sharing.
But for my needs on Azure SQL and with a "distinct" in the query, i had to modify the code like this:
Declare #SQL nvarchar(max);
-- Set SQL Variable
-- Prepare exec command for each distinctive tenantid found in Machines
SELECT #SQL = (Select distinct 'exec dbo.sp_S2_Laser_to_cache ' +
convert(varchar(8),tenantid) + ';'
from Dim_Machine
where iscurrent = 1
FOR XML PATH(''))
--for debugging print the sql
print #SQL;
--execute the generated sql script
exec sp_executesql #SQL;
I hope this helps someone...