Transaction dans une procédure stockée

12

J'ai besoin d'effectuer une MISE À JOUR et une INSÉRER en une seule transaction. Ce code fonctionne bien seul, mais j'aimerais pouvoir l'appeler facilement et transmettre les paramètres requis. Lorsque j'essaie d'imbriquer cette transaction dans une procédure stockée, je rencontre de nombreuses erreurs de syntaxe.

Comment puis-je encapsuler le code suivant afin qu'il puisse être facilement appelé?

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
la source
1
Il serait probablement utile que vous ajoutiez des détails dans votre question sur ce que sont exactement les "erreurs" (utilisez le lien d' édition sous votre question). Veuillez également faire le tour . Merci!
Max Vernon

Réponses:

15

Vous aimez avoir besoin d'encapsuler ce code dans la CREATE PROCEDURE ...syntaxe et de supprimer les GOinstructions après BEGIN TRANSACTIONet avant 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

Notez également que j'ai ajouté un TRY...CATCHbloc d'instructions pour permettre l'exécution d'une ROLLBACK TRANSACTIONinstruction en cas d'erreur. Vous avez probablement besoin d'une meilleure gestion des erreurs que cela, mais sans connaître vos besoins, c'est difficile au mieux.

Quelques bonnes lectures:

  1. Spécifiez toujours le schéma

  2. Meilleures pratiques pour les procédures stockées

  3. De mauvaises habitudes à éviter

Max Vernon
la source
1
Vous souhaitez toujours avoir une transaction enregistrée. Si vous placez une transaction dans un SP et que le SP est enveloppé dans une autre transaction, les choses échoueront. sqlstudies.com/2014/01/06/…
Kenneth Fisher
merci de m'avoir indiqué ça. Je sais qu'il n'y a pas de transactions imbriquées dans SQL Server, mais je n'étais pas au courant des SAVE TRANSimplications de la commande.
Max Vernon
8

Si vous souhaitez gérer correctement les procédures stockées imbriquées qui peuvent gérer les transactions (qu'elles soient démarrées à partir de T-SQL ou du code d'application), vous devez suivre le modèle que j'ai décrit dans la réponse suivante:

Sommes-nous tenus de gérer la transaction en code C # ainsi qu'en procédure stockée

Vous remarquerez deux différences par rapport à ce que vous tentez ici:

  1. L'utilisation de l' RAISERRORintérieur du CATCHbloc. Cela fait remonter l'erreur jusqu'au niveau de l'appelant (que ce soit dans la couche de base de données ou d'application) afin qu'une décision puisse être prise concernant le fait qu'une erreur s'est produite.

  2. Non SAVE TRANSACTION. Je n'ai jamais trouvé de cas pour l'utiliser. Je sais que certaines personnes le préfèrent, mais dans tout ce que j'ai jamais fait à n'importe quel endroit où j'ai travaillé, la notion d'une erreur se produisant dans l'un des niveaux imbriqués impliquait que tout travail déjà effectué était invalide. En utilisant, SAVE TRANSACTIONvous revenez à l'état juste avant l'appel de cette procédure stockée, laissant le processus existant comme autrement valide.

    Si vous souhaitez plus de détails SAVE TRANSACTION, veuillez consulter les informations de cette réponse:

    Comment restaurer lorsque 3 procédures stockées sont démarrées à partir d'une procédure stockée

    Un autre problème avec SAVE TRANSACTIONest une nuance de son comportement, comme indiqué dans la page MSDN pour SAVE TRANSACTION (soulignement ajouté):

    Les noms de point de sauvegarde en double sont autorisés dans une transaction, mais une instruction ROLLBACK TRANSACTION qui spécifie le nom du point de sauvegarde ne ramènera la transaction que dans le SAVE TRANSACTION le plus récent en utilisant ce nom.

    Cela signifie que vous devez être très prudent pour donner à chaque point de sauvegarde de chaque procédure stockée un nom unique sur tous les points de sauvegarde de toutes les procédures stockées. Les exemples suivants illustrent ce point.

    Ce premier exemple montre ce qui se passe lorsque vous réutilisez le nom du point d'enregistrement; seul le point de sauvegarde de niveau le plus bas est annulé.

    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
    

    Ce deuxième exemple montre ce qui se passe lorsque vous utilisez des noms de point de sauvegarde uniques; le point de sauvegarde du niveau souhaité est annulé.

    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
la source
C'est pourquoi j'utilise @@ NESTLEVEL pour fabriquer mon nom de point de sauvegarde SAVE TRANSACTION.
Vincent Vancalbergh