TSQL - Как использовать GO внутри блока BEGIN .. END?

96

Я создаю сценарий для автоматического переноса изменений из нескольких баз данных разработки в промежуточную / производственную. По сути, он берет кучу сценариев изменений и объединяет их в один сценарий, заключая каждый сценарий в IF whatever BEGIN ... ENDоператор.

Однако для некоторых сценариев требуется GOоператор, чтобы, например, синтаксический анализатор SQL знал о новом столбце после его создания.

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

Однако, как только я заключу это в IFблок:

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

Это не удается, потому что я отправляю BEGINбез соответствия END. Однако, если я удалю, GOон снова жалуется на неизвестный столбец.

Есть ли способ создать и обновить один и тот же столбец в одном IFблоке?

BlueRaja - Дэнни Пфлугофт
источник
2
См stackoverflow.com/questions/4855537/... пожалуйста
ГБН
2
@gbn: Да, я понимаю, почему это происходит (см. второй абзац) ; но я понятия не имею, как это обойти - действительно ли мне нужно превращать каждый запрос в кучу строк !?
BlueRaja - Danny Pflughoeft
@BlueRaja: Что вас беспокоит? Если это сработает, это все, что имеет значение в конце концов. Если с предоставленным решением возникла законная бизнес-проблема, сообщите об этом. Есть ли что-то особенно неприятное в преобразовании каждого запроса в набор строк?
mellamokb
1
@mellamokb: Да, проблема; если слово GO используется в любом другом контексте (например, в комментарии или строке), сценарий не будет работать. Кроме того, мы теряем полезные номера строк в сообщениях об ошибках, если что-то пойдет не так. Нет возможности сделать это с транзакциями? Или попробовать / поймать?
BlueRaja - Дэнни Пфлугофт
@BlueRaja: 1) Я считаю, что GOон должен быть в отдельной строке, поэтому вы можете искать только этот регистр, а не каждое вхождение слова GO. 2) Вы всегда можете записать, какие операторы были выполнены успешно. Или вы можете обернуть все это в try / catch и использовать свои собственные номера строк, используя некоторую переменную, например @lineNo, которую вы отслеживаете, и сообщать об ошибке. Поскольку вы создаете их автоматически, внесение таких изменений должно быть легким делом. Просто звучит так, будто вы просто не хотите исследовать этот маршрут, хотя я думаю, что есть решения, которые можно найти для всех ваших проблем.
mellamokb

Ответы:

45

У меня была такая же проблема, и мне, наконец, удалось решить ее с помощью 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
Это отличное решение!
Bazinga
+1! На данный момент это ЕДИНСТВЕННЫЙ практический ответ для использования в SQLCMDсценарии режима SS (т. Е. В основном сценарии развертывания), который вызывает (через :rкоманду) другие сценарии SS (то есть сценарии дополнительного развертывания) с некоторыми из этих вызовов внутри ifоператоров. Ответы Одеда, мелламокба и Энди Джойнера о включении всех этих утверждений в execвызовы / begin- не endявляются стартовыми. Кроме того, метод begin- endне будет работать, если есть оператор create(например, требует явного goперед ним). Но, чувак, "Святые двойные негативы, Бэтмен!" ;)
Tom
Для удобочитаемости (например, чтобы помочь преодолеть двойное отрицание и прояснить, что он имитирует виртуальный if -блок), я бы поставил перед блоком -- If whateverкомментарий, сделал бы отступ и закрепил блок --end If whateverкомментарием.
Tom
Ты спас мой бекон! Я запускал операторы слияния, и эти тупые GO не любят находиться внутри IF BEGIN END ELSE
Omzig
Хм, у меня как-то возникает ошибка обновления после выполнения set noexec on? (ошибка из-за недопустимого имени столбца для обновления) Выполняется на MSSQL 2014 в редакторе запросов. Работает нормально, если условие становится ложным (таким образом, noexec остается выключенным)
Джерри
43

GO не является SQL - это просто разделитель пакетов, используемый в некоторых инструментах MS SQL.

Если вы не используете это, вам необходимо убедиться, что операторы выполняются отдельно - либо в разных пакетах, либо с использованием динамического SQL для населения (спасибо @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
Да, я это понимаю. Это не отвечает на вопрос - мне нужно создать и обновить столбец в том же IFблоке.
BlueRaja - Danny Pflughoeft
@Oded: Здесь ;поможет? - Вы только что отредактировали свой ответ: o)
Нил Найт
@Neil - это мои мысли, да.
Oded
;тоже не работает - синтаксический анализатор по-прежнему выдает мне «Неверное имя столбца 'EMP_IS_ADMIN'».
BlueRaja - Danny Pflughoeft
Когда пакет скомпилирован, EMP_IS_ADMIN не существует. stackoverflow.com/questions/4855537/…
gbn
16

Вы можете попробовать sp_executesqlразделить содержимое каждого GOоператора на отдельную строку для выполнения, как показано в примере ниже. Кроме того, существует переменная @statementNo, чтобы отслеживать, какой оператор выполняется, для упрощения отладки, где произошло исключение. Номера строк будут относиться к началу номера соответствующего оператора, вызвавшего ошибку.

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

Вы также можете легко выполнять многострочные операторы, как показано в примере выше, просто заключив их в одинарные кавычки ( '). Не забывайте экранировать одинарные кавычки внутри строки двойными одинарными кавычками ( '') при создании скриптов.

мелламокб
источник
Не думаете, что это сработает для команд, разделенных на несколько строк, не так ли?
BlueRaja - Danny Pflughoeft
@BlueRaja: Я обновил пример, чтобы показать, как это будет работать. Эти строки могут быть многострочными, если любые одинарные кавычки ('), содержащиеся внутри, экранируются с помощью двойной одинарной кавычки (' ')
mellamokb
1
@mellamokb: строго говоря, sp_executesql нужен только для UPDATE ... stackoverflow.com/questions/4855537/…
gbn
1
@gbn: Верно. Но если вы собираетесь автоматизировать это для сотен операторов, будет проще применить его вслепую ко всем операторам, вместо того, чтобы решать, когда и где вам это нужно.
mellamokb
@gbn @mellamokb: Я имел в виду такие утверждения, как SELECT * <newline> FROM whatever. Если я выполню каждую строку с ее собственным оператором EXEC, это сломается. Или вы предлагаете мне ломать каждое GOзаявление?
BlueRaja - Danny Pflughoeft
9

В конечном итоге я заставил его работать, заменив каждый экземпляр в GOотдельной строке на

END
GO

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

Это намного предпочтительнее, чем заключать каждую группу операторов в строку, но все же далеко от идеала. Если кто-нибудь найдет лучшее решение, опубликуйте его, и я приму его.

BlueRaja - Дэнни Пфлугофт
источник
6
Если первым условным условием является «если этот столбец не существует», первым оператором в блоке будет «добавить этот столбец», тогда вторая проверка условного условия найдет столбец и не выполнит второй оператор,
Damien_The_Unbeliever,
@ Дэмиен: Верно; к счастью, в моем случае этого никогда не произойдет (условное выражение - это всегда проверка определенного значения в определенной таблице, которая всегда добавляется как последний оператор IFблока). Кажется, что в SQL просто нет хорошего способа сделать это.
BlueRaja - Дэнни Пфлугхёфт
set noexecОтвет Мины Джейкоб до сих пор является ЕДИНСТВЕННЫМ практическим ответом для использования в SQLCMDсценарии режима SS (т. Е. В основном сценарии развертывания), который вызывает (через :rкоманду) другие сценарии SS (т.е. сценарии дополнительного развертывания) с некоторыми из этих вызовов внутри ifоператоров. Ответы Одеда, мелламокба и Энди Джойнера о включении всех этих утверждений в execCalls / begin- не endявляются стартовыми. Кроме того, метод begin- endне будет работать, если есть оператор create(например, требует явного goперед ним).
Tom
8

Вы можете заключить инструкции в BEGIN и END вместо GO между

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

(Проверено в базе данных Northwind)

Изменить: (Вероятно, протестировано на SQL2012)

Энди Джойнер
источник
1
Пожалуйста, объясните причину -1
Энди Джойнер
1
Не знаю, почему он был отклонен ... для меня работает как шарм.
Торарин,
10
При использовании SQL Server 2008 R2 это, похоже, не работает для меня, я все равно получаю сообщение об ошибке «Недопустимое имя столбца« EMP_IS_ADMIN »». в строке ОБНОВЛЕНИЕ.
MerickOWA,
Пакетная обработка BEGIN-END у меня сработала с использованием SQL Server 2016. ИМО, это самый чистый синтаксис.
Uber Schnoz,
set noexecОтвет Мины Джейкоб до сих пор является ЕДИНСТВЕННЫМ практическим ответом для использования в SQLCMDсценарии режима SS (т. Е. В основном сценарии развертывания), который вызывает (через :rкоманду) другие сценарии SS (т.е. сценарии дополнительного развертывания) с некоторыми из этих вызовов внутри ifоператоров. Ответы Одеда, мелламокба и Энди Джойнера о включении всех этих утверждений в execCalls / begin- не endявляются стартовыми. Кроме того, метод begin- endне будет работать, если есть оператор create(например, требует явного goперед ним).
Tom
1

Вы можете попробовать это решение:

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

--if upper code not true

ALTER...
GO
UPDATE...
GO
Лук
источник
1
Не очень полезно, если у вас есть несколько блоков if-else один за другим, верно?
Джерри
0

Я использовал RAISERRORв прошлом для этого

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

Вы можете включить операторы GOTOи, LABELчтобы пропустить код, оставив GOключевые слова нетронутыми.

Джим
источник
5
Похоже, что на LABEL нельзя ссылаться в операторах GO, поскольку они не входят в пакет, отправляемый в SQL,
Берклибросс,