web-dev-qa-db-fra.com

Transaction dans une procédure stockée

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
12
Charlie K

Vous aimez avoir besoin d'encapsuler ce code dans CREATE PROCEDURE ... syntaxe, et supprimez les instructions GO après BEGIN TRANSACTION et 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...CATCH bloc d'instructions pour permettre d'effectuer un ROLLBACK TRANSACTION instruction 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. Mauvaises habitudes à éviter

15
Max Vernon

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 dans le code C # ainsi que dans la procédure stockée

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

  1. L'utilisation de RAISERROR dans le bloc CATCH. Cela fait remonter l'erreur jusqu'au niveau de l'appel (que ce soit dans la couche DB ou app) 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 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 TRANSACTION vous revenez seulement à 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 sur SAVE TRANSACTION, alors 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 TRANSACTION est 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 vers la plus récente SAVE TRANSACTION à l'aide de 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>
    
8
Solomon Rutzky