我正在创建一个将在MS SQL服务器中运行的脚本。此脚本将运行多个语句,并且需要是事务性的,如果其中一个语句失败,则停止整体执行并回滚任何更改。
在发出ALTER TABLE语句以向表中添加列然后更新新添加的列时,我无法创建此事务模型。为了立即访问新添加的列,我使用GO命令执行ALTER TABLE语句,然后调用我的UPDATE语句。我面临的问题是我无法在IF语句中发出GO命令。 IF语句在我的事务模型中很重要。这是我尝试运行的脚本的示例代码。另请注意,发出GO命令会丢弃@errorCode变量,并且需要在使用之前在代码中声明(这不在下面的代码中)。
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语句以添加列然后更新该列,所有这些都在作为事务单元执行的脚本中。
答案 0 :(得分:42)
GO不是T-SQL命令。是批处理分隔符。客户端工具(SSM,sqlcmd,osql等)使用它在每个GO上有效地剪切文件,并将各个批次发送给服务器。显然你不能在IF中使用GO,也不能期望变量跨越批次范围。
此外,如果不检查XACT_STATE()
以确保交易没有注定,您就无法捕获异常。
对ID使用GUID始终至少是可疑的。
使用NOT NULL约束并提供'{00000000-0000-0000-0000-000000000000}'
这样的默认'guid'也不正确。
更新:
XACT_ABORT
强制错误中断批处理。这经常用于维护脚本(架构更改)。存储过程和应用程序逻辑脚本通常使用TRY-CATCH块,但要小心谨慎:Exception handling and nested transactions。示例脚本:
: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
)。
答案 1 :(得分:19)
与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;
创建升级脚本时我们需要这样做。通常我们只使用GO,但有必要有条件地做事。
答案 2 :(得分:18)
我几乎同意Remus但你可以用SET XACT_ABORT ON和XACT_STATE
来做到这一点基本上
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。它的意思是独一无二的:不要试图将每一行设置为全零,因为它会以泪水结束......
答案 3 :(得分:2)
你没试过GO吗?
通常,您不应在同一脚本中混合表更改和数据更改。
答案 4 :(得分:1)
另一种选择,如果您不想将代码拆分为单独的批次,则使用EXEC创建嵌套范围/批处理 as here
答案 5 :(得分:-1)
我认为你可以使用“;”终止并执行每个单独的命令,而不是GO。
请注意,GO不是Transact-SQL的一部分: