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;