TSQL - Comment utiliser GO à l'intérieur d'un bloc BEGIN .. END?

96

Je génère un script pour migrer automatiquement les modifications de plusieurs bases de données de développement vers la préparation / production. Fondamentalement, cela prend un tas de scripts de changement et les fusionne en un seul script, enveloppant chaque script dans une IF whatever BEGIN ... ENDinstruction.

Cependant, certains scripts nécessitent une GOinstruction afin que, par exemple, l'analyseur SQL connaisse une nouvelle colonne après sa création.

ALTER TABLE dbo.EMPLOYEE 
ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
GO -- Necessary, or next line will generate "Unknown column:  EMP_IS_ADMIN"
UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever

Cependant, une fois que j'enroule cela dans un IFbloc:

IF whatever
BEGIN
    ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
    GO
    UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever
END

Il échoue parce que j'envoie un BEGINsans correspondance END. Cependant, si je supprime le, GOil se plaint à nouveau d'une colonne inconnue.

Existe-t-il un moyen de créer et de mettre à jour la même colonne dans un seul IFbloc?

BlueRaja - Danny Pflughoeft
la source
2
Voir stackoverflow.com/questions/4855537/… s'il vous plaît
gbn
2
@gbn: Oui, je comprends pourquoi cela se produit (voir le deuxième paragraphe) ; mais je ne sais pas comment contourner ce problème - ai-je vraiment besoin de transformer chaque requête en un groupe de chaînes !?
BlueRaja - Danny Pflughoeft
@BlueRaja: Quelle est la préoccupation? Si cela fonctionne, c'est tout ce qui compte à la fin de la journée. S'il y a un problème commercial légitime avec la solution fournie, veuillez l'exprimer. Y a-t-il quelque chose de particulièrement déconcertant à propos de la conversion de chaque requête en un groupe de chaînes?
mellamokb
1
@mellamokb: Oui, il y a un problème; si le mot GO est utilisé dans un autre contexte (tel qu'un commentaire ou une chaîne), le script ne fonctionnera pas. En outre, nous perdons les numéros de ligne utiles dans les messages d'erreur en cas de problème. N'y a-t-il aucun moyen de faire cela avec les transactions? Ou essayer / attraper?
BlueRaja - Danny Pflughoeft
@BlueRaja: 1) Je pense qu'il GOdoit être sur une ligne par lui-même, vous pouvez donc rechercher uniquement ce cas, et pas toutes les instances du mot GO. 2) Vous pouvez toujours enregistrer les instructions qui ont été exécutées avec succès. Ou vous pouvez envelopper le tout dans un try / catch et utiliser vos propres numéros de ligne en utilisant une variable, comme @lineNo, dont vous gardez une trace et signalez l'erreur. Puisque vous les générez automatiquement, apporter des modifications comme celles-ci devrait être un jeu d'enfant. Il semble juste que vous ne vouliez pas explorer cette voie alors que je pense qu'il y a des solutions à trouver pour toutes vos préoccupations.
mellamokb

Réponses:

44

J'ai eu le même problème et j'ai finalement réussi à le résoudre en utilisant SET NOEXEC .

IF not whatever
BEGIN
    SET NOEXEC ON; 
END

ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
GO
UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever

SET NOEXEC OFF; 
Mina Jacob
la source
2
C'est une excellente solution!
Bazinga
+1! C'est la SEULE réponse pratique à ce jour à utiliser dans un SQLCMDscript de mode SS (c'est-à-dire un script de déploiement principal) qui appelle (via une :rcommande) d'autres scripts SS (c'est-à-dire des scripts de sous-déploiement) avec certains de ces appels dans des ifdéclarations. Les réponses d'Oded, de mellamokb et d'Andy Joiner qui ont inclus toutes ces déclarations dans les execappels / begin- endne sont pas des démarreurs. De plus, la méthode begin- endne fonctionnera pas s'il y a une createdéclaration (par exemple, elle nécessite un explicite goavant). Mais, mec, "Saint doubles négatifs, Batman!" ;)
Tom
Pour la lisibilité (par exemple, pour aider à surmonter les doubles négatifs et rendre plus clair qu'il simule un bloc virtuel if ), je préfixerais le bloc avec un -- If whatevercommentaire, mettrais en retrait le bloc et postfixerais le bloc avec un --end If whatevercommentaire.
Tom
Vous avez sauvé mon bacon! J'exécutais des déclarations de fusion et ces GO stupides n'aiment pas être dans un IF BEGIN END ELSE
Omzig
Hm, j'obtiens une erreur sur la mise à jour après l'exécution de set noexec on? (erreur indiquant que le nom de la colonne à mettre à jour n'est pas valide) Exécution sur MSSQL 2014 dans l'éditeur de requêtes. Fonctionne bien si la condition devient fausse (donc noexec reste désactivé)
Jerry
43

GO n'est pas SQL - c'est simplement un séparateur de lots utilisé dans certains outils MS SQL.

Si vous ne l'utilisez pas, vous devez vous assurer que les instructions sont exécutées séparément - soit dans différents lots ou en utilisant SQL dynamique pour la population (merci @gbn):

IF whatever
BEGIN
    ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL;

    EXEC ('UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever')
END
Oded
la source
8
Oui, je comprends cela. Cela ne répond pas à la question - je dois créer et mettre à jour une colonne dans le même IFbloc.
BlueRaja - Danny Pflughoeft
@Oded: Est-ce que de l' ;aide ici? - Vous venez de modifier votre réponse: o)
Neil Knight
@Neil - c'est ma pensée, oui.
Oded le
;ne fonctionne pas non plus - l'analyseur me donne toujours "Nom de colonne non valide 'EMP_IS_ADMIN'."
BlueRaja - Danny Pflughoeft
Lorsque le lot est compilé, EMP_IS_ADMIN n'existe pas. stackoverflow.com/questions/4855537/…
gbn
16

Vous pouvez essayer de sp_executesqldiviser le contenu entre chaque GOinstruction en une chaîne distincte à exécuter, comme illustré dans l'exemple ci-dessous. En outre, il existe une variable @statementNo pour suivre quelle instruction est exécutée pour un débogage facile là où une exception s'est produite. Les numéros de ligne seront relatifs au début du numéro de relevé pertinent qui a causé l'erreur.

BEGIN TRAN

DECLARE @statementNo INT
BEGIN TRY
    IF 1=1
    BEGIN
        SET @statementNo = 1
        EXEC sp_executesql
            N'  ALTER TABLE dbo.EMPLOYEE
                    ADD COLUMN EMP_IS_ADMIN BIT NOT NULL'

        SET @statementNo = 2
        EXEC sp_executesql
            N'  UPDATE dbo.EMPLOYEE
                    SET EMP_IS_ADMIN = 1'

        SET @statementNo = 3
        EXEC sp_executesql
            N'  UPDATE dbo.EMPLOYEE
                    SET EMP_IS_ADMIN = 1x'
    END
END TRY
BEGIN CATCH
    PRINT 'Error occurred on line ' + cast(ERROR_LINE() as varchar(10)) 
       + ' of ' + 'statement # ' + cast(@statementNo as varchar(10)) 
       + ': ' + ERROR_MESSAGE()
    -- error occurred, so rollback the transaction
    ROLLBACK
END CATCH
-- if we were successful, we should still have a transaction, so commit it
IF @@TRANCOUNT > 0
    COMMIT

Vous pouvez également facilement exécuter des instructions multilignes, comme illustré dans l'exemple ci-dessus, en les entourant simplement de guillemets simples ( '). N'oubliez pas d'échapper les guillemets simples contenus dans la chaîne avec un guillemet simple double ( '') lors de la génération des scripts.

mellamokb
la source
Ne pensez pas que cela fonctionnerait pour les commandes réparties sur plusieurs lignes, n'est-ce pas?
BlueRaja - Danny Pflughoeft
@BlueRaja: J'ai mis à jour l'exemple pour montrer comment cela fonctionnerait. Ces chaînes peuvent être sur plusieurs lignes, à condition que les guillemets simples (') contenus à l'intérieur soient échappés à l'aide d'un guillemet simple double (' ')
mellamokb
1
@mellamokb: à proprement parler, seule la mise à jour a besoin de sp_executesql ... stackoverflow.com/questions/4855537
gbn
1
@gbn: Vrai. Mais si vous prévoyez d'automatiser cela pour des centaines d'instructions, il sera plus facile de l'appliquer aveuglément à toutes les instructions au lieu de décider quand et où vous en avez besoin.
mellamokb
@gbn @mellamokb: Je voulais dire des déclarations comme SELECT * <newline> FROM whatever. Si j'exécute chaque ligne avec sa propre instruction EXEC, cela va casser. Ou suggérez-vous que je m'interromps à chaque GOdéclaration?
BlueRaja - Danny Pflughoeft
9

J'ai finalement réussi à le faire fonctionner en remplaçant chaque instance de GOsur sa propre ligne par

END
GO

---Automatic replacement of GO keyword, need to recheck IF conditional:
IF whatever
BEGIN

Ceci est grandement préférable à l'encapsulation de chaque groupe d'instructions dans une chaîne, mais c'est encore loin d'être idéal. Si quelqu'un trouve une meilleure solution, postez-la et je l'accepterai à la place.

BlueRaja - Danny Pflughoeft
la source
6
Si le premier conditionnel est "si cette colonne n'existe pas", la première instruction du bloc est "ajouter cette colonne", alors la deuxième vérification du conditionnel trouvera la colonne et n'exécutera pas la deuxième instruction,
Damien_The_Unbeliever
@Damien: Vrai; heureusement, cela ne se produira jamais dans mon cas (le conditionnel est toujours une vérification d'une valeur spécifique dans une table spécifique, qui est toujours ajoutée comme dernière instruction du IFbloc). Il semble qu'il n'y ait tout simplement pas de bon moyen de faire cela en SQL.
BlueRaja - Danny Pflughoeft
La set noexecréponse de Mina Jacob est la SEULE réponse pratique à ce jour à utiliser dans un SQLCMDscript de mode SS (c'est-à-dire un script de déploiement principal) qui appelle (via une :rcommande) d'autres scripts SS (c'est-à-dire des scripts de sous-déploiement) avec certains de ces appels dans des ifdéclarations. Les réponses d'Oded, de mellamokb et d'Andy Joiner qui ont inclus toutes ces déclarations dans les execappels / begin- endne sont pas des démarreurs. De plus, la méthode begin- endne fonctionnera pas s'il y a une createdéclaration (par exemple, elle nécessite un explicite goavant).
Tom
8

Vous pouvez placer les instructions dans BEGIN et END au lieu de GO entre

IF COL_LENGTH('Employees','EMP_IS_ADMIN') IS NULL --Column does not exist
BEGIN
    BEGIN
        ALTER TABLE dbo.Employees ADD EMP_IS_ADMIN BIT
    END

    BEGIN
        UPDATE EMPLOYEES SET EMP_IS_ADMIN = 0
    END
END

(Testé sur la base de données Northwind)

Edit: (probablement testé sur SQL2012)

Andy Joiner
la source
1
Veuillez donner la raison pour -1
Andy Joiner
1
Je ne sais pas pourquoi il a été déclassé ... ça marche comme un charme pour moi.
Thorarin
10
En utilisant SQL Server 2008 R2, cela ne semble pas fonctionner pour moi, j'obtiens toujours une erreur "Nom de colonne non valide" EMP_IS_ADMIN "." sur la ligne UPDATE.
MerickOWA
Le traitement par lots BEGIN-END a fonctionné pour moi en utilisant SQL Server 2016. IMO, c'est la syntaxe la plus propre.
Uber Schnoz
La set noexecréponse de Mina Jacob est la SEULE réponse pratique à ce jour à utiliser dans un SQLCMDscript de mode SS (c'est-à-dire un script de déploiement principal) qui appelle (via une :rcommande) d'autres scripts SS (c'est-à-dire des scripts de sous-déploiement) avec certains de ces appels dans des ifdéclarations. Les réponses d'Oded, de mellamokb et d'Andy Joiner qui ont inclus toutes ces déclarations dans les execappels / begin- endne sont pas des démarreurs. De plus, la méthode begin- endne fonctionnera pas s'il y a une createdéclaration (par exemple, elle nécessite un explicite goavant).
Tom
1

Vous pouvez essayer cette solution:

if exists(
SELECT...
)
BEGIN
PRINT 'NOT RUN'
RETURN
END

--if upper code not true

ALTER...
GO
UPDATE...
GO
Luk
la source
1
Pas très utile si vous avez plusieurs blocs if-else les uns après les autres, non?
Jerry
0

J'ai utilisé RAISERRORdans le passé pour cela

IF NOT whatever BEGIN
    RAISERROR('YOU''RE ALL SET, and sorry for the error!', 20, -1) WITH LOG
END

ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
GO
UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever
kavun
la source
-1

Vous pouvez incorporer des instructions GOTOet LABELpour sauter le code, laissant ainsi les GOmots - clés intacts.

Jim un
la source
5
Il semble que les LABEL ne puissent pas être référencées dans les instructions GO car elles ne font pas partie du lot envoyé à SQL
berkeleybross