将列添加到表中,然后在事务内部更新它

我正在创build将在MS SQL服务器中运行的脚本。 该脚本将运行多个语句,并且需要是事务性的,如果其中一个语句失败,则整体执行被停止并且任何更改被回滚。

在发出ALTER TABLE语句向表中添加列并更新新添加的列时,我无法创build此事务模型。 为了立即访问新添加的列,我使用GO命令来执行ALTER TABLE语句,然后调用我的UPDATE语句。 我面临的问题是我不能在IF语句中发出GO命令。 IF语句在我的事务模型中很重要。 这是我试图运行的脚本的示例代码。 另外请注意,发出一个GO命令,将会放弃@errorCodevariables,并且在使用之前需要在代码中声明这个variables(这不在下面的代码中)。

BEGIN TRANSACTION DECLARE @errorCode INT SET @errorCode = @@ERROR -- ********************************** -- * Settings -- ********************************** IF @errorCode = 0 BEGIN BEGIN TRY ALTER TABLE Color ADD [CodeID] [uniqueidentifier] NOT NULL DEFAULT ('{00000000-0000-0000-0000-000000000000}') GO END TRY BEGIN CATCH SET @errorCode = @@ERROR END CATCH END IF @errorCode = 0 BEGIN BEGIN TRY UPDATE Color SET CodeID= 'B6D266DC-B305-4153-A7AB-9109962255FC' WHERE [Name] = 'Red' END TRY BEGIN CATCH SET @errorCode = @@ERROR END CATCH END -- ********************************** -- * Check @errorCode to issue a COMMIT or a ROLLBACK -- ********************************** IF @errorCode = 0 BEGIN COMMIT PRINT 'Success' END ELSE BEGIN ROLLBACK PRINT 'Failure' END 

所以我想知道的是如何解决这个问题,发出ALTER TABLE语句来添加一个列,然后更新这个列,所有这些都在作为事务单元执行的脚本中。

GO不是一个T-SQL命令。 是批量分隔符。 客户端工具(SSM,sqlcmd,osql等)使用它在每个GO中有效地剪切文件,并将各个批次发送给服务器。 所以显然你不能在IF中使用GO,也不能期望variables跨批次跨越范围。

而且,如果不检查XACT_STATE()以确保事务没有注定,则无法捕获exception。

使用ID的GUID至less是可疑的。

使用NOT NULL约束并提供像'{00000000-0000-0000-0000-000000000000}'这样的默认“guid '{00000000-0000-0000-0000-000000000000}'也是不正确的。

更新:

  • 将ALTER和UPDATE分成两个批次。
  • 使用sqlcmd扩展来破坏错误的脚本。 当sqlcmd模式处于打开状态时 , SSMS支持这一function,sqlcmd,并且在客户端库中也支持它: dbutilsqlcmd 。
  • 使用XACT_ABORT强制错误中断批处理。 这经常用于维护脚本(模式更改)。 一般来说,存储过程和应用程序逻辑脚本使用TRY-CATCH块,但要小心: exception处理和嵌套事务 。

示例脚本:

 :on error exit set xact_abort on; go begin transaction; go if columnproperty(object_id('Code'), 'ColorId', 'AllowsNull') is null begin alter table Code add ColorId uniqueidentifier null; end go update Code set ColorId = '...' where ... go commit; go 

只有成功的脚本才能到达COMMIT 。 任何错误将中止脚本和回滚。

我用COLUMNPROPERTY检查列存在,你可以使用任何你喜欢的方法(例如,查找sys.columns )。

我几乎同意Remus,但你可以用SET XACT_ABORT ON和XACT_STATE来做到这一点

基本上

  • SET XACT_ABORT ON会在错误和ROLLBACK时中止每个批处理
  • 每个批次都由GO分隔
  • 执行跳转到下一批出错
  • 使用XACT_STATE()将testing事务是否仍然有效

像Red Gate SQL Compare这样的工具使用这种技术

就像是:

 SET XACT_ABORT ON GO BEGIN TRANSACTION GO IF COLUMNPROPERTY(OBJECT_ID('Color'), 'CodeID', ColumnId) IS NULL ALTER TABLE Color ADD CodeID [uniqueidentifier] NULL GO IF XACT_STATE() = 1 UPDATE Color SET CodeID= 'B6D266DC-B305-4153-A7AB-9109962255FC' WHERE [Name] = 'Red' GO IF XACT_STATE() = 1 COMMIT TRAN --else would be rolled back 

我也删除了默认。 对于GUID值,没有值= NULL。 它的意思是独一无二的:不要试图将每行都设置为全零,因为它会以泪结束。

正交Remus的评论,你可以做的是在sp_executesql执行更新。

 ALTER TABLE [Table] ADD [Xyz] NVARCHAR(256); DECLARE @sql NVARCHAR(2048) = 'UPDATE [Table] SET [Xyz] = ''abcd'';'; EXEC sys.sp_executesql @query = @sql; 

创build升级脚本时,我们需要这样做。 通常我们只是使用GO,但是有条件地做事情是必须的。

你没有尝试过没有GO?

通常情况下,您不应该在同一个脚本中混合表格更改和数据更改。

另一种方法是,如果你不想把代码拆分成不同的批处理,就是使用EXEC来创build一个嵌套的作用域/批处理

我想你可以用一个“;” 终止和执行每个单独的命令,而不是GO。

请注意,GO不是Transact-SQL的一部分:

http://msdn.microsoft.com/en-us/library/ms188037.aspx