TSQL - Come utilizzare GO all'interno di un blocco BEGIN .. END?


96

Sto generando uno script per la migrazione automatica delle modifiche da più database di sviluppo a staging / produzione. Fondamentalmente, ci vogliono un sacco di script di modifica e li unisce in un singolo script, avvolgendo ogni script in IF whatever BEGIN ... ENDun'istruzione.

Tuttavia, alcuni degli script richiedono GOun'istruzione in modo che, ad esempio, il parser SQL sia a conoscenza di una nuova colonna dopo che è stata creata.

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

Tuttavia, una volta che lo avvolgo in un IFblocco:

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

Non riesce perché invio un messaggio BEGINsenza corrispondenza END. Tuttavia, se rimuovo il, GOsi lamenta di nuovo di una colonna sconosciuta.

C'è un modo per creare e aggiornare la stessa colonna all'interno di un singolo IFblocco?



2
@gbn: Sì, mi rendo conto del motivo per cui questo accade (vedi secondo paragrafo) ; ma non ho idea di come aggirarlo - ho davvero bisogno di trasformare ogni query in un mucchio di stringhe !?
BlueRaja - Danny Pflughoeft

@BlueRaja: qual è il problema? Se funziona, è tutto ciò che conta alla fine della giornata. Se c'è un problema aziendale legittimo con la soluzione fornita, esprimilo. C'è qualcosa di specificamente sconcertante nel convertire ogni query in un mucchio di stringhe?
mellamokb

1
@mellamokb: Sì, c'è un problema; se la parola GO viene utilizzata in qualsiasi altro contesto (come un commento o una stringa), lo script non funzionerà. Inoltre, perdiamo i numeri di riga utili nei messaggi di errore nel caso qualcosa vada storto. Non c'è modo di farlo con le transazioni? O provare / catturare?
BlueRaja - Danny Pflughoeft

@BlueRaja: 1) Credo che GOdebba essere su una riga da solo, quindi puoi cercare solo quel caso e non ogni istanza della parola GO. 2) Puoi sempre registrare quali istruzioni sono state completate con successo. Oppure puoi racchiudere il tutto in un tentativo / cattura e utilizzare i tuoi numeri di riga utilizzando alcune variabili, come @lineNo, di cui tieni traccia e segnalare gli errori. Dal momento che li stai generando automaticamente, apportare modifiche come questa dovrebbe essere un gioco da ragazzi. Sembra proprio che tu non voglia esplorare questo percorso quando penso che ci siano soluzioni da trovare per tutte le tue preoccupazioni.
mellamokb

Risposte:


44

Ho avuto lo stesso problema e finalmente sono riuscito a risolverlo usando 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; 

2
Questa è un'ottima soluzione!
Bazinga

+1! Questa è l'UNICA risposta pratica finora per l'uso in uno SQLCMDscript in modalità SS (cioè uno script di distribuzione principale) che chiama (tramite :rcomando) altri script SS (cioè script di distribuzione secondaria) con alcune di queste chiamate all'interno di ifistruzioni. Le risposte di Oded, mellamokb e Andy Joiner di racchiudere tutte quelle dichiarazioni nelle execchiamate / begin- endnon sono partenti. Inoltre, il metodo begin- endnon funzionerà se c'è createun'istruzione (es. Richiede un esplicito goprima di essa). Ma, amico, "Santo doppio negativo, Batman!" ;)
Tom

Per la leggibilità (ad esempio, per aiutare a superare i doppi negativi e rendere più chiaro che sta simulando un blocco virtuale if ), aggiungerei un -- If whatevercommento al blocco come prefisso, indenterei il blocco e postfissi il blocco con un --end If whatevercommento.
Tom

Mi hai salvato la pancetta! Stavo eseguendo dichiarazioni di fusione e a quegli stupidi GO non piace essere all'interno di un IF BEGIN END ELSE
Omzig

Hm, ricevo un errore durante l'aggiornamento in qualche modo dopo che l'impostazione noexec su è stata eseguita? (errore che il nome della colonna da aggiornare non è valido) In esecuzione su MSSQL 2014 nell'editor di query. Funziona bene se la condizione diventa falsa (quindi noexec rimane spento)
Jerry

43

GO non è SQL: è semplicemente un separatore di batch utilizzato in alcuni strumenti MS SQL.

Se non lo usi, devi assicurarti che le istruzioni vengano eseguite separatamente, in batch diversi o utilizzando SQL dinamico per la popolazione (grazie @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

8
Sì, lo capisco. Questo non risponde alla domanda: devo creare e aggiornare una colonna nello stesso IFblocco.
BlueRaja - Danny Pflughoeft

@Oded: L' ;aiuto sarebbe qui? - Hai appena modificato la tua risposta: o)
Neil Knight

@ Neil - questo è il mio pensiero, sì.
Oded il

;non funziona neanche - il parser continua a darmi "Nome colonna non valido" EMP_IS_ADMIN "."
BlueRaja - Danny Pflughoeft,

Quando il batch viene compilato, EMP_IS_ADMIN non esiste. stackoverflow.com/questions/4855537/…
gbn

16

Potresti provare a sp_executesqlsuddividere il contenuto tra ciascuna GOistruzione in una stringa separata da eseguire, come mostrato nell'esempio seguente. Inoltre, c'è una variabile @statementNo per tenere traccia dell'istruzione in esecuzione per un facile debug in cui si è verificata un'eccezione. I numeri di riga saranno relativi all'inizio del numero di istruzione pertinente che ha causato l'errore.

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

Puoi anche eseguire facilmente istruzioni su più righe, come mostrato nell'esempio sopra, semplicemente racchiudendole tra virgolette singole ( '). Non dimenticare di eseguire l'escape di virgolette singole contenute nella stringa con virgolette doppie ( '') durante la generazione degli script.


Non credo che questo funzionerebbe per comandi suddivisi su più righe, vero?
BlueRaja - Danny Pflughoeft

@BlueRaja: ho aggiornato l'esempio per mostrare come funzionerebbe. Queste stringhe possono essere multi-riga, a condizione che le virgolette singole (') contenute all'interno siano sfuggite usando una virgoletta singola doppia (' ')
mellamokb

1
@mellamokb: in senso stretto, solo l'AGGIORNAMENTO necessita di sp_executesql ... stackoverflow.com/questions/4855537/…
gbn

1
@gbn: vero. Ma se hai intenzione di automatizzarlo per centinaia di affermazioni, sarà più facile applicarlo alla cieca su tutte le affermazioni invece di decidere quando e dove ne hai bisogno.
mellamokb

@gbn @mellamokb: intendevo affermazioni come SELECT * <newline> FROM whatever. Se eseguo ogni riga con la propria istruzione EXEC, si interromperà. O stai suggerendo di infrangere ogni GOaffermazione?
BlueRaja - Danny Pflughoeft

9

Alla fine l'ho fatto funzionare sostituendo ogni istanza di GOsulla propria linea con

END
GO

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

Questo è di gran lunga preferibile a racchiudere ogni gruppo di istruzioni in una stringa, ma è ancora lontano dall'essere ideale. Se qualcuno trova una soluzione migliore, pubblicala e invece la accetterò.


6
Se il primo condizionale è "se questa colonna non esiste", la prima istruzione nel blocco è "aggiungi questa colonna", quindi il secondo controllo del condizionale troverà la colonna e non eseguirà la seconda istruzione,
Damien_The_Unbeliever

@Damien: vero; fortunatamente, questo non accadrà mai nel mio caso (il condizionale è sempre un controllo di un valore specifico in una tabella specifica, che viene sempre aggiunto come ultima istruzione del IFblocco). Sembra che non ci sia un buon modo per farlo in SQL.
BlueRaja - Danny Pflughoeft

La set noexecrisposta di Mina Jacob è l'UNICA risposta pratica finora per l'uso in uno SQLCMDscript in modalità SS (cioè uno script di distribuzione principale) che chiama (tramite :rcomando) altri script SS (cioè script di distribuzione secondaria) con alcune di quelle chiamate all'interno di ifistruzioni. Le risposte di Oded, mellamokb e Andy Joiner di racchiudere tutte quelle affermazioni nelle execchiamate / begin- endnon sono partenti. Inoltre, il metodo begin- endnon funzionerà se c'è createun'istruzione (es. Richiede un esplicito goprima di essa).
Tom

8

È possibile racchiudere le istruzioni in BEGIN e END anziché nel GO tra di esse

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

(Testato sul database Northwind)

Modifica: (probabilmente testato su SQL2012)


1
Si prega di dare ragione per -1
Andy Joiner

1
Non so perché è stato svalutato ... funziona come un fascino per me.
Thorarin

10
Utilizzando SQL Server 2008 R2, questo non sembra funzionare per me, ricevo ancora un errore "Nome colonna non valido" EMP_IS_ADMIN "." nella riga UPDATE.
MerickOWA

Il batch BEGIN-END ha funzionato per me utilizzando SQL Server 2016. IMO questa è la sintassi più pulita.
Uber Schnoz

La set noexecrisposta di Mina Jacob è l'UNICA risposta pratica finora per l'uso in uno SQLCMDscript in modalità SS (cioè uno script di distribuzione principale) che chiama (tramite :rcomando) altri script SS (cioè script di distribuzione secondaria) con alcune di quelle chiamate all'interno di ifistruzioni. Le risposte di Oded, mellamokb e Andy Joiner di racchiudere tutte quelle affermazioni nelle execchiamate / begin- endnon sono partenti. Inoltre, il metodo begin- endnon funzionerà se c'è createun'istruzione (es. Richiede un esplicito goprima di essa).
Tom

1

Puoi provare questa soluzione:

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

--if upper code not true

ALTER...
GO
UPDATE...
GO

1
Non molto utile se hai diversi blocchi if-else uno dopo l'altro, giusto?
Jerry

0

L'ho usato RAISERRORin passato per questo

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

-1

È possibile incorporare istruzioni GOTOe LABELper saltare il codice, lasciando così GOintatte le parole chiave.


5
Sembra che non sia possibile fare riferimento a LABEL tra le istruzioni GO poiché non sono nel batch inviato a SQL
berkeleybross
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.