【问题标题】:TSQL - How to use GO inside of a BEGIN .. END block?TSQL - 如何在 BEGIN .. END 块中使用 GO?
【发布时间】:2011-09-16 15:20:24
【问题描述】:

我正在生成一个脚本,用于将更改从多个开发数据库自动迁移到登台/生产。基本上,它需要一堆更改脚本,并将它们合并为一个脚本,将每个脚本包装在 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 块中创建和更新同一列?

【问题讨论】:

  • @gbn:是的,我知道为什么会发生这种情况(见第二段);但我不知道如何解决它 - 我是否真的需要将每个查询变成一堆字符串!?
  • @BlueRaja:有什么问题?如果它有效,那么这就是一天结束时最重要的事情。如果所提供的解决方案存在合法的业务问题,请表达出来。将每个查询转换成一堆字符串有什么特别令人不安的地方吗?
  • @mellamokb:是的,有问题;如果在任何其他上下文(例如注释或字符串)中使用了单词 GO,则脚本将不起作用。此外,如果出现任何问题,我们会在错误消息中丢失有用的行号。有没有办法通过交易来做到这一点?还是尝试/抓住?
  • @BlueRaja: 1) 我相信GO 必须单独在一行中,因此您只能搜索这种情况,而不是搜索单词GO 的每个实例。 2)您始终可以记录哪些语句已成功完成。或者您可以将整个内容包装在 try/catch 中,并使用您自己的行号,使用您跟踪的某个变量(如 @lineNo)并报告错误。由于您是自动生成这些,因此进行这样的更改应该是轻而易举的事。当我认为可以找到解决您所有问题的解决方案时,听起来您显然不想探索这条路线。

标签: sql sql-server tsql sql-server-2008


【解决方案1】:

我遇到了同样的问题,最后设法使用 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; 

【讨论】:

  • 这是一个很好的解决方案!
  • +1!这是迄今为止唯一的实用答案,可用于调用(通过:r 命令)其他 SS 脚本(即子部署脚本)的 SS SQLCMD 模式脚本(即主部署脚本) ) 与 if 语句中的一些调用。 Oded、mellamokb 和 Andy Joiner 将 all 这些语句包含在 exec 呼叫/begin-end 中的答案是非首发。此外,如果有 create 语句,begin-end 方法将不起作用(例如,在它之前需要显式的 go)。但是,伙计,“神圣的双重否定,蝙蝠侠!” ;)
  • 为了可读性(例如,为了帮助克服双重否定并使其更清楚地表明它正在模拟 virtual if-block),我会在该块前面加上一个-- If whatever 注释,缩进块并用--end If whatever 注释为块添加后缀。
  • 你救了我的培根!我正在运行合并语句,而那些愚蠢的 GO 不喜欢在 IF BEGIN END ELSE 中
  • 嗯,我在执行 set noexec on 后不知何故收到了更新错误? (要更新的列名无效的错误)在查询编辑器中的 MSSQL 2014 上运行。如果条件变为假,则工作正常(因此 noexec 保持关闭)
【解决方案2】:

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

【讨论】:

  • 是的,我明白这一点。这不能回答问题 - 我需要在同一 IF 块中创建和更新一列。
  • @Oded:; 会在这里帮忙吗? - 你刚刚编辑了你的答案:o)
  • @Neil - 这是我的想法,是的。
  • ; 也不起作用 - 解析器仍然给我 "Invalid column name 'EMP_IS_ADMIN'."
  • 批量编译时,EMP_IS_ADMIN不存在。 stackoverflow.com/questions/4855537/…
【解决方案3】:

您可以尝试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:我已经更新了这个例子来展示它是如何工作的。这些字符串可以是多行的,只要其中包含的任何单引号 (') 使用双单引号 ('') 进行转义
  • @mellamokb: 严格来说,只有 UPDATE 需要 sp_executesql ...stackoverflow.com/questions/4855537/…
  • @gbn:是的。但是,如果您要为 100 条语句自动执行此操作,那么在所有语句中盲目地应用它会更容易,而不是决定何时何地需要它。
  • @gbn @mellamokb:我的意思是像SELECT * <newline> FROM whatever 这样的声明。如果我用自己的 EXEC 语句执行每一行,那将会中断。或者你是建议我在每一个GO 声明中休息?
【解决方案4】:

您可以将语句括在 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的理由
  • 不知道为什么它被否决...对我来说就像一个魅力。
  • 使用 SQL Server 2008 R2,这似乎对我不起作用,我仍然收到错误“无效的列名 'EMP_IS_ADMIN'。”在更新行。
  • BEGIN-END 批处理使用 SQL Server 2016 为我工作。IMO 这是最简洁的语法。
  • Mina Jacob 的set noexec 答案是迄今为止唯一实用 答案用于调用(通过@ 987654324@ 命令)其他 SS 脚本(即子部署脚本),其中一些调用位于if 语句中。 Oded、mellamokb 和 Andy Joiner 将 all 这些语句包含在 exec 呼叫/begin-end 中的答案是非首发。此外,begin-end 方法如果有 create 语句将不起作用(例如,在它之前需要显式的 go)。
【解决方案5】:

我最终通过将 GO 的每个实例替换为自己的行来使其工作

END
GO

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

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

【讨论】:

  • 如果第一个条件是“如果该列不存在”,则块中的第一条语句是“添加该列”,那么条件的第二个检查将找到该列而不执行第二个语句,
  • @Damien:是的;幸运的是,这在我的情况下永远不会发生(条件始终是检查特定表中的特定值,它总是作为IF 块的最后一条语句添加)。似乎在 SQL 中没有很好的方法来做到这一点。
  • Mina Jacob 的set noexec 答案是迄今为止唯一实用 的答案,可用于调用(通过@ 987654326@ 命令)其他 SS 脚本(即子部署脚本),其中一些调用位于 if 语句中。 Oded、mellamokb 和 Andy Joiner 将 all 这些语句包含在 exec 呼叫/begin-end 中的答案是非首发。此外,begin-end 方法如果有 create 语句将不起作用(例如,在它之前需要显式的 go)。
【解决方案6】:

你可以试试这个解决方案:

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

--if upper code not true

ALTER...
GO
UPDATE...
GO

【讨论】:

  • 如果你有几个 if-else 块一个接一个,这不是很有用,对吧?
【解决方案7】:

我过去曾为此使用过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

【讨论】:

    【解决方案8】:

    您可以合并GOTOLABEL 语句来跳过代码,从而保持GO 关键字不变。

    【讨论】:

    • 似乎不能跨 GO 语句引用 LABEL,因为它们不在发送到 SQL 的批处理中
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-10
    • 1970-01-01
    • 2012-12-04
    • 2011-05-12
    相关资源
    最近更新 更多