Transakcja w procedurze przechowywanej

12

Muszę wykonać AKTUALIZACJĘ i WSTAW w jednej transakcji. Ten kod działa dobrze sam, ale chciałbym móc go łatwo wywoływać i przekazywać wymagane parametry. Kiedy próbuję zagnieździć tę transakcję w procedurze składowanej, napotykam wiele błędów składniowych.

Jak mogę obudować poniższy kod, aby można go było łatwo wywołać?

BEGIN TRANSACTION AssignUserToTicket
GO

DECLARE @updateAuthor varchar(100)
DECLARE @assignedUser varchar(100)
DECLARE @ticketID bigint

SET @updateAuthor = 'user1'
SET @assignedUser = 'user2'
SET @ticketID = 123456

    UPDATE tblTicket SET ticketAssignedUserSamAccountName = @assignedUser WHERE (ticketID = @ticketID);
    INSERT INTO [dbo].[tblTicketUpdate]
           ([ticketID]
           ,[updateDetail]
           ,[updateDateTime]
           ,[userSamAccountName]
           ,[activity])
     VALUES
           (@ticketID,
           'Assigned ticket to ' + @assignedUser,
           GetDate(),
           @updateAuthor,
           'Assign');
GO
COMMIT TRANSACTION AssignUserToTicket
Charlie K.
źródło
1
Byłoby to pomocne, gdybyś dodał szczegółowe informacje na temat tego, czym dokładnie są „błędy” (użyj linku edycji pod pytaniem). Proszę również o wycieczkę . Dzięki!
Max Vernon

Odpowiedzi:

15

Chcesz owinąć ten kod w CREATE PROCEDURE ...składnię i usunąć GOinstrukcje BEGIN TRANSACTIONprzed i po COMMIT TRANSACTION.

GO
CREATE PROCEDURE dbo.AssignUserToTicket
(
     @updateAuthor varchar(100)
    , @assignedUser varchar(100)
    , @ticketID bigint
)
AS
BEGIN
    BEGIN TRANSACTION;
    SAVE TRANSACTION MySavePoint;
    SET @updateAuthor = 'user1';
    SET @assignedUser = 'user2';
    SET @ticketID = 123456;

    BEGIN TRY
        UPDATE dbo.tblTicket 
        SET ticketAssignedUserSamAccountName = @assignedUser 
        WHERE (ticketID = @ticketID);

        INSERT INTO [dbo].[tblTicketUpdate]
            (
            [ticketID]
            ,[updateDetail]
            ,[updateDateTime]
            ,[userSamAccountName]
            ,[activity]
            )
        VALUES (
            @ticketID
            , 'Assigned ticket to ' + @assignedUser
            , GetDate()
            , @updateAuthor
            , 'Assign'
            );
        COMMIT TRANSACTION 
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
        BEGIN
            ROLLBACK TRANSACTION MySavePoint; -- rollback to MySavePoint
        END
    END CATCH
END;
GO

Uwaga: dodałem TRY...CATCHblok instrukcji, aby umożliwić wykonanie ROLLBACK TRANSACTIONinstrukcji w przypadku wystąpienia błędu. Prawdopodobnie potrzebujesz lepszej obsługi błędów niż to, ale bez znajomości swoich wymagań jest to w najlepszym razie trudne.

Dobra lektura:

  1. Zawsze określaj schemat

  2. Procedury składowane Najlepsze praktyki

  3. Złe nawyki, których należy unikać

Max Vernon
źródło
1
Nadal chcesz mieć zapisaną transakcję. Jeśli umieścisz transakcję w SP, a SP zostanie opakowane w inną transakcję, rzeczy się nie powiodą. sqlstudies.com/2014/01/06/…
Kenneth Fisher
dzięki za wskazanie mi tego. Wiem, że w SQL Server nie ma zagnieżdżonych transakcji, ale nie byłem świadomy SAVE TRANSkonsekwencji polecenia.
Max Vernon
8

Jeśli chcesz poprawnie obsługiwać zagnieżdżone procedury składowane, które mogą obsługiwać transakcje (niezależnie od tego, czy zostały uruchomione z T-SQL lub kodu aplikacji), powinieneś postępować zgodnie z szablonem, który opisałem w następującej odpowiedzi:

Czy jesteśmy zobowiązani do obsługi transakcji w kodzie C #, a także w procedurze przechowywanej

Zauważysz tam dwie różnice w stosunku do tego, co tutaj próbujesz:

  1. Zastosowanie RAISERRORw CATCHbloku. Spowoduje to przeniesienie błędu do poziomu wywołania (zarówno w warstwie bazy danych, jak i aplikacji), dzięki czemu można podjąć decyzję dotyczącą wystąpienia błędu.

  2. Nie SAVE TRANSACTION. Nigdy nie znalazłem powodu do tego. Wiem, że niektórzy ludzie wolą to, ale we wszystkim, co kiedykolwiek zrobiłem w dowolnym miejscu, w którym pracowałem, pojęcie błędu występującego na dowolnym z zagnieżdżonych poziomów sugerowało, że wszystko, co zostało już wykonane, było nieprawidłowe. Korzystając z tej opcji SAVE TRANSACTION, powracasz do stanu tuż przed wywołaniem tej procedury składowanej, pozostawiając istniejący proces jako inny ważny.

    Jeśli chcesz uzyskać więcej informacji SAVE TRANSACTION, zapoznaj się z informacjami w tej odpowiedzi:

    Jak wycofać, gdy 3 procedury przechowywane są uruchamiane z jednej procedury przechowywanej

    Kolejny problem z SAVE TRANSACTION jest niuans jego zachowania, jak zauważono na stronie MSDN SAVE TRANSACTION (wyróżnienie dodane):

    Zduplikowane nazwy punktów zapisu są dozwolone w transakcji, ale instrukcja ROLLBACK TRANSACTION, która określa nazwę punktu zapisu, zwróci transakcję tylko do najnowszej ZAPISZ TRANSAKCJĘ przy użyciu tej nazwy.

    Oznacza to, że należy bardzo uważać, aby nadać każdemu Punktowi zapisu w każdej Procedurze składowanej unikalną nazwę we wszystkich Punktach zapisu we wszystkich Procedurach przechowywanych. Poniższe przykłady ilustrują ten punkt.

    Ten pierwszy przykład pokazuje, co się stanie, gdy użyjesz ponownie nazwy punktu zapisu; wycofany zostanie tylko punkt zapisu najniższego poziomu.

    IF (OBJECT_ID(N'tempdb..#SaveTranTestA') IS NOT NULL)
    BEGIN
        DROP TABLE #SaveTranTestA;
    END;
    CREATE TABLE #SaveTranTestA (SomeVal INT NOT NULL);
    
    BEGIN TRAN; -- start level 1
    SAVE TRANSACTION MySavePoint;
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 1
    
    INSERT INTO #SaveTranTestA (SomeVal) VALUES (100);
    
    BEGIN TRAN; -- start level 2
    SAVE TRANSACTION MySavePoint;
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 2
    
    INSERT INTO #SaveTranTestA (SomeVal) VALUES (200);
    
    COMMIT; -- exit level 2
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 1
    SELECT * FROM #SaveTranTestA;
    -- 100
    -- 200
    
    ROLLBACK TRANSACTION MySavePoint; -- error occurred; undo actions up to this point
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 1
    SELECT * FROM #SaveTranTestA;
    -- 100
    
    COMMIT; -- exit level 1
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 0
    SELECT * FROM #SaveTranTestA;
    -- 100

    Ten drugi przykład pokazuje, co dzieje się, gdy używasz unikatowych nazw punktów zapisu; Punkt zapisu żądanego poziomu jest wycofywany.

    IF (OBJECT_ID(N'tempdb..#SaveTranTestB') IS NOT NULL)
    BEGIN
        DROP TABLE #SaveTranTestB;
    END;
    CREATE TABLE #SaveTranTestB (SomeVal INT NOT NULL);
    
    BEGIN TRAN; -- start level 1
    SAVE TRANSACTION MySavePointUno;
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 1
    
    INSERT INTO #SaveTranTestB (SomeVal) VALUES (100);
    
    BEGIN TRAN; -- start level 2
    SAVE TRANSACTION MySavePointDos;
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 2
    
    INSERT INTO #SaveTranTestB (SomeVal) VALUES (200);
    
    COMMIT; -- exit level 2
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 1
    SELECT * FROM #SaveTranTestB;
    -- 100
    -- 200
    
    ROLLBACK TRANSACTION MySavePointUno; --error occurred; undo actions up to this point
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 1
    SELECT * FROM #SaveTranTestB;
    -- <no rows>
    
    COMMIT; -- exit level 1
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 0
    SELECT * FROM #SaveTranTestB;
    -- <no rows>
Solomon Rutzky
źródło
Dlatego używam @@ NESTLEVEL do tworzenia nazwy punktu zapisu SAVE TRANSACTION.
Vincent Vancalbergh