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


71

我正在创建一个将在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语句以添加列,然后更新该列,所有这些都在作为事务单元执行的脚本中进行。


如果仅执行DDL,您的数据库将处于模棱两可或无法维持的状态?您不能先执行DDL,然后再执行DML,如果DDL失败了,您会在DML上捕获任何错误吗?
蒂姆(Tim)2010年

谢谢蒂姆,您的目标正确了!希望我也可以将您的评论作为解决方案。
吉列尔莫·戈麦斯

Answers:


47

GO不是T-SQL命令。是批量定界符。客户端工具(SSM,sqlcmd,osql等)使用它来有效地剪切每个GO处的文件,并将各个批次发送到服务器。因此,显然您不能在IF内使用GO,也不能期望变量跨批处理。

另外,如果不检查XACT_STATE()来确保事务不会失败,就无法捕获异常。

至少将GUID用于ID是可疑的。

使用NOT NULL约束并提供默认的“ guid”,例如 '{00000000-0000-0000-0000-000000000000}'也不正确。

更新:

  • 将ALTER和UPDATE分为两批。
  • 使用sqlcmd扩展来在出错时中断脚本。当sqlcmd模式为on,sqlcmd时,SSMS支持此功能,并且也很容易在客户端库中支持它:dbutilsqlcmd
  • 用于XACT_ABORT强制错误以中断批次。这在维护脚本中经常使用(模式更改)。通常,存储过程和应用程序逻辑脚本改用TRY-CATCH块,但要格外小心:异常处理和嵌套事务

示例脚本:

: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检查列是否存在,可以改用任何您喜欢的方法(例如lookup sys.columns)。


Using GUIDs for IDs is always at least suspicious.这是Asp.Net身份表的PK列的默认值,为什么可疑?
caesay

这很棒:),但不要与powershell invoke-sqlcmd一起使用。如果没有错误,它将正常运行,但是如果出现故障,则不会一切都回滚,并且将进一步执行批处理!
Yepeekai

32

与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,但是必须有条件地执行操作。


这正是我所需要的。谢谢。
Codure

21

我几乎同意Remus,但您可以使用SET XACT_ABORT ON和XACT_STATE来完成此操作

基本上

  • SET XACT_ABORT ON将在错误和ROLLBACK时中止每个批次
  • 每一批用GO分开
  • 执行因错误而跳至下一批
  • 使用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。它的意思是独特的:不要尝试将每一行都设置为全零,因为它将以眼泪结束。


有趣的是,XACT_STATE()用作保持批次间状态的一种手段。不知道红门会这样做。自动化的生成代码可以IF XACT_STATE()在每次批量启动时进行检查,从而防弹,但是我不确定我是否会相信开发人员在整个脚本生命周期中都遵循这一原则。这就是我偏爱的原因:on abort exit,即使它依赖于客户端工具来支持它。
雷木斯·鲁萨努

@Remus Rusanu:Red Gate仅使用SET XACT_ABORT ON和临时表来扩展GO。为了简单起见,使用XACT_STATE()更容易。不过,您对开发人员的纪律是正确的。我注意到您的更新是沿着相同的路线为我的回答太
GBN

2

您是否在没有GO的情况下尝试过?

通常,您不应在同一脚本中混合使用表更改和数据更改。


感谢您的答复。我正在考虑将脚本分成两个部分。一种用于DDL,另一种用于DML。
吉列尔莫·戈麦斯

我最终将脚本分为两个文件,一个DDL和一个DML。这样,我不需要在ALTER语句之后执行。
吉列尔莫·戈麦斯

1
@ggomez:使用SET XACT_ABORT ON,例如Red Gate工具
gbn

谢谢gbn,我包括其中!
吉列尔莫·戈麦斯

1

如果您不想将代码分成多个单独的批次,则另一种选择是使用EXEC来创建嵌套的作用域/批次, 如下所示


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.