TSQL - Jak używać GO wewnątrz bloku BEGIN .. END?

96

Generuję skrypt do automatycznej migracji zmian z wielu programistycznych baz danych do przemieszczania / produkcji. Zasadniczo wymaga kilku skryptów zmian i łączy je w jeden skrypt, opakowując każdy skrypt w IF whatever BEGIN ... ENDinstrukcję.

Jednak niektóre skrypty wymagają GOinstrukcji, aby na przykład parser SQL wiedział o nowej kolumnie po jej utworzeniu.

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

Jednak kiedy zawinąłem to w IFblok:

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

Nie udaje się, ponieważ wysyłam BEGINbez dopasowania END. Jeśli jednak usunę GOto znowu narzeka na nieznaną kolumnę.

Czy istnieje sposób na utworzenie i zaktualizowanie tej samej kolumny w jednym IFbloku?

BlueRaja - Danny Pflughoeft
źródło
2
@gbn: Tak, zdaję sobie sprawę, dlaczego tak się dzieje (zobacz drugi akapit) ; ale nie mam pojęcia, jak to obejść - czy naprawdę muszę zamieniać każde zapytanie w kilka ciągów znaków !?
BlueRaja - Danny Pflughoeft
@BlueRaja: O co chodzi? Jeśli to zadziała, to wszystko, co się liczy pod koniec dnia. Jeśli istnieje uzasadniony problem biznesowy z dostarczonym rozwiązaniem, wyraź to. Czy jest coś szczególnie niepokojącego w konwertowaniu każdego zapytania na zbiór ciągów?
mellamokb
1
@mellamokb: Tak, jest problem; jeśli słowo GO zostanie użyte w jakimkolwiek innym kontekście (takim jak komentarz lub ciąg znaków), skrypt nie zadziała. Ponadto tracimy przydatne numery wierszy w komunikatach o błędach na wypadek, gdyby coś poszło nie tak. Czy nie można tego zrobić w przypadku transakcji? Albo spróbować / złapać?
BlueRaja - Danny Pflughoeft
@BlueRaja: 1) Uważam, że GOmusi znajdować się w osobnej linii, więc możesz wyszukać tylko ten przypadek, a nie każde wystąpienie tego słowa GO. 2) Zawsze możesz zarejestrować, które instrukcje zostały pomyślnie wykonane. Lub możesz zawinąć całość w try / catch i użyć własnych numerów linii, używając jakiejś zmiennej, takiej jak @lineNo, którą śledzisz i zgłaszać błąd. Ponieważ generujesz je automatycznie, wprowadzanie takich zmian powinno być proste. Wygląda na to, że po prostu nie chcesz badać tej drogi, kiedy myślę, że są rozwiązania, które można znaleźć dla wszystkich twoich obaw.
mellamokb

Odpowiedzi:

45

Miałem ten sam problem iw końcu udało mi się go rozwiązać za pomocą 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
źródło
2
To świetne rozwiązanie!
Bazinga
+1! Jak dotąd jest to JEDYNA praktyczna odpowiedź do użycia w SQLCMDskrypcie trybu SS (tj. Głównym skrypcie wdrażania), który wywołuje (za pośrednictwem :rpolecenia) inne skrypty SS (tj. Skrypty wdrażania podrzędnego) z niektórymi z tych wywołań wewnątrz ifinstrukcji. Odpowiedzi Odeda, mellamokb i Andy'ego Joinera dotyczące załączania wszystkich tych stwierdzeń do execwezwań / begin- endnie są początkowe. Ponadto metoda begin- endnie zadziała, jeśli istnieje createinstrukcja (np. Wymaga goprzed nią jawnego ). Ale człowieku, "Święte podwójne negatywy, Batmanie!" ;)
Tom
Ze względu na czytelność (np. Aby pomóc przezwyciężyć podwójne negatywy i wyjaśnić, że symuluje wirtualny if blok), poprzedziłbym blok -- If whateverkomentarzem, wciąłbym blok i dodał do niego --end If whateverkomentarz.
Tom
Uratowałeś mój bekon!
Uruchomiłem
Hm, jakoś wyskakuje mi błąd przy aktualizacji po wykonaniu set noexec on? (błąd, że nazwa kolumny do aktualizacji jest nieprawidłowa) Działa w MSSQL 2014 w edytorze zapytań. Działa dobrze, jeśli warunek okaże się fałszywy (a więc noexec pozostanie wyłączony)
Jerry
43

GO to nie jest SQL - to po prostu separator wsadowy używany w niektórych narzędziach MS SQL.

Jeśli tego nie używasz, musisz upewnić się, że instrukcje są wykonywane osobno - w różnych partiach lub przy użyciu dynamicznego SQL dla populacji (dzięki @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
źródło
8
Tak rozumiem to. To nie odpowiada na pytanie - muszę utworzyć i zaktualizować kolumnę w tym samym IFbloku.
BlueRaja - Danny Pflughoeft
@Oded: Czy ;pomoc byłaby tutaj? - Właśnie zredagowałeś swoją odpowiedź: o)
Neil Knight
@Neil - tak myślę, tak.
Oded
;też nie działa - parser nadal podaje mi komunikat „Nieprawidłowa nazwa kolumny 'EMP_IS_ADMIN'."
BlueRaja - Danny Pflughoeft
Kiedy partia jest kompilowana, EMP_IS_ADMIN nie istnieje. stackoverflow.com/questions/4855537/ ...
gbn
16

Możesz spróbować podzielić sp_executesqlzawartość między każdą GOinstrukcją na oddzielny ciąg do wykonania, jak pokazano w poniższym przykładzie. Istnieje również zmienna @statementNo do śledzenia, która instrukcja jest wykonywana, aby ułatwić debugowanie, w którym wystąpił wyjątek. Numery wierszy będą podane względem początku odpowiedniego numeru instrukcji, który spowodował błąd.

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

Możesz również łatwo wykonywać instrukcje wielowierszowe, jak pokazano w powyższym przykładzie, po prostu zawijając je w pojedyncze cudzysłowy ( '). ''Podczas generowania skryptów nie zapomnij o zastąpieniu pojedynczych cudzysłowów zawartych w ciągu znaków za pomocą podwójnego apostrofu ( ).

mellamokb
źródło
Nie myśl, że to zadziałałoby dla poleceń podzielonych na wiele linii, prawda?
BlueRaja - Danny Pflughoeft
@BlueRaja: Zaktualizowałem przykład, aby pokazać, jak to działa. Te ciągi znaków mogą być wielowierszowe, o ile pojedyncze cudzysłowy (') zawarte w środku są
poprzedzone
1
@mellamokb: ściśle mówiąc, tylko AKTUALIZACJA wymaga sp_executesql ... stackoverflow.com/questions/4855537/
gbn
1
@gbn: True. Ale jeśli zamierzasz to zautomatyzować dla setek instrukcji, łatwiej będzie po prostu zastosować to na ślepo do wszystkich instrukcji, zamiast decydować, kiedy i gdzie tego potrzebujesz.
mellamokb
@gbn @mellamokb: Miałem na myśli takie stwierdzenia SELECT * <newline> FROM whatever. Jeśli wykonam każdy wiersz z własną instrukcją EXEC, to się zepsuje. A może sugerujesz, żebym łamał się przy każdym GOstwierdzeniu?
BlueRaja - Danny Pflughoeft
9

Ostatecznie udało mi się go uruchomić, zastępując każdą instancję GOna własnej linii przez

END
GO

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

Jest to znacznie lepsze niż zawijanie każdej grupy instrukcji w łańcuch, ale nadal jest dalekie od ideału. Jeśli ktoś znajdzie lepsze rozwiązanie, opublikuj je, a ja je zaakceptuję.

BlueRaja - Danny Pflughoeft
źródło
6
Jeśli pierwszy warunek to „jeśli ta kolumna nie istnieje”, pierwsza instrukcja w bloku to „dodaj tę kolumnę”, to drugie sprawdzenie warunku znajdzie kolumnę i nie wykona drugiej instrukcji,
Damien_The_Unbeliever
@Damien: True; na szczęście w moim przypadku tak się nigdy nie stanie (warunek jest zawsze sprawdzeniem określonej wartości w określonej tabeli, która jest zawsze dodawana jako ostatnia instrukcja IFbloku). Wydaje się, że po prostu nie ma dobrego sposobu na zrobienie tego w SQL.
BlueRaja - Danny Pflughoeft
set noexecOdpowiedź Miny Jacob jest jak dotąd JEDYNĄ praktyczną odpowiedzią do użycia w SQLCMDskrypcie trybu SS (tj. Głównym skrypcie wdrażania), który wywołuje (za pomocą :rpolecenia) inne skrypty SS (tj. Skrypty wdrażania podrzędnego) z niektórymi z tych wywołań wewnątrzif instrukcji. Odpowiedzi Odeda, mellamokb i Andy'ego Joinera dotyczące załączania wszystkich tych stwierdzeń do execwezwań / begin- endnie są początkowe. Ponadto metoda begin- endnie zadziała, jeśli istnieje createinstrukcja (np. Wymaga goprzed nią jawnego ).
Tom
8

Możesz ująć instrukcje w BEGIN i END zamiast GO pomiędzy

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

(Testowane w bazie danych Northwind)

Edycja: (prawdopodobnie przetestowane na SQL2012)

Andy Joiner
źródło
1
Proszę podać powód -1
Andy Joiner
1
Nie wiem, dlaczego został odrzucony ... dla mnie działa jak urok.
Thorarin
10
Używając SQL Server 2008 R2, wydaje mi się, że to nie działa, nadal pojawia się błąd „Nieprawidłowa nazwa kolumny„ EMP_IS_ADMIN ”. w wierszu UPDATE.
MerickOWA
Batching BEGIN-END działał dla mnie przy użyciu SQL Server 2016. IMO to jest najczystsza składnia.
Uber Schnoz
set noexecOdpowiedź Miny Jacob jest jak dotąd JEDYNĄ praktyczną odpowiedzią do użycia w SQLCMDskrypcie trybu SS (tj. Głównym skrypcie wdrażania), który wywołuje (za pomocą :rpolecenia) inne skrypty SS (tj. Skrypty wdrażania podrzędnego) z niektórymi z tych wywołań wewnątrz ifinstrukcji. Odpowiedzi Odeda, mellamokb i Andy Joinera dotyczące załączania wszystkich tych stwierdzeń w execwezwaniach / begin- endnie są początkowe. Ponadto metoda begin- endnie zadziała, jeśli istnieje createinstrukcja (np. Wymaga goprzed nią jawnego ).
Tom
1

Możesz wypróbować to rozwiązanie:

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

--if upper code not true

ALTER...
GO
UPDATE...
GO
Luk
źródło
1
Niezbyt przydatne, jeśli masz kilka bloków if-else jeden po drugim, prawda?
Jerry,
0

Używałem tego RAISERRORw przeszłości

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
źródło
-1

Możesz dołączyć instrukcje a GOTOi LABEL, aby pominąć kod, pozostawiając w ten sposób GOsłowa kluczowe nietknięte.

Jim a
źródło
5
Wygląda na to, że nie można odwoływać się do LABEL w instrukcjach GO, ponieważ nie znajdują się one w pakiecie wysłanym do SQL
berkeleybross