TSQL - Wie verwende ich GO in einem BEGIN .. END-Block?

96

Ich generiere ein Skript für die automatische Migration von Änderungen aus mehreren Entwicklungsdatenbanken zu Staging / Produktion. Grundsätzlich werden eine Reihe von Änderungsskripten benötigt, die zu einem einzigen Skript zusammengeführt werden, wobei jedes Skript in eine IF whatever BEGIN ... ENDAnweisung eingeschlossen wird.

Einige der Skripte erfordern jedoch eine GOAnweisung, damit der SQL-Parser beispielsweise nach der Erstellung eine neue Spalte kennt.

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

Sobald ich das jedoch in einen IFBlock einpacke:

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

Es schlägt fehl, weil ich eine BEGINohne Übereinstimmung sende END. Wenn ich das jedoch entferne GO, beschwert es sich erneut über eine unbekannte Spalte.

Gibt es eine Möglichkeit, dieselbe Spalte in einem einzelnen IFBlock zu erstellen und zu aktualisieren ?

BlueRaja - Danny Pflughoeft
quelle
2
@gbn: Ja, mir ist klar, warum dies passiert (siehe zweiter Absatz) . Aber ich habe keine Ahnung, wie ich das umgehen soll. Muss ich wirklich jede Abfrage in eine Reihe von Zeichenfolgen umwandeln?
BlueRaja - Danny Pflughoeft
@ BlueRaja: Was ist das Problem? Wenn es funktioniert, ist das alles, was am Ende des Tages zählt. Wenn es ein legitimes Geschäftsproblem mit der bereitgestellten Lösung gibt, drücken Sie dies bitte aus. Ist es etwas besonders Beunruhigendes, jede Abfrage in eine Reihe von Zeichenfolgen umzuwandeln?
Mellamokb
1
@ Mellamokb: Ja, es gibt ein Problem. Wenn das Wort GO in einem anderen Kontext verwendet wird (z. B. in einem Kommentar oder einer Zeichenfolge), funktioniert das Skript nicht. Außerdem verlieren wir die nützlichen Zeilennummern in Fehlermeldungen, falls etwas schief geht. Gibt es keine Möglichkeit, dies bei Transaktionen zu tun? Oder versuchen / fangen?
BlueRaja - Danny Pflughoeft
@BlueRaja: 1) Ich glaube GO, es muss eine eigene Zeile sein, damit Sie nur nach diesem Fall suchen können und nicht nach jeder Instanz des Wortes GO. 2) Sie können jederzeit protokollieren, welche Anweisungen erfolgreich abgeschlossen wurden. Oder Sie können das Ganze in einen Versuch / Fang einwickeln und Ihre eigenen Zeilennummern mit einer Variablen wie @lineNo verwenden, die Sie verfolgen und über Fehler berichten. Da Sie diese automatisch generieren, sollten Änderungen wie diese ein Kinderspiel sein. Es hört sich einfach so an, als ob Sie diese Route einfach nicht erkunden möchten, wenn ich denke, dass es Lösungen für all Ihre Anliegen gibt.
Mellamokb

Antworten:

43

GO ist kein SQL - es ist einfach ein Batch-Trennzeichen, das in einigen MS SQL-Tools verwendet wird.

Wenn Sie dies nicht verwenden, müssen Sie sicherstellen, dass die Anweisungen separat ausgeführt werden - entweder in verschiedenen Stapeln oder mithilfe von dynamischem SQL für die Grundgesamtheit (danke @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
quelle
8
Ja ich verstehe das. Dies beantwortet die Frage nicht - ich muss eine Spalte im selben IFBlock erstellen und aktualisieren .
BlueRaja - Danny Pflughoeft
@Oded: Würde das ;hier raushelfen ? - Sie haben gerade Ihre Antwort bearbeitet: o)
Neil Knight
@Neil - das ist mein Denken, ja.
Oded
;funktioniert auch nicht - der Parser gibt mir immer noch "Ungültiger Spaltenname 'EMP_IS_ADMIN'".
BlueRaja - Danny Pflughoeft
Beim Kompilieren des Stapels ist EMP_IS_ADMIN nicht vorhanden. stackoverflow.com/questions/4855537/…
gbn
43

Ich hatte das gleiche Problem und konnte es schließlich mit SET NOEXEC lösen .

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
quelle
2
Dies ist eine großartige Lösung!
Bazinga
+1! Dies ist die bislang EINZIGE praktische Antwort für die Verwendung in einem SS- SQLCMDModus-Skript (dh einem Master-Bereitstellungsskript), das (über einen :rBefehl) andere SS-Skripte (dh Unterbereitstellungsskripte) mit einigen dieser Aufrufe in ifAnweisungen aufruft . Die Antworten von Oded, Mellamokb und Andy Joiner, alle diese Aussagen in execCalls / begin- aufzunehmen end, sind keine Starter. Außerdem funktioniert die begin- end-Methode nicht, wenn eine createAnweisung vorhanden ist (z. B. erfordert eine explizite Anweisung godavor). Aber Mann, "Heilige Doppel-Negative, Batman!" ;)
Tom
Zur besseren Lesbarkeit (z. B. um die Doppel-Negative zu überwinden und klarer zu machen, dass ein virtueller if Block simuliert wird ) würde ich dem Block einen -- If whateverKommentar voranstellen, den Block einrücken und den Block mit einem --end If whateverKommentar nachfixieren.
Tom
Du hast meinen Speck gerettet! Ich habe Merge-Statements ausgeführt und diese dummen
GOs sind
Hm, ich bekomme irgendwie einen Fehler beim Update, nachdem set noexec on ausgeführt wurde? (Fehler, dass der zu aktualisierende Spaltenname ungültig ist) Wird unter MSSQL 2014 im Abfrageeditor ausgeführt. Funktioniert gut, wenn die Bedingung falsch wird (daher bleibt noexec ausgeschaltet)
Jerry
16

Sie können versuchen sp_executesql, den Inhalt zwischen den einzelnen GOAnweisungen in eine separate auszuführende Zeichenfolge aufzuteilen, wie im folgenden Beispiel gezeigt. Außerdem gibt es eine Variable @statementNo, mit der verfolgt werden kann, welche Anweisung zum einfachen Debuggen ausgeführt wird, wenn eine Ausnahme aufgetreten ist. Die Zeilennummern beziehen sich auf den Anfang der entsprechenden Anweisungsnummer, die den Fehler verursacht hat.

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

Sie können auch einfach mehrzeilige Anweisungen ausführen, wie im obigen Beispiel gezeigt, indem Sie sie einfach in einfache Anführungszeichen ( ') setzen. Vergessen Sie nicht, ''beim Erstellen der Skripte einfache Anführungszeichen in der Zeichenfolge mit einem doppelten einfachen Anführungszeichen ( ) zu umgehen.

mellamokb
quelle
Denken Sie nicht, dass dies für Befehle funktionieren würde, die über mehrere Zeilen verteilt sind, oder?
BlueRaja - Danny Pflughoeft
@BlueRaja: Ich habe das Beispiel aktualisiert, um zu zeigen, wie es funktionieren würde. Diese Zeichenfolgen können mehrzeilig sein, solange alle darin enthaltenen einfachen Anführungszeichen (') mit einem doppelten einfachen Anführungszeichen (' ')
maskiert werden
1
@ Mellamokb: Genau genommen benötigt nur das UPDATE sp_executesql ... stackoverflow.com/questions/4855537/…
gbn
1
@gbn: Stimmt. Wenn Sie dies jedoch für Hunderte von Anweisungen automatisieren möchten, ist es einfacher, es blind auf alle Anweisungen anzuwenden, anstatt zu entscheiden, wann und wo Sie es benötigen.
Mellamokb
@gbn @mellamokb: Ich meinte Aussagen wie SELECT * <newline> FROM whatever. Wenn ich jede Zeile mit einer eigenen EXEC-Anweisung ausführe, wird dies unterbrochen. Oder schlagen Sie vor, dass ich bei jeder GOAussage breche ?
BlueRaja - Danny Pflughoeft
9

Ich habe es letztendlich zum Laufen gebracht, indem ich jede Instanz GOauf ihrer eigenen Linie durch ersetzt habe

END
GO

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

Dies ist stark bevorzugt , jede Gruppe von Anweisungen in einer Zeichenfolge zu wickeln, ist aber noch weit vom Ideal entfernt . Wenn jemand eine bessere Lösung findet, poste sie und ich akzeptiere sie stattdessen.

BlueRaja - Danny Pflughoeft
quelle
6
Wenn die erste Bedingung "Wenn diese Spalte nicht vorhanden ist" lautet, lautet die erste Anweisung im Block "Diese Spalte hinzufügen". Bei der zweiten Überprüfung der Bedingung wird die Spalte gefunden und die zweite Anweisung
Damien_The_Unbeliever
@ Damien: Richtig; Glücklicherweise wird dies in meinem Fall niemals passieren (die Bedingung ist immer eine Überprüfung auf einen bestimmten Wert in einer bestimmten Tabelle, die immer als letzte Anweisung des IFBlocks hinzugefügt wird ). Es scheint, als gäbe es in SQL einfach keinen guten Weg, dies zu tun.
BlueRaja - Danny Pflughoeft
Die set noexecAntwort von Mina Jacob ist die bislang EINZIGE praktische Antwort für die Verwendung in einem SS- SQLCMDModus-Skript (dh einem Master-Bereitstellungsskript), das (über einen :rBefehl) andere SS-Skripte (dh Unterbereitstellungsskripte) mit einigen dieser Aufrufe in ifAnweisungen aufruft . Die Antworten von Oded, Mellamokb und Andy Joiner, alle diese Aussagen in execCalls / begin- aufzunehmen end, sind keine Starter. Außerdem funktioniert die begin- end-Methode nicht, wenn eine createAnweisung vorhanden ist (z. B. erfordert eine explizite Anweisung godavor).
Tom
8

Sie können die Anweisungen in BEGIN und END anstelle des GO dazwischen einschließen

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

(Getestet in der Northwind-Datenbank)

Bearbeiten: (wahrscheinlich auf SQL2012 getestet)

Andy Joiner
quelle
1
Bitte geben Sie Grund für -1
Andy Joiner
1
Ich weiß nicht, warum es herabgestuft wurde ... funktioniert für mich wie ein Zauber.
Thorarin
10
Bei Verwendung von SQL Server 2008 R2 scheint dies bei mir nicht zu funktionieren. Es wird weiterhin die Fehlermeldung "Ungültiger Spaltenname" EMP_IS_ADMIN "angezeigt." in der UPDATE-Zeile.
MerickOWA
Das BEGIN-END-Batching hat bei mir mit SQL Server 2016 funktioniert. IMO ist dies die sauberste Syntax.
Uber Schnoz
Die set noexecAntwort von Mina Jacob ist die bislang EINZIGE praktische Antwort für die Verwendung in einem SS- SQLCMDModus-Skript (dh einem Master-Bereitstellungsskript), das (über einen :rBefehl) andere SS-Skripte (dh Unterbereitstellungsskripte) mit einigen dieser Aufrufe in ifAnweisungen aufruft . Die Antworten von Oded, Mellamokb und Andy Joiner, alle diese Aussagen in execCalls / begin- aufzunehmen end, sind keine Starter. Außerdem funktioniert die begin- end-Methode nicht, wenn eine createAnweisung vorhanden ist (z. B. erfordert eine explizite Anweisung godavor).
Tom
1

Sie können diese Lösung ausprobieren:

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

--if upper code not true

ALTER...
GO
UPDATE...
GO
Luk
quelle
1
Nicht sehr nützlich, wenn Sie mehrere if-else-Blöcke nacheinander haben, oder?
Jerry
0

Ich habe RAISERRORin der Vergangenheit dafür verwendet

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
quelle
-1

Sie können a- GOTOund LABELAnweisungen einfügen, um Code zu überspringen, sodass die GOSchlüsselwörter intakt bleiben.

jim a
quelle
5
Es scheint, dass LABELs nicht über GO-Anweisungen hinweg referenziert werden können, da sie nicht in dem Stapel enthalten sind, der
berkeleybross