TSQL-如何在BEGIN .. END块内使用GO?


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块中创建和更新同一列?



2
@gbn:是的,我知道为什么会发生这种情况(请参阅第二段);但是我不知道如何解决-我真的需要将每个查询变成一堆字符串吗?
BlueRaja-Danny Pflughoeft

@BlueRaja:有什么问题?如果可行,那么到一天结束时,这才是最重要的。如果提供的解决方案存在合法的业务问题,请对此进行说明。关于将每个查询转换为一串字符串是否有特别令人不安的东西?
mellamokb

1
@mellamokb:是的,有问题;如果在任何其他上下文(例如注释或字符串)中使用了单词GO,则该脚本将不起作用。同样,如果发生任何错误,我们将在错误消息中丢失有用的行号。交易没有办法做到这一点吗?还是尝试/捕捉?
BlueRaja-Danny Pflughoeft

@BlueRaja:1)我相信GO它必须自己排成一行,因此您只能搜索这种情况,而不是单词的每个实例GO。2)您始终可以记录成功完成哪些语句。或者,您可以将整个内容包装在try / catch中,并使用一些变量(例如@lineNo)使用自己的行号,您可以跟踪该行号并报告错误。由于您是自动生成这些内容的,因此轻而易举地进行更改。当我认为可以为您的所有担忧找到解决方案时,听起来您似乎根本不想探索这条路线。
mellamokb 2011年

Answers:


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

43

我遇到了同样的问题,最后设法使用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
这是一个很好的解决方案!
巴青加

+1!到目前为止,这是仅在SS 模式脚本(即,主部署脚本)中使用(通过命令)调用其他SS脚本(即,子部署脚本)以及在Statements中使用其中一些调用的实用答案。俄德的,mellamokb的封闭和安迪·乔伊纳的答案在这些发言中呼吁/ - 的都是非首发。另外,如果有一条语句,则- 方法将不起作用(例如,在其之前需要显式)。但是,伙计,“神圣的双重否定,蝙蝠侠!” ;)SQLCMD:rifexecbeginendbeginendcreatego
汤姆(Tom

为了提高可读性(例如,为了帮助克服双负数,并使其更清楚地模拟虚拟 if -block),我将在该块的前面加上-- If whateverComment,在该块上缩进并在后缀一个--end If whateverComment。
汤姆

你救了我的培根!我正在运行合并语句,但那些笨拙的GO不喜欢在IF BEGIN END ELSE内
-Omzig

嗯,我在执行set noexec on后以某种方式收到更新错误?(要更新的列名无效的错误)在查询编辑器中的MSSQL 2014上运行。如果条件变为假,则工作正常(因此noexec保持关闭状态)
杰里(Jerry

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的... stackoverflow.com/questions/4855537/...
GBN

1
@gbn:是的。但是,如果要针对100条语句自动执行此操作,将盲目地将其应用于所有语句将更容易,而不是确定何时何地需要它。
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

这比将每组语句都包装在一个字符串中要好得多,但是仍然不理想。如果有人找到更好的解决方案,请将其发布,我会接受。


6
如果第一个条件是“如果此列不存在”,则块中的第一条语句是“添加此列”,那么对该条件的第二次检查将找到该列,而不执行第二条语句,
Damien_The_Unbeliever

@Damien:是的;幸运的是,在我的情况下这永远不会发生(条件总是检查特定表中的特定值,该表总是作为该IF块的最后一条语句添加)。似乎在SQL中没有很好的方法可以做到这一点。
BlueRaja-Danny Pflughoeft

到目前为止,Mina Jacob的set noexec答案是唯一实用的答案,可用于SS SQLCMD模式脚本(即主部署脚本),该模式通过语句调用(通过:r命令)其他SS脚本(即子部署脚本),其中一些调用在ifStatement中。俄德的,mellamokb的封闭和安迪·乔伊纳的答案在这些发言中exec呼吁/ begin- end的都是非首发。另外,如果有一个Statement ,则begin- end方法将不起作用create(例如,go在其之前需要显式)。
汤姆

8

您可以将语句括在BEGIN和END中,而不能放在它们之间

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

(在罗斯文数据库上测试)

编辑:(可能已在SQL2012上测试)


1
请给出-1的理由
Andy Joiner 2014年

1
不知道为什么它被否决了...对我来说就像一个魅力。
Thorarin 2015年

10
使用SQL Server 2008 R2,这似乎对我不起作用,我仍然收到错误“无效的列名'EMP_IS_ADMIN'”。在UPDATE行上。
MerickOWA 2015年

使用SQL Server 2016对我的BEGIN-END批处理有效。IMO这是最干净的语法。
Uber Schnoz

到目前为止,Mina Jacob的set noexec答案是唯一实用的答案,可用于SS SQLCMD模式脚本(即主部署脚本),该模式通过语句调用(通过:r命令)其他SS脚本(即子部署脚本),其中一些调用在ifStatement中。俄德的,mellamokb的封闭和安迪·乔伊纳的答案在这些发言中exec呼吁/ begin- end的都是非首发。另外,如果有一条语句,则begin- end方法将不起作用create(例如,go在其之前需要显式)。
汤姆

1

您可以尝试以下解决方案:

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

--if upper code not true

ALTER...
GO
UPDATE...
GO

1
如果一个接一个的有多个if-else块,不是很有用,对吗?
杰里(Jerry)

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

您可以合并GOTOLABEL语句以跳过代码,从而使GO关键字完整无缺。


5
似乎无法跨GO语句引用LABEL,因为它们不在发送到SQL的批处理中
berkeleybross 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.