Добавьте столбец в таблицу, а затем обновите его внутри транзакции

Я создаю скрипт,который будет выполняться на сервере MS SQL. Этот сценарий будет запускать несколько операторов и должен быть транзакционным, если один из операторов завершается неудачно, общее выполнение останавливается и любые изменения откатываются.

У меня возникли проблемы с созданием этой транзакционной модели при выдаче операторов ALTER TABLE для добавления столбцов в таблицу, а затем обновления вновь добавленного столбца. Чтобы сразу получить доступ к недавно добавленному столбцу, я использую команду GO для выполнения оператор ALTER TABLE, а затем вызовите мой оператор UPDATE. Проблема, с которой я сталкиваюсь, заключается в том, что я не могу выдать команду GO внутри оператора IF. Оператор 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 для добавления столбца, а затем обновляя этот столбец, все в сценарии, выполняющемся как транзакционная единица.

6 ответов


GO не является командой T-SQL. Партия разделитель. Клиентский инструмент (SSM, sqlcmd, osql и т. д.) использует его для эффективного вырезать файл при каждом заходе и отправке на сервер отдельных пакетов. Поэтому, очевидно, вы не можете использовать GO inside IF и не можете ожидать, что переменные будут охватывать область между пакетами.

кроме того, вы не можете поймать исключения без проверки на XACT_STATE() чтобы убедиться, что сделка не обречена.

использование GUID для идентификаторов всегда по крайней мере подозрительно.

используя не нулевые ограничения и предоставляя "guid" по умолчанию, как '{00000000-0000-0000-0000-000000000000}' также не может быть правильным.

обновление:

  • разделите ALTER и UPDATE на два пакета.
  • используйте расширения sqlcmd для разрыва сценария при ошибке. Это поддерживается SSMS, когда включен режим sqlcmd, sqlcmd и тривиально поддерживать его в клиентских библиотеках: 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 чтобы проверить наличие столбца, вы можете использовать любой метод, который вам нравится (например. поиск sys.columns).


ортогонально комментариям 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, но это было необходимо сделать условно.


я почти согласен с Remus, но вы можете сделать это с помощью SET XACT_ABORT ON и XACT_STATE

в принципе

  • SET XACT_ABORT ON будет прерывать каждую партию при ошибке и откате
  • каждая партия разделяется GO
  • выполнение переходит к следующему пакету при ошибке
  • Use 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

Я также удалил значение по умолчанию. No value = NULL для значений GUID. Это должно быть уникальным: не пытайтесь установить каждую строку на все нули, потому что это закончится слезами...


вы пробовали это без GO?

обычно вы не должны смешивать изменения таблицы и изменения данных в одном скрипте.


Другой альтернативой, если вы не хотите разбивать код на отдельные пакеты, является использование EXEC для создания вложенной области / пакета как тут


Я думаю, вы можете использовать ";" для завершения и выполнения отдельной команды eachn, а не GO.

обратите внимание, что GO не является частью Transact-SQL:

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