SQL Server中使用Begin / End块和Go关键字的方法?

126

在 SQL Server 中,何时使用 BEGINEND 关键字的准则是什么?

此外,GO 关键字具体是做什么用的?

6个回答

140

GO就像脚本的结尾。

你可以有多个CREATE TABLE语句,它们之间用GO分隔。这是一种将脚本的不同部分隔离开来但又可以作为一个块提交的方法。


BEGIN和END就像C / ++ / #、Java等中的{和}一样。

它们将一段逻辑代码绑定在一起。我倾向于在存储过程的开头和结尾使用BEGIN和END,但在那里并非严格必要。在需要循环,IF语句等多步操作时,则必须使用BEGIN和END。

IF EXISTS (SELECT * FROM my_table WHERE id = @id)
BEGIN
   INSERT INTO Log SELECT @id, 'deleted'
   DELETE my_table WHERE id = @id
END

你试过创建一个没有 BEGIN 和 END 的存储过程吗?如果我没记错的话,只有第一行包含在存储过程中,其余的就直接在那里执行了... - cjk
2
从 SQL Server 2000 开始,我的经验与此完全不同。 - MatBailie
2
BEGIN和END是否也定义了新的作用域? - samus
3
是的,任何在外部声明的内容都可以在内部被看到,但是任何在内部声明的内容将在结尾时超出作用域。 - MatBailie
@cjk 为什么我们应该使用go语句来隔离查询?所有命令同时运行而不是分开运行有什么问题吗?! - user10997800

46
你需要使用BEGINEND来创建跨越多个语句的块。因此,如果您想在IF语句的一个“段”中执行2个操作,或者如果您想在WHILE循环的主体中执行多个操作,则需要用BEGIN...END括起这些语句。 GO关键字不是SQL的一部分。它仅由查询分析器用于将脚本划分为独立执行的“批次”。

33

在SQL Server中,GO不是一个关键字,而是批处理分隔符。GO结束了一组语句的执行。当你使用类似于SQLCMD的工具时,这一点尤其有用。想象一下,在命令行上输入SQL语句时,您不一定希望每次结束语句时都执行语句,因此SQL Server不会执行任何操作,直到您输入"GO"为止。

同样,在批处理开始之前,通常需要某些对象可见。例如,假设你正在创建一个数据库然后查询它。你不能写:

CREATE DATABASE foo;
USE foo;
CREATE TABLE bar;

因为执行CREATE TABLE的批处理中不存在foo。您需要这样做:

CREATE DATABASE foo;
GO
USE foo;
CREATE TABLE bar;

14

其他人已经很好地回答了BEGIN和END。

正如Gary所指出的那样,GO是一个批处理分隔符,由大多数微软提供的客户端工具使用,比如isql、sqlcmd、查询分析器和SQL Server管理工作室。(至少一些工具允许更改批处理分隔符。我从未见过更改批处理分隔符的用途。)

要回答何时使用GO的问题,就需要知道何时将SQL分隔成批处理。

有些语句必须是批处理的第一条语句。

select 1
create procedure #Zero as
    return 0

在SQL Server 2000上,错误信息为:

Msg 111, Level 15, State 1, Line 3
'CREATE PROCEDURE' must be the first statement in a query batch.
Msg 178, Level 15, State 1, Line 4
A RETURN statement with a return value cannot be used in this context.

在 SQL Server 2005 中,错误消息不够明确:

Msg 178, Level 15, State 1, Procedure #Zero, Line 5
A RETURN statement with a return value cannot be used in this context.

因此,请使用GO来分隔必须作为批处理的开头的语句和在脚本中先于它之前的语句。

运行脚本时,许多错误会导致批处理停止执行,但客户端将简单地发送下一批处理,脚本的执行不会停止。我经常在测试中使用这种方法。我将以begin transaction开始脚本,以rollback结束脚本,在中间进行所有测试:

begin transaction
go
... test code here ...
go
rollback transaction
那样做的好处是,即使测试代码中发生错误,事务的开始和回滚语句仍然会在单独的批次中发生,使我始终返回到起始状态。如果它们不在单独的批次中,则语法错误将阻止“begin transaction”发生,因为一个批次被解析为一个单位。而运行时错误会防止回滚发生。此外,如果您正在执行安装脚本,并且在一个文件中有多个批次,一个批次中的错误不会阻止脚本继续运行,这可能会留下混乱(安装之前始终备份)。与Dave Markel指出的相关问题是,在批处理中创建对象之前,SQL Server在数据字典中查找对象时,解析可能会失败,但解析可以在运行任何语句之前发生。有时这是一个问题,有时不是。我想不出一个好的例子。但是,如果您曾经遇到过“X不存在”的错误,尽管显然该对象会存在于该语句中断成批次。最后注意一点,事务可以跨越批次进行,但变量不能跨越批次。
declare @i int
set @i = 0
go
print @i

Msg 137, Level 15, State 2, Line 1
Must declare the scalar variable "@i".

1
这正是我需要的,谢谢:“事务可以跨批次。变量不跨批次。” - Gary

5

GO用于结束一批操作,通常情况下你不需要在代码中使用它。请注意,如果你在存储过程中使用它,在执行存储过程时,GO之后的代码将不会被执行。

BEGIN和END用于处理有多行代码的过程类型语句。你需要它们来处理WHILE循环和游标(当然,如果可能的话,你会尽量避免使用游标),以及IF语句(严格地说,如果IF语句只有一行代码,你不需要它们,但是如果你在IF之后总是加上它们,那么代码更容易维护)。CASE语句也使用END,但没有BEGIN。


GO关键字后的代码是否会被存储到存储过程中?CREATE或ALTER语句是否会像GO之后没有代码一样进行处理?然后,GO关键字后的代码将被执行,就好像它是自己的脚本一样? - MatBailie
游标与此有何关系? - Gary McGill

3
在今天解决这个问题之后,我的看法是:BEGIN...END括号代码就像C语言中的{....}一样,例如if...else和循环的代码块。
在前面的语句定义了一个对象,而后续语句依赖于该对象时,必须使用GO。上面的USE数据库是一个很好的例子,但以下情况也会让你感到困扰:
alter table foo add bar varchar(8);
-- if you don't put GO here then the following line will error as it doesn't know what bar is.
update foo set bar = 'bacon';
-- need a GO here to tell the interpreter to execute this statement, otherwise the Parser will lump it together with all successive statements.

似乎问题出在这里:与Oracle不同,SQL Server SQL解析器无法意识到你在第一行定义了一个新符号,并且在接下来的行中引用它是可以的。直到遇到GO标记时,它才“看到”该符号,因为GO告诉它执行从上一个GO以来的SQL,此时该符号被应用于数据库并对解析器可见。
我不知道为什么它不只是将分号视为语义分隔符,并单独应用语句,但我希望它能这样做。唯一的好处是,你可以在GO之前放置一个print()语句,如果任何语句失败,该print将不会执行。虽然只有微小的收益,但这也是许多麻烦。

你是正确的,我对以下语句有问题: 如果列存在,则开始创建列;更新记录;结束。 有人知道如何解决吗? - ADM-IT

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接