2017-10-04 13 views
1

J'essaie de supprimer une procédure stockée si elle existe, puis de la recréer, le tout à l'intérieur d'une transaction.SQL Server: impossible de supprimer et de créer un SP dans la transaction

BEGIN TRANSACTION 
BEGIN TRY 

IF OBJECT_ID(N'dbo.GET_DATA', N'P') IS NOT NULL 
BEGIN 
    DROP PROCEDURE [dbo].[GET_DATA] 
END 

CREATE PROCEDURE [dbo].[GET_DATA] 
    @date datetime2 
AS 
    SET NOCOUNT ON 
BEGIN 

    SELECT 
     dbo.Products.product_cod AS 'product_cod', 
     dbo.Product_Types.name AS 'product_type_name', 
     dbo.UM.name AS 'um_name', 
     dbo.Products.category_id AS 'category_id', 
     dbo.Bins_Products.bin_id AS 'product_bin_id' 

    FROM dbo.Products 
     LEFT JOIN dbo.Product_Types on Products.product_type_id = Product_Types.product_type_id 
     LEFT JOIN dbo.UM on Products.um_id = UM.um_id 
     LEFT JOIN Bins_Products ON Bins_Products.product_id = Products.product_id 
    WHERE 
     Products.update_date >= @date 
END 
    COMMIT TRANSACTION 
END TRY 
BEGIN CATCH 
    SELECT ERROR_MESSAGE() AS 'ErrorMessage' 
    ROLLBACK TRANSACTION 
END CATCH 

Quand je lance le script ci-dessus, je reçois les erreurs suivantes:

Msg 156, Level 15, State 1, Line 9 
Incorrect syntax near the keyword 'PROCEDURE'. 
Msg 137, Level 15, State 2, Line 31 
Must declare the scalar variable "@date". 

Et je squiggly lignes sur le SET et @date.

L'instruction IF et l'instruction create fonctionnent correctement toutes seules.

+0

Il vous manque une instruction GO avant la procédure de création et vous ne pouvez pas utiliser try/catch block. –

+0

'CREATE PROCEDURE' et' DROP PROCEDURE' ne peuvent pas être faits transactionnellement. Au lieu de cela, créez une procédure vide si elle n'existe pas encore ("CREATE PROCEDURE GET_DATA COMME BEGIN RETURN END"), puis "ALTER" inconditionnellement. –

+0

@ DeanSavović J'ai essayé d'utiliser GO mais cela ne fait que créer d'autres erreurs. – dmdany07

Répondre

2

Vous pouvez utiliser EXEC pour créer la procédure dans une transaction, mais c'est très pratique, car le corps doit être échappé. Une meilleure approche est d'assurer la procédure stockée existe toujours, puis effectuer une ALTER, qui n'a pas besoin d'une opération distincte:

IF OBJECT_ID('Foo', 'P') IS NULL 
    EXEC ('CREATE PROCEDURE Foo AS BEGIN RETURN END;'); 
GO 
ALTER PROCEDURE Foo(@Arg INT) AS BEGIN 
    ... 
END; 

Un autre avantage de cette approche (ou un inconvénient, selon votre processus de déploiement) est que cela laisse intacte toutes les autorisations existantes sur la procédure stockée, contrairement à l'abandon et la création.

+0

Oui, cela semble être ce que je cherchais, même s'il n'utilise pas de transaction. Merci – dmdany07

+0

@ dmdany07 - vous pouvez envelopper dans une transaction très bien. La difficulté est que a) vous ne pouvez pas utiliser TRY/CATCH (parce que ce sont * ceux * qui ne peuvent pas couvrir des lots, pas des transactions comme certaines réponses l'ont suggéré) et b) une fois que vous êtes dans un lot séparé, essayez pour savoir si vous devriez 'COMMIT' ou' ROLLBACK' est le bit difficile à automatiser. –

0

Une définition de procédure doit être dans son propre lot. Dans Management Studio vous mettiez deux lignes avec

go 

avant create procedure et après la end. Malheureusement, les transactions ne peuvent pas couvrir plusieurs lots.

Vous pouvez créer la procédure dans un appel exec? Comme:

exec ('create procedure dbo.MyProc as ...'); 
+0

"les transactions ne peuvent pas s'étendre sur plusieurs lots." - Je ne sais pas pourquoi tu penses ça mais c'est carrément faux. les lots et les transactions sont orthogonaux. –

0

utiliser ce code, ceci est mon exemple de travail.

Modifiez-le selon vos besoins.

USE [DatabaseName] 
GO 

/****** Object: StoredProcedure [dbo].[_AdvancePaymentDelete] Script Date: 10/04/2017 15:12:43 ******/ 
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[_AdvancePaymentDelete]') AND type in (N'P', N'PC')) 
DROP PROCEDURE [dbo].[_AdvancePaymentDelete] 
GO 

USE [DatabaseName] 
GO 

/****** Object: StoredProcedure [dbo].[_AdvancePaymentDelete] Script Date: 10/04/2017 15:12:43 ******/ 
SET ANSI_NULLS ON 
GO 

SET QUOTED_IDENTIFIER ON 
GO 

CREATE PROCEDURE [dbo].[_AdvancePaymentDelete] 
(
     @_ADVANCEPAYMENTID_PK uniqueidentifier, 
     @_COMPANYID_PK uniqueidentifier , 
     @_COMPANYDETID_PK uniqueidentifier , 
     @_USERID_PK uniqueidentifier 
     ) 
AS 
BEGIN 
     BEGIN TRANSACTION; 
     SAVE TRANSACTION MySavePoint; 
    DECLARE @ErrorMessage nvarchar(MAX) = 'OK'; 

     BEGIN TRY 
UPDATE [dbo].[_ADVANCEPAYMENT] SET _ISDELETED = N'2' 

WHERE _COMPANYID_PK = @_COMPANYID_PK AND _COMPANYDETID_PK = @_COMPANYDETID_PK AND _USERID_PK = @_USERID_PK AND _ADVANCEPAYMENTID_PK = @_ADVANCEPAYMENTID_PK AND _ISDELETED = N'1' 

UPDATE [dbo].[_ADVANCEPAYMENTDET] SET _ISDELETED = N'2' 

WHERE _COMPANYID_PK = @_COMPANYID_PK AND _COMPANYDETID_PK = @_COMPANYDETID_PK AND _USERID_PK = @_USERID_PK AND _ADVANCEPAYMENTID_PK = @_ADVANCEPAYMENTID_PK AND _ISDELETED = N'1' 


    END TRY 
    BEGIN CATCH 
    IF @@TRANCOUNT > 0 
     BEGIN 

    ROLLBACK TRANSACTION MySavePoint; -- rollback to MySavePoint 
    DECLARE @ErrorSeverity INT = ERROR_SEVERITY(); 
    DECLARE @ErrorState INT = ERROR_STATE(); 
    SET @ErrorMessage = 'Error No : ' + CAST (ERROR_NUMBER() AS nvarchar(MAX)) + CHAR(13) + 'Line No : ' + CAST (ERROR_LINE() AS nvarchar(MAX))+ CHAR(13) + 'Procedure Name : ' + QUOTENAME(OBJECT_SCHEMA_NAME(@@PROCID)) + '.' + QUOTENAME(OBJECT_NAME(@@PROCID)) + CHAR(13) + 'Error Message : ' + ERROR_MESSAGE(); 
    RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState); 
     END 
    END CATCH 

    COMMIT TRANSACTION 
END; 

SELECT @ErrorMessage 

GO 
0

Je ne sais pas ce que vous essayez de faire là-bas, mais je peux voir quelques choses évidentes mal avec ce code, il devrait ressembler à quelque chose comme ça ...

esprit que vous créez la procédure doit être la seule déclaration à l'intérieur d'un lot, ce qui signifie que vous ne pouvez pas laisser tomber et créer proc en une seule transaction.

IF OBJECT_ID(N'dbo.GET_DATA', N'P') IS NOT NULL 
BEGIN 
    DROP PROCEDURE [dbo].[GET_DATA] 
END 
GO 

CREATE PROCEDURE [dbo].[GET_DATA] 
    @date datetime2 
AS 
BEGIN      --<-- Proc body start 
    SET NOCOUNT ON; 

BEGIN TRY 
    BEGIN TRANSACTION; 

      SELECT 
       dbo.Products.product_cod AS 'product_cod', 
       dbo.Product_Types.name AS 'product_type_name', 
       dbo.UM.name AS 'um_name', 
       dbo.Products.category_id AS 'category_id', 
       dbo.Bins_Products.bin_id AS 'product_bin_id' 

      FROM dbo.Products 
       LEFT JOIN dbo.Product_Types on Products.product_type_id = Product_Types.product_type_id 
       LEFT JOIN dbo.UM on Products.um_id = UM.um_id 
       LEFT JOIN Bins_Products ON Bins_Products.product_id = Products.product_id 
      WHERE 
       Products.update_date >= @date; 
    COMMIT TRANSACTION; 
END TRY 

BEGIN CATCH 
     IF (@@TRANCOUNT > 0) -- Check for open transactions before you try to rollback 
     BEGIN 
      ROLLBACK TRANSACTION; 
     END 

    SELECT ERROR_MESSAGE() AS 'ErrorMessage' 

END CATCH 

END      --<-- Proc body End 
GO 
+0

Y a-t-il un moyen d'annuler l'instruction de procédure drop si la création échoue pour une raison quelconque? – dmdany07

2

Juste pour démontrer que cette peut être fait dans une transaction, voici un script de démonstration:

create procedure dbo.A 
as 
    select 1 as T 
go 
exec dbo.A 
go 
begin transaction 
go 
IF OBJECT_ID(N'dbo.A', N'P') IS NOT NULL 
BEGIN 
    EXEC('drop procedure dbo.A') 
END 
go 
create procedure dbo.A 
as 
    select penguin from sys.objects --This will fail 
go 
IF OBJECT_ID(N'dbo.A', N'P') IS NOT NULL 
BEGIN 
    commit 
END 
ELSE 
BEGIN 
    rollback transaction 
END 
go 
exec dbo.A 

Il produit une erreur lors de la tentative de créer une nouvelle procédure A et l'annulation revient à l'original version de A. Cela ne peut vraiment fonctionner (comme ici) où la création de la nouvelle version de A entraîne une erreur matérielle telle que nous pouvons le détecter par la suite et décider de rollback plutôt que commit. Cela étant dit, je continuerais à utiliser Jeroen's answer moi-même.

+0

Vous soulevez un autre excellent point, cependant - la résolution de nom différée signifie que 'CREATE PROCEDURE' échouera rarement, même si la procédure est absolument défectueuse. Une erreur de syntaxe dure ou un déclencheur DDL échoué sont sur les seules choses que la transaction se prémunirait contre. –

+0

Merci pour l'exemple, bon de savoir que cela peut être fait. Toujours semble un peu cumbersone. J'ai fini par aller avec l'exemple de Jereon. – dmdany07