I have the following script which works fine:
DECLARE db_cursor1 CURSOR LOCAL FOR
SELECT ID, Name table_1
OPEN db_cursor1
FETCH NEXT FROM db_cursor1 INTO #ID, #Name
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRANSACTION
BEGIN TRY
<insert into table values>
COMMIT TRANSACTION
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
ROLLBACK TRANSACTION
END CATCH
FETCH NEXT FROM db_cursor1 INTO #ID, #Name
END
CLOSE db_cursor1
DEALLOCATE db_cursor1
The above script works fine in that it rolls back what is in the current iteration of db_cursor1 and then proceeds to the next iteration in case there is an error.
The problem arises when I have a nested cursor. It rolls back what is in the current iteration but DOES NOT proceed to the next iteration of cursor1.
DECLARE db_cursor1 CURSOR LOCAL FOR
SELECT ID, Name table_1
OPEN db_cursor1
FETCH NEXT FROM db_cursor1 INTO #ID, #Name
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRANSACTION
BEGIN TRY
<insert into table values>
--- inner cursor
DECLARE db_cursor2 CURSOR LOCAL FOR
SELECT ID, Name table_2
OPEN db_cursor2
FETCH NEXT FROM db_cursor2 INTO #ID, #Name
WHILE ##FETCH_STATUS = 0
BEGIN
<insert into table values>
FETCH NEXT FROM db_cursor2 INTO #ID, #Name
END
CLOSE db_cursor2
DEALLOCATE db_cursor2
COMMIT TRANSACTION
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
ROLLBACK TRANSACTION
END CATCH
FETCH NEXT FROM db_cursor1 INTO #ID, #Name
END
CLOSE db_cursor1
DEALLOCATE db_cursor1
I was able to create it on my end with basic values, please see SCRIPT #1 - ERROR if you'd like to test it. The issue that you're running into is that when you have an error in db_cursor2, you exit the loop without closing or deallocating the cursor. Then when the code goes to next iteration, it fails with this error A cursor with the name 'db_cursor2' already exists. Please see SCRIPT #2 - SUCCESS for correct results. To give it more color, you needed to add CLOSE db_cursor2; DEALLOCATE db_cursoe2; in your BEGIN CATCH.
SETUP, Designed for SQL Server 2016+
DROP TABLE IF EXISTS #table_1, #table_2
CREATE TABLE #table_1
(
[ID] INT,
[Name] VARCHAR(5)
);
CREATE TABLE #table_2
(
[ID] INT,
[NAME] VARCHAR(5)
);
INSERT INTO #table_1 SELECT 1, 'j';
INSERT INTO #table_1 SELECT 2, 'j';
INSERT INTO #table_2 SELECT 1, 'j';
INSERT INTO #table_2 SELECT 2, 'j';
SCRIPT #1 - ERROR
DECLARE #ID INT;
DECLARE #name VARCHAR(5);
DECLARE db_cursor1 CURSOR LOCAL FOR SELECT [ID], [Name] FROM #table_1;
OPEN db_cursor1;
FETCH NEXT FROM db_cursor1
INTO #ID, #name;
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRANSACTION;
BEGIN TRY
PRINT('trying 1')
--- inner cursor
DECLARE db_cursor2 CURSOR LOCAL FOR SELECT [ID], [Name] FROM #table_2;
OPEN db_cursor2;
FETCH NEXT FROM db_cursor2
INTO #ID, #name;
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT('trying 2')
SELECT 1/0
FETCH NEXT FROM db_cursor2
INTO #ID, #name;
END;
CLOSE db_cursor2;
DEALLOCATE db_cursor2;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
ROLLBACK TRANSACTION;
END CATCH;
FETCH NEXT FROM db_cursor1
INTO #ID, #name;
END;
CLOSE db_cursor1;
DEALLOCATE db_cursor1;
SCRIPT #2 - SUCCESS
DECLARE #ID INT;
DECLARE #name VARCHAR(5);
DECLARE db_cursor1 CURSOR LOCAL FOR SELECT [ID], [Name] FROM #table_1;
OPEN db_cursor1;
FETCH NEXT FROM db_cursor1
INTO #ID, #name;
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRANSACTION;
BEGIN TRY
PRINT('trying 1')
--- inner cursor
DECLARE db_cursor2 CURSOR LOCAL FOR SELECT [ID], [Name] FROM #table_2;
OPEN db_cursor2;
FETCH NEXT FROM db_cursor2
INTO #ID, #name;
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT('trying 2')
SELECT 1/0
FETCH NEXT FROM db_cursor2
INTO #ID, #name;
END;
CLOSE db_cursor2;
DEALLOCATE db_cursor2;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
-- was missing in above script
CLOSE db_cursor2
DEALLOCATE db_cursor2
ROLLBACK TRANSACTION;
END CATCH;
FETCH NEXT FROM db_cursor1
INTO #ID, #name;
END;
CLOSE db_cursor1;
DEALLOCATE db_cursor1;
Because you are using a TRY-CATCH, if there is an error within the TRY, code execution will begin in the CATCH.
Inside your catch you would have to handle the error, and depending on the error, close and deallocate db_cursor2. Or if the error is benign, perhaps return execution back to the TRY with a GOTO. GOTO statements cannot be used to enter a TRY or CATCH block. GOTO statements can be used to jump to a label inside the same TRY or CATCH block or to leave a TRY or CATCH block.
As #paul-wehland stated, it's because your Try-Catch isn't disposing the nested cursor. As such, your next iteration is going to initialize a cursor by name that already exists. I've provided an example of code that will run your basic scenario with an intended failure condition at iteration 11 of each cursor.
In the example, I've commented out the part of the code that addresses the issue. Where you choose to place that block is totally up to you, but it would make sense to check either before the nested cursor declaration, or inside the Catch block.
declare
#id tinyint,
#parent_id tinyint,
#name varchar(255),
#parent_name varchar(255);
declare
#table
table
(
id tinyint not null primary key,
[name] varchar(255) not null
);
declare
#target
table
(
parent_id tinyint not null,
child_id tinyint not null,
parent_name varchar(10) not null,
child_name varchar(10) not null,
primary key(parent_id, child_id)
);
with cteNumber
as (
select top 11
[id] = row_number() over (order by [object_id])
from
sys.objects
)
insert into
#table
select
id,
[name] = replicate('a', id)
from
cteNumber;
declare
db_cursor1
cursor
local keyset read_only forward_only
for
select
0,
id,
'Initial',
[name]
from
#table;
open
db_cursor1;
fetch
next
from
db_cursor1
into
#id,
#parent_id,
#name,
#parent_name;
while ##FETCH_STATUS = 0
begin
begin transaction;
begin try
insert into #target
(parent_id, child_id, parent_name, [child_name])
values
(#parent_id, #id, #parent_name, #name);
--- inner cursor
/*
if CURSOR_STATUS('local', 'db_cursor2') = 1
begin
close
db_cursor2;
deallocate
db_cursor2;
end;
-- */
declare
db_cursor2
cursor
local keyset read_only forward_only
for
select
id,
[name]
from
#table;
open
db_cursor2;
fetch
next
from
db_cursor2
into
#id,
#name;
while ##FETCH_STATUS = 0
begin
insert into #target
(parent_id, child_id, parent_name, [child_name])
values
(#parent_id, #id, #parent_name, #name);
fetch
next
from
db_cursor2
into
#id,
#name;
end;
close
db_cursor2;
deallocate
db_cursor2;
commit transaction
end try
begin catch
print ERROR_MESSAGE();
rollback transaction;
end catch;
fetch
next
from
db_cursor1
into
#id,
#parent_id,
#name,
#parent_name;
end;
close
db_cursor1;
deallocate
db_cursor1;
select
[Last #id] = #id,
[Last #name] = #name,
[Last #parent_id] = #parent_id,
[Last #parent_name] = #parent_name;
select
*
from
#table;
select
*
from
#target;
EDITED
You could also use the creation of a Cursor Variable, and assign the nested cursor declaration to it, which would negate the problem of dealing with the duplicate names. See below:
declare
#id tinyint,
#parent_id tinyint,
#name varchar(255),
#parent_name varchar(255);
declare
#table
table
(
id tinyint not null primary key,
[name] varchar(255) not null
);
declare
#target
table
(
parent_id tinyint not null,
child_id tinyint not null,
parent_name varchar(10) not null,
child_name varchar(10) not null,
primary key(parent_id, child_id)
);
with cteNumber
as (
select top 11
[id] = row_number() over (order by [object_id])
from
sys.objects
)
insert into
#table
select
id,
[name] = replicate('a', id)
from
cteNumber;
declare
#db_cursor2 cursor;
declare
db_cursor1
cursor
local keyset read_only forward_only
for
select
0,
id,
'Initial',
[name]
from
#table;
open
db_cursor1;
fetch
next
from
db_cursor1
into
#id,
#parent_id,
#name,
#parent_name;
while ##FETCH_STATUS = 0
begin
begin transaction;
begin try
insert into #target
(parent_id, child_id, parent_name, [child_name])
values
(#parent_id, #id, #parent_name, #name);
--- inner cursor
set #db_cursor2 = cursor
local keyset read_only forward_only
for
select
id,
[name]
from
#table;
open
#db_cursor2;
fetch
next
from
#db_cursor2
into
#id,
#name;
while ##FETCH_STATUS = 0
begin
insert into #target
(parent_id, child_id, parent_name, [child_name])
values
(#parent_id, #id, #parent_name, #name);
fetch
next
from
#db_cursor2
into
#id,
#name;
end;
close
#db_cursor2;
deallocate
#db_cursor2;
commit transaction
end try
begin catch
print ERROR_MESSAGE();
rollback transaction;
end catch;
fetch
next
from
db_cursor1
into
#id,
#parent_id,
#name,
#parent_name;
end;
close
db_cursor1;
deallocate
db_cursor1;
select
[Last #id] = #id,
[Last #name] = #name,
[Last #parent_id] = #parent_id,
[Last #parent_name] = #parent_name;
select
*
from
#table;
select
*
from
#target;
Related
I'm not able to next cursors and and have the variables set from db_cursor1 used in the where clause of db_cursor2. By the time db_cursor2 declared it only works with the settings for #curLocation and #curTimeBandName at the time of declaration. I'm hoping "FETCH FIRST FROM db_cursor1 INTO #curLocation, #curTimeBandName" will set these variables to new values and I can do a new fetch on db_cursor2 with these new critera, but it doesn't work. I hate to move the declaration and deallocation for db_cursor2 inside the loop. It there a better way to next these cursors where the variables set in #1 can be used as criteria in #2.
DECLARE
#Id INT,
#prevId INT = 0,
#prevEndDate DATETIME,
#curStartDate DATETIME,
#curEndDate DATETIME,
#curLocation NVARCHAR(100) = 'PHX',
#curTimeBandName NVARCHAR(50) = 'Long3'
SELECT #PrevEndDate = ISNULL(#startDate,'1900/01/01');
DECLARE db_cursor1 CURSOR
SCROLL
FOR
SELECT DISTINCT [Location], TimeBandName
FROM #Temp
ORDER BY Location, TimeBandName;
-- SELECT #curLocation = 'PHX', #curTimeBandName = 'Long3';
DECLARE db_cursor2 CURSOR
SCROLL
FOR
SELECT Id, StartDate, EndDate
FROM #Temp
WHERE [Location] = #curLocation
AND TimeBandName = #curTimeBandName
ORDER BY Location, TimeBandName, StartDate;
OPEN db_cursor1
OPEN db_cursor2
FETCH FIRST FROM db_cursor1 INTO #curLocation, #curTimeBandName
WHILE (##FETCH_STATUS = 0)
BEGIN
FETCH NEXT FROM db_cursor2 INTO #Id, #curStartDate, #curEndDate
WHILE (##FETCH_STATUS = 0)
BEGIN
IF (#prevEndDate + 1 < #curStartDate)
BEGIN
UPDATE t
SET t.BlackoutStartDate = #PrevEndDate + 1,
t.BlackoutEndDate = #CurStartDate -1
FROM #Temp t
WHERE t.Id = #Id
END
SELECT #prevId = #id, #prevEndDate = #curEndDate
FETCH NEXT FROM db_cursor2 INTO #Id, #curStartDate, #curEndDate
END
FETCH NEXT FROM db_cursor1 INTO #curLocation, #curTimeBandName
END
CLOSE db_cursor2;
DEALLOCATE db_cursor2;
CLOSE db_cursor1;
DEALLOCATE db_cursor1;
I found a better way. I dumped the second cursor all together. Instead I added a ProcessStatus field to the temp table to track the next unfinished record and added the below code to track witch of the next records to work on.
WHILE ...
BEGIN
SELECT TOP 1
#Id = Id,
#curStartDate = StartDate,
#curEndDate = #EndDate
FROM #Temp
WHERE [Location] = #curLocation
AND TimeBandName = #curTimeBandName
AND ProcessStatus = 0
ORDER BY Location, TimeBandName, StartDate;
.... do work ...
UPDATE t
SET ProcessStatus = 1
FROM #Temp t
WHERE Id = #Id;
END
I have a stored procedure. There is 3 biggest record date in order table. I want to read the IDs one by one and send a stored procedure, but it gives an error.
BEGIN TRAN
exec trn_siparis_insert 'Database',348
DECLARE #ID uniqueidentifier
DECLARE SIPARIS CURSOR FOR
Select id from TABLE.dbo.siparis where kayit_tarihi = (SELECT MAX(kayit_tarihi) FROM TABLE.dbo.siparis );
OPEN SIPARIS
FETCH NEXT FROM SIPARIS INTO #ID
WHILE ##FETCH_STATUS =0
BEGIN
BEGIN TRAN
exec trn_boyutlu_siparis_olustur #siparis_id=#ID, #TargetDb='Database'
FETCH NEXT FROM SIPARIS INTO #ID
END
CLOSE SIPARIS
DEALLOCATE SIPARIS
COMMIT TRAN
BEGIN TRAN should be moved to just before the WHILE:
DECLARE #ID uniqueidentifier
DECLARE SIPARIS CURSOR FOR
Select id from TABLE.dbo.siparis where kayit_tarihi = (SELECT MAX(kayit_tarihi) FROM TABLE.dbo.siparis );
OPEN SIPARIS
FETCH NEXT FROM SIPARIS INTO #ID
BEGIN TRAN
WHILE ##FETCH_STATUS =0
BEGIN
exec trn_boyutlu_siparis_olustur #siparis_id=#ID, #TargetDb='Database'
FETCH NEXT FROM SIPARIS INTO #ID
END
COMMIT TRAN
CLOSE SIPARIS
DEALLOCATE SIPARIS
I am looking for a way to exit an T-SQL script when #Value is null. This is what I have so far but it does not work as expected:
SELECT
#Value,
CASE
WHEN #Value IS NULL
RAISERROR('EXIT', 16, 1)
FROM
table
WHERE
name LIKE 'test'
Perhaps this will work for you:
DECLARE #Value INT = 1
IF( #Value IS NULL)
BEGIN
RAISERROR('Exit',16,1)
END
ELSE
BEGIN
SELECT #Value
END
IF #Value IS NULL RAISERROR('EXIT', 16,1);
Using a cursor and a temp table you can get the output you want. don't know if that's the goal,
USE AdventureWorksLT2012
DECLARE #CustomerID AS INT
DECLARE #CompanyName AS VARCHAR(MAX)
DECLARE #EmailAddress AS VARCHAR(MAX)
CREATE TABLE #output (CustomerID INT,CompanyName VARCHAR(MAX),EmailAddress VARCHAR(MAX))
DECLARE testCursor CURSOR
FOR
SELECT TOP (100)
CustomerID
,CompanyName
,EmailAddress
FROM SalesLT.Customer
ORDER BY customerID DESC;
OPEN testCursor;
FETCH NEXT FROM testCursor
INTO #CustomerID, #CompanyName, #emailAddress;
if #EmailAddress is not null
BEGIN
INSERT INTO #output values( #CustomerID, #CompanyName, #emailAddress);
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM testCursor
INTO #CustomerID, #CompanyName, #emailAddress;
if #EmailAddress is null
BEGIN
RAISERROR('Exit',16,1);
BREAK;
end
INSERT INTO #output values( #CustomerID, #CompanyName, #emailAddress);
END;
END
CLOSE testCursor;
DEALLOCATE testCursor;
SELECT * FROM #output;
DROP TABLE #output
I want to use cursor to delete record from table. How can I do it?
I use MSSQL 2008 Express this code does not delete anything from #temp. I also tried where current of cursor_name did not work.
Here is my sample code:
use AdventureWorks
drop table #temp
select * into #temp from HumanResources.Employee;
declare #eid as int;
declare #nid as varchar(15);
DECLARE Employee_Cursor CURSOR FOR
SELECT A.EmployeeID, A.NationalIDNumber FROM #temp AS A
OPEN Employee_Cursor;
FETCH NEXT FROM Employee_Cursor INTO #eid , #nid ;
WHILE ##FETCH_STATUS = 0
BEGIN
IF (#eid > 10)
BEGIN
delete from #temp where #temp.EmployeeID = #eid;
END
FETCH NEXT FROM Employee_Cursor;
END;
CLOSE Employee_Cursor;
DEALLOCATE Employee_Cursor;
GO
select * from #temp
thanks in advance
use AdventureWorks
select * into #temp from HumanResources.Employee;
declare #eid as int;
declare #nid as varchar(15);
DECLARE Employee_Cursor CURSOR FOR
SELECT A.EmployeeID, A.NationalIDNumber FROM #temp AS A
OPEN Employee_Cursor;
FETCH NEXT FROM Employee_Cursor INTO #eid , #nid ;
WHILE ##FETCH_STATUS = 0
BEGIN
IF (#eid > 10)
BEGIN
delete from #temp where current of Employee_Cursor
END
FETCH NEXT FROM Employee_Cursor INTO #eid , #nid ;
END;
CLOSE Employee_Cursor;
DEALLOCATE Employee_Cursor;
select * from #temp
drop table #temp
this works for me
There is a much simpler answer - use this command:
delete from HumanResources.Employee where current of Employee_Cursor
It's called 'Positioned delete' and described at MSDN.
Could you please try in below ways, thanks for your time.
You have fetched data from cursor but didn't push into your variables missed in WHILE loop, please have a look on below code, thanks.
drop table #temp
select * into #temp from Employee;
declare #eid as int;
declare #nid as varchar(15);
DECLARE Employee_Cursor CURSOR FOR
SELECT A.EmployeeID, A.NationalIDNumber FROM #temp AS A
OPEN Employee_Cursor;
FETCH NEXT FROM Employee_Cursor INTO #eid , #nid ;
WHILE ##FETCH_STATUS = 0
BEGIN
IF (#eid > 10)
BEGIN
delete from #temp where #temp.EmployeeID = #eid;
END
FETCH NEXT FROM Employee_Cursor INTO #eid , #nid ;
END;
CLOSE Employee_Cursor;
DEALLOCATE Employee_Cursor;
GO
select * from #temp
Update:
I've changes in 2nd FETCH statement, just have added below highlighted part, thanks
FETCH NEXT FROM Employee_Cursor INTO #eid , #nid ;
We have triggers on a table called OSPP to save specific data to a table for later use.
I get the following error in SAP when adding more than one line at a time to the table.
Invalid Cursor State
We have SQL Server 2005 SP3 (but I tried it on a clean 2005 install, on SP1 and SP2)
The one trigger :
CREATE TRIGGER [dbo].[tr_OSPP_Insert]
ON [dbo].[OSPP]
FOR INSERT
AS
BEGIN
Declare #ItemCode varchar(255)
Declare #CardCode varchar(255)
Declare #Price decimal(18,2)
Declare #ListNum bigint
Declare #ID bigint
Declare #Remote char(1)
DECLARE db_cursor CURSOR FOR
SELECT ItemCode, CardCode, Price, ListNum
FROM INSERTED
OPEN db_cursor
FETCH NEXT
FROM db_cursor INTO #ItemCode, #CardCode, #Price, #ListNum
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #Remote = isnull(U_Remote, 'N') FROM OITM WHERE ItemCode = #ItemCode
IF ltrim(rtrim(upper(#Remote))) = 'Y'
BEGIN
SELECT #ID = U_ID FROM [dbo].[#BDS_MAINTENANCE]
UPDATE [dbo].[#BDS_MAINTENANCE] set U_ID = U_ID + 1
INSERT INTO [dbo].[#BDS_REMOTESPECIALPRICELIST]
(
Code,
[Name],
U_ID,
U_ItemCode,
U_CardCode,
U_Price,
U_ListNum,
U_TransactionType,
U_Uploaded
) VALUES (
#ID,
'_' + cast(#ID as VARCHAR(50)),
#ID,
#ItemCode,
#CardCode,
#Price,
#ListNum,
1,
0
)
FETCH NEXT
FROM db_cursor INTO #ItemCode, #CardCode, #Price, #ListNum
END
CLOSE db_cursor
DEALLOCATE db_cursor
END
END
We also tried :
CREATE TRIGGER [dbo].[tr_OSPP_Insert]
ON [dbo].[OSPP]
FOR INSERT
AS
BEGIN
SELECT * INTO [#TEMPTABLE222] FROM INSERTED
END
But still get the same error.
Do you guys have any idea what is wrong?
Thanks in advance!
I count three Begins, and three Ends. But it's the second pair that represent the cursor loop - so I'd move your Close/Deallocate to be after the second End, rather than before. E.g:
FETCH NEXT
FROM db_cursor INTO #ItemCode, #CardCode, #Price, #ListNum
END
CLOSE db_cursor
DEALLOCATE db_cursor
END
Probably needs to be:
END
FETCH NEXT
FROM db_cursor INTO #ItemCode, #CardCode, #Price, #ListNum
END
CLOSE db_cursor
DEALLOCATE db_cursor
(I've also moved the fetch next one level out, since otherwise you only move the cursor forwards inside your IF condition)
And one style comment (can't resist). It's generally considered good practice to SET NOCOUNT ON within the body of a trigger, to avoid sending lots of extra n rows affected messages.