Добавить столбец в таблицу, а затем обновить его внутри транзакции - PullRequest
63 голосов
/ 14 декабря 2010

Я создаю скрипт, который будет запускаться на сервере 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 ]

42 голосов
/ 14 декабря 2010

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

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

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

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

Обновлено:

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

20 голосов
/ 05 ноября 2014

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

18 голосов
/ 14 декабря 2010

Я почти согласен с 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

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

2 голосов
/ 14 декабря 2010

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

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

1 голос
/ 17 ноября 2014

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

0 голосов
/ 14 декабря 2010

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

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

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

...