tsql вложенный курсор не выполняет откат - PullRequest
0 голосов
/ 14 сентября 2018

У меня есть следующий скрипт, который отлично работает:

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 

Вышеописанный скрипт отлично работает, поскольку он откатывает то, что находится в текущей итерации db_cursor1, а затем переходит к следующей итерации, если естьошибка.

Проблема возникает, когда у меня есть вложенный курсор.Он откатывает то, что находится в текущей итерации, но НЕ переходит к следующей итерации 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 

Ответы [ 3 ]

0 голосов
/ 21 сентября 2018

Как сказал @ paul-wehland, это потому, что ваш Try-Catch не использует вложенный курсор. Таким образом, ваша следующая итерация будет инициализировать курсор по имени, которое уже существует. Я предоставил пример кода, который будет запускать ваш базовый сценарий с предполагаемым условием сбоя на итерации 11 каждого курсора.

В этом примере я закомментировал ту часть кода, которая решает проблему. Где вы решите разместить этот блок, полностью зависит от вас, но имеет смысл проверить это либо до объявления вложенного курсора, либо внутри блока Catch.

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 Вы также можете использовать создание переменной курсора и назначить ей объявление вложенного курсора, что устранит проблему обращения с дублирующимися именами. Смотрите ниже:

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;
0 голосов
/ 21 сентября 2018

Я смог создать его самостоятельно, используя базовые значения, см. СЦЕНАРИЙ # 1 - ОШИБКА , если вы хотите проверить его.Проблема, с которой вы сталкиваетесь, заключается в том, что когда вы получаете ошибку в db_cursor2, вы выходите из цикла, не закрывая и не освобождая курсор.Затем, когда код переходит к следующей итерации, он завершается с ошибкой A cursor with the name 'db_cursor2' already exists. Пожалуйста, см. СЦЕНАРИЙ # 2 - УСПЕХ для правильных результатов.Чтобы придать ему больше цвета, вам нужно добавить CLOSE db_cursor2; DEALLOCATE db_cursoe2; в ваш BEGIN CATCH.

НАСТРОЙКА , Разработано для 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';

СКРИПТ № 1 - ОШИБКА

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;

СЦЕНАРИЙ № 2 - УСПЕХ

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;
0 голосов
/ 18 сентября 2018

Поскольку вы используете TRY-CATCH, если в TRY есть ошибка, выполнение кода начнется в CATCH.

Внутри вашего улова вам придется обработать ошибку, и в зависимости от ошибки закройте и освободите db_cursor2. Или, если ошибка доброкачественная, возможно, верните выполнение обратно в ПОПРОБОВАТЬ с GOTO. Операторы GOTO нельзя использовать для ввода блока TRY или CATCH. Операторы GOTO могут использоваться для перехода к метке внутри того же блока TRY или CATCH или для выхода из блока TRY или CATCH.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...