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