TSQL not causing infinite loop - sql

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: .

Related

What is the benefit of iterating updates using cursors in SQL? [duplicate]

I want to use a database cursor; first I need to understand what its use and syntax are, and in which scenario we can use this in stored procedures? Are there different syntaxes for different versions of SQL Server?
When is it necessary to use?
Cursors are a mechanism to explicitly enumerate through the rows of a result set, rather than retrieving it as such.
However, while they may be more comfortable to use for programmers accustomed to writing While Not RS.EOF Do ..., they are typically a thing to be avoided within SQL Server stored procedures if at all possible -- if you can write a query without the use of cursors, you give the optimizer a much better chance to find a fast way to implement it.
In all honesty, I've never found a realistic use case for a cursor that couldn't be avoided, with the exception of a few administrative tasks such as looping over all indexes in the catalog and rebuilding them. I suppose they might have some uses in report generation or mail merges, but it's probably more efficient to do the cursor-like work in an application that talks to the database, letting the database engine do what it does best -- set manipulation.
cursor are used because in sub query we can fetch record row by row
so we use cursor to fetch records
Example of cursor:
DECLARE #eName varchar(50), #job varchar(50)
DECLARE MynewCursor CURSOR -- Declare cursor name
FOR
Select eName, job FROM emp where deptno =10
OPEN MynewCursor -- open the cursor
FETCH NEXT FROM MynewCursor
INTO #eName, #job
PRINT #eName + ' ' + #job -- print the name
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM MynewCursor
INTO #ename, #job
PRINT #eName +' ' + #job -- print the name
END
CLOSE MynewCursor
DEALLOCATE MynewCursor
OUTPUT:
ROHIT PRG
jayesh PRG
Rocky prg
Rocky prg
Cursor might used for retrieving data row by row basis.its act like a looping statement(ie while or for loop).
To use cursors in SQL procedures, you need to do the following:
1.Declare a cursor that defines a result set.
2.Open the cursor to establish the result set.
3.Fetch the data into local variables as needed from the cursor, one row at a time.
4.Close the cursor when done.
for ex:
declare #tab table
(
Game varchar(15),
Rollno varchar(15)
)
insert into #tab values('Cricket','R11')
insert into #tab values('VollyBall','R12')
declare #game varchar(20)
declare #Rollno varchar(20)
declare cur2 cursor for select game,rollno from #tab
open cur2
fetch next from cur2 into #game,#rollno
WHILE ##FETCH_STATUS = 0
begin
print #game
print #rollno
FETCH NEXT FROM cur2 into #game,#rollno
end
close cur2
deallocate cur2
Cursor itself is an iterator (like WHILE). By saying iterator I mean a way to traverse the record set (aka a set of selected data rows) and do operations on it while traversing. Operations could be INSERT or DELETE for example. Hence you can use it for data retrieval for example. Cursor works with the rows of the result set sequentially - row by row. A cursor can be viewed as a pointer to one row in a set of rows and can only reference one row at a time, but can move to other rows of the result set as needed.
This link can has a clear explanation of its syntax and contains additional information plus examples.
Cursors can be used in Sprocs too. They are a shortcut that allow you to use one query to do a task instead of several queries. However, cursors recognize scope and are considered undefined out of the scope of the sproc and their operations execute within a single procedure. A stored procedure cannot open, fetch, or close a cursor that was not declared in the procedure.
I would argue you might want to use a cursor when you want to do comparisons of characteristics that are on different rows of the return set, or if you want to write a different output row format than a standard one in certain cases. Two examples come to mind:
One was in a college where each add and drop of a class had its own row in the table. It might have been bad design but you needed to compare across rows to know how many add and drop rows you had in order to determine whether the person was in the class or not. I can't think of a straight forward way to do that with only sql.
Another example is writing a journal total line for GL journals. You get an arbitrary number of debits and credits in your journal, you have many journals in your rowset return, and you want to write a journal total line every time you finish a journal to post it into a General Ledger. With a cursor you could tell when you left one journal and started another and have accumulators for your debits and credits and write a journal total line (or table insert) that was different than the debit/credit line.
CREATE PROCEDURE [dbo].[SP_Data_newUsingCursor]
(
#SCode NVARCHAR(MAX)=NULL,
#Month INT=NULL,
#Year INT=NULL,
#Msg NVARCHAR(MAX)=null OUTPUT
)
AS
BEGIN
DECLARE #SEPERATOR as VARCHAR(1)
DECLARE #SP INT
DECLARE #VALUE VARCHAR(MAX)
SET #SEPERATOR = ','
CREATE TABLE #TempSiteCode (id int NOT NULL)
WHILE PATINDEX('%' + #SEPERATOR + '%', #SCode ) <> 0
BEGIN
SELECT #SP = PATINDEX('%' + #SEPERATOR + '%' ,#SCode)
SELECT #VALUE = LEFT(#SCode , #SP - 1)
SELECT #SCode = STUFF(#SCode, 1, #SP, '')
INSERT INTO #TempSiteCode (id) VALUES (#VALUE)
END
DECLARE
#EmpCode bigint=null,
#EmpName nvarchar(50)=null
CREATE TABLE #TempEmpDetail
(
EmpCode bigint
)
CREATE TABLE #TempFinalDetail
(
EmpCode bigint,
EmpName nvarchar(500)
)
DECLARE #TempSCursor CURSOR
DECLARE #TempFinalCursor CURSOR
INSERT INTO #TempEmpDetail
(
EmpCode
)
(
SELECT DISTINCT EmpCode FRom tbl_Att_MSCode
WHERE tbl_Att_MSCode.SiteCode IN (SELECT id FROM #TempSiteCode)
AND fldMonth=#Month AND fldYear=#Year
)
SET #TempSiteFinalCursor=CURSOR FOR SELECT EmpCode FROM #TempEmpDetail
OPEN #TempSiteFinalCursor
FETCH NEXT FROM #TempSiteFinalCursor INTO #EmpCode,#SiteCode,#HrdCompanyId
WHILE ##FETCH_STATUS=0
BEGIN
SEt #EmpName=(SELECt EmpName FROm tbl_Employees WHERE EmpCode=#EmpCode)
INSERT INTO #TempFinalDetail
(
EmpCode,
EmpName
)
VALUES
(
#EmpCode,
#EmpName
)
FETCH NEXT FROM #TempSiteFinalCursor INTO #EmpCode
END
SELECT EmpCode,
EmpName
FROM #TempFinalDetail
DEALLOCATE #TempSiteFinalCursor
DROP TABLE #TempEmpDetail
DROP TABLE #TempFinalDetail
END

Cursor - Declare Select-Statement executed multiple times?

I've got a question regarding cursor in t-sql.
when i do a cursor like that, it will end up in an endless loop.
drop table [dbo].[cursorcheck]
GO
CREATE TABLE [dbo].[cursorcheck](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Wert] [varchar](10) NOT NULL
) ON [PRIMARY]
GO
delete dbo.cursorcheck
GO
insert into dbo.cursorcheck
select 'Insert'
GO
DECLARE #vendor_id int, #vendor_name nvarchar(50);
DECLARE vendor_cursor CURSOR FOR
SELECT ID, wert
FROM dbo.cursorcheck
WHERE wert = 'insert';
OPEN vendor_cursor
FETCH NEXT FROM vendor_cursor
INTO #vendor_id, #vendor_name
WHILE ##FETCH_STATUS = 0
BEGIN
insert into dbo.cursorcheck
select 'Insert'
FETCH NEXT FROM vendor_cursor
INTO #vendor_id, #vendor_name
END
CLOSE vendor_cursor;
DEALLOCATE vendor_cursor;
I'm a bit confused about this behavior. Does that mean that the select script, the cursor was declared for, is executed multiple times? i got the same effect when i insert into that table from another transaction while debugging the cursor. the problem is solved when i use the "static" keyword for declaring the cursor. but i'm confused about the behavior of the cursor when i don't use the static keyword.
any explanation?
Regards,
Reto
The key is you try to read and insert the same table in loop (so it is like "a snake eating own tail"):
Cursor:
DECLARE vendor_cursor CURSOR FOR
SELECT ID, wert
FROM dbo.cursorcheck
WHERE wert = 'insert';
And loop:
WHILE ##FETCH_STATUS = 0
BEGIN
insert into dbo.cursorcheck
select 'Insert'
FETCH NEXT FROM vendor_cursor
INTO #vendor_id, #vendor_name
END
For second question: The problem is solved when i use the "static":
STATIC CURSOR
The complete result set of a static cursor is built in tempdb when the
cursor is opened. A static cursor always displays the result set as it
was when the cursor was opened.
and:
The cursor does not reflect any changes made in the database that
affect either the membership of the result set or changes to the
values in the columns of the rows that make up the result set. A
static cursor does not display new rows inserted in the database after
the cursor was opened, even if they match the search conditions of the
cursor SELECT statement

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

process each row in table in stored procedure using cursor

My Scenario is bit different. what i am doing in my stored procedure is
Create Temp Table and insert rows it in using "Cursor"
Create Table #_tempRawFeed
(
Code Int Identity,
RawFeed VarChar(Max)
)
Insert Data in temp table using cursor
Set #GetATM = Cursor Local Forward_Only Static For
Select DeviceCode,ReceivedOn
From RawStatusFeed
Where C1BL=1 AND Processed=0
Order By ReceivedOn Desc
Open #GetATM
Fetch Next
From #GetATM Into #ATM_ID,#Received_On
While ##FETCH_STATUS = 0
Begin
Set #Raw_Feed=#ATM_ID+' '+Convert(VarChar,#Received_On,121)+' '+'002333'+' '+#ATM_ID+' : Bills - Cassette Type 1 - LOW '
Insert Into #_tempRawFeed(RawFeed) Values(#Raw_Feed)
Fetch Next
From #GetATM Into #ATM_ID,#Received_On
End
Now have to process each row in Temp Table using another Cursor
DECLARE #RawFeed VarChar(Max)
DECLARE Push_Data CURSOR FORWARD_ONLY LOCAL STATIC
FOR SELECT RawFeed
FROM #_tempRawFeed
OPEN Push_Data
FETCH NEXT FROM Push_Data INTO #RawFeed
WHILE ##FETCH_STATUS = 0
BEGIN
/*
What Should i write here to retrieve each row one at a time ??
One Row should get stored in Variable..in next iteration previous value should get deleted.
*/
FETCH NEXT FROM Push_Data INTO #RawFeed
END
CLOSE Push_Data
DEALLOCATE Push_Data
Drop Table #_tempRawFeed
What Should i write In BEGIN to retrieve each row one at a time ??
One Row should get stored in Variable..in next iteration previous value should get deleted.
Regarding your last question, if what you are really intending to do within your last cursor is to concatenate RawFeed column values into one variable, you don't need cursors at all. You can use the following (adapted from your SQL Fiddle code):
CREATE TABLE #_tempRawFeed
(
Code Int IDENTITY
RawFeed VarChar(MAX)
)
INSERT INTO #_tempRawFeed(RawFeed) VALUES('SAGAR')
INSERT INTO #_tempRawFeed(RawFeed) VALUES('Nikhil')
INSERT INTO #_tempRawFeed(RawFeed) VALUES('Deepali')
DECLARE #RawFeed VarChar(MAX)
SELECT #RawFeed = COALESCE(#RawFeed + ', ', '') + ISNULL(RawFeed, '')
FROM #_tempRawFeed
SELECT #RawFeed
DROP TABLE #_tempRawFeed
More on concatenating different row values into a single string here: Concatenate many rows into a single text string?
I am pretty sure that you can avoid using the first cursor as well. Please, avoid using cursors, since the really hurt performance. The same result can be achieved using set based operations.

Scope of table variable within SQL cursor

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.