Je crée un script qui sera exécuté sur un serveur MS SQL. Ce script exécutera plusieurs instructions et doit être transactionnel.Si l'une des instructions échoue, l'exécution globale est arrêtée et toutes les modifications sont annulées.
Je rencontre des problèmes pour créer ce modèle transactionnel lors de l'émission d'instructions ALTER TABLE pour ajouter des colonnes à une table, puis pour mettre à jour la colonne nouvellement ajoutée. Afin d'accéder immédiatement à la colonne nouvellement ajoutée, j'utilise une commande GO pour exécuter l'instruction ALTER TABLE, puis j'appelle mon instruction UPDATE. Le problème auquel je suis confronté est que je ne peux pas émettre de commande GO dans une instruction IF. L'instruction IF est importante dans mon modèle transactionnel. Ceci est un exemple de code du script que j'essaie d'exécuter. Notez également que l'exécution d'une commande GO annulera la variable @errorCode et devra être déclarée dans le code avant d'être utilisée (ce n'est pas dans le code ci-dessous).
BEGIN TRANSACTION
DECLARE @errorCode INT
SET @errorCode = @@ERROR
-- **********************************
-- * Settings
-- **********************************
IF @errorCode = 0
BEGIN
BEGIN TRY
ALTER TABLE Color ADD [CodeID] [uniqueidentifier] NOT NULL DEFAULT ('{00000000-0000-0000-0000-000000000000}')
GO
END TRY
BEGIN CATCH
SET @errorCode = @@ERROR
END CATCH
END
IF @errorCode = 0
BEGIN
BEGIN TRY
UPDATE Color
SET CodeID= 'B6D266DC-B305-4153-A7AB-9109962255FC'
WHERE [Name] = 'Red'
END TRY
BEGIN CATCH
SET @errorCode = @@ERROR
END CATCH
END
-- **********************************
-- * Check @errorCode to issue a COMMIT or a ROLLBACK
-- **********************************
IF @errorCode = 0
BEGIN
COMMIT
PRINT 'Success'
END
ELSE
BEGIN
ROLLBACK
PRINT 'Failure'
END
Donc, ce que je voudrais savoir, c'est comment contourner ce problème, en émettant des instructions ALTER TABLE pour ajouter une colonne, puis en mettant à jour cette colonne, le tout dans un script s'exécutant en tant qu'unité transactionnelle.
GO n'est pas une commande T-SQL. Est un délimiteur par lots. L'outil client (SSM, sqlcmd, osql, etc.) l'utilise pour couper efficacement le fichier à chaque GO et envoyer au serveur les lots individuels. Donc, évidemment, vous ne pouvez pas utiliser GO dans IF, ni vous attendre à ce que les variables s'étendent sur plusieurs lots.
En outre, vous ne pouvez pas intercepter des exceptions sans vérifier la XACT_STATE()
pour vous assurer que la transaction n'est pas condamnée.
L'utilisation de GUID pour les ID est toujours au moins suspecte.
Utiliser des contraintes NOT NULL et fournir un "guid" par défaut comme '{00000000-0000-0000-0000-000000000000}'
ne peut pas non plus être correct.
Mis à jour:
XACT_ABORT
pour forcer l'erreur à interrompre le lot. Ceci est fréquemment utilisé dans les scripts de maintenance (modifications de schéma). Les procédures stockées et les scripts de logique d'application utilisent généralement des blocs TRY-CATCH à la place, mais avec le soin approprié: Gestion des exceptions et transactions imbriquées .exemple de script:
:on error exit
set xact_abort on;
go
begin transaction;
go
if columnproperty(object_id('Code'), 'ColorId', 'AllowsNull') is null
begin
alter table Code add ColorId uniqueidentifier null;
end
go
update Code
set ColorId = '...'
where ...
go
commit;
go
Seul un script réussi atteindra le COMMIT
. Toute erreur annulera le script et la restauration.
J'ai utilisé COLUMNPROPERTY
pour vérifier l'existence de la colonne, vous pouvez utiliser n'importe quelle méthode à votre place (par exemple, recherche sys.columns
).
Orthogonale aux commentaires de Remus, ce que vous pouvez faire est d'exécuter la mise à jour dans un sp_executesql.
ALTER TABLE [Table] ADD [Xyz] NVARCHAR(256);
DECLARE @sql NVARCHAR(2048) = 'UPDATE [Table] SET [Xyz] = ''abcd'';';
EXEC sys.sp_executesql @query = @sql;
Nous avons dû le faire lors de la création de scripts de mise à niveau. Habituellement, nous utilisons simplement GO, mais il a été nécessaire de faire les choses de manière conditionnelle.
Je suis presque d'accord avec Remus mais vous pouvez le faire avec SET XACT_ABORT ON et XACT_STATE
Fondamentalement
Des outils comme Red Gate SQL Compare utilisent cette technique
Quelque chose comme:
SET XACT_ABORT ON
GO
BEGIN TRANSACTION
GO
IF COLUMNPROPERTY(OBJECT_ID('Color'), 'CodeID', ColumnId) IS NULL
ALTER TABLE Color ADD CodeID [uniqueidentifier] NULL
GO
IF XACT_STATE() = 1
UPDATE Color
SET CodeID= 'B6D266DC-B305-4153-A7AB-9109962255FC'
WHERE [Name] = 'Red'
GO
IF XACT_STATE() = 1
COMMIT TRAN
--else would be rolled back
J'ai également supprimé la valeur par défaut. Aucune valeur = NULL pour GUID valeurs. Il est censé être unique: n'essayez pas de définir chaque ligne sur tous les zéros car cela se terminera en larmes ...
L'avez-vous essayé sans le GO?
Normalement, vous ne devez pas mélanger les changements de table et les changements de données dans le même script.
Une autre alternative, si vous ne voulez pas diviser le code en lots séparés, est d'utiliser EXEC pour créer une étendue/un lot imbriqué comme ici