将列添加到表中,然后在事务内部更新它
我正在创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的一部分: