Sql откат транзакции при ошибке

Привет, Хабр! Представляю вашему вниманию перевод статьи «Error and Transaction Handling in SQL Server. Part One – Jumpstart Error Handling» автора Erland Sommarskog.

1. Введение

Эта статья – первая в серии из трёх статей, посвященных обработке ошибок и транзакций в SQL Server. Её цель – дать вам быстрый старт в теме обработки ошибок, показав базовый пример, который подходит для большей части вашего кода. Эта часть написана в расчете на неопытного читателя, и по этой причине я намеренно умалчиваю о многих деталях. В данный момент задача состоит в том, чтобы рассказать как без упора на почему. Если вы принимаете мои слова на веру, вы можете прочесть только эту часть и отложить остальные две для дальнейших этапов в вашей карьере.

С другой стороны, если вы ставите под сомнение мои рекомендации, вам определенно необходимо прочитать две остальные части, где я погружаюсь в детали намного более глубоко, исследуя очень запутанный мир обработки ошибок и транзакций в SQL Server. Вторая и третья части, так же, как и три приложения, предназначены для читателей с более глубоким опытом. Первая статья — короткая, вторая и третья значительно длиннее.

Все статьи описывают обработку ошибок и транзакций в SQL Server для версии 2005 и более поздних версий.

1.1 Зачем нужна обработка ошибок?

Почему мы обрабатываем ошибки в нашем коде? На это есть много причин. Например, на формах в приложении мы проверяем введенные данные и информируем пользователей о допущенных при вводе ошибках. Ошибки пользователя – это предвиденные ошибки. Но нам также нужно обрабатывать непредвиденные ошибки. То есть, ошибки могут возникнуть из-за того, что мы что-то упустили при написании кода. Простой подход – это прервать выполнение или хотя бы вернуться на этап, в котором мы имеем полный контроль над происходящим. Недостаточно будет просто подчеркнуть, что совершенно непозволительно игнорировать непредвиденные ошибки. Это недостаток, который может вызвать губительные последствия: например, стать причиной того, что приложение будет предоставлять некорректную информацию пользователю или, что еще хуже, сохранять некорректные данные в базе. Также важно сообщать о возникновении ошибки с той целью, чтобы пользователь не думал о том, что операция прошла успешно, в то время как ваш код на самом деле ничего не выполнил.

Мы часто хотим, чтобы в базе данных изменения были атомарными. Например, задача по переводу денег с одного счета на другой. С этой целью мы должны изменить две записи в таблице CashHoldings и добавить две записи в таблицу Transactions. Абсолютно недопустимо, чтобы ошибки или сбой привели к тому, что деньги будут переведены на счет получателя, а со счета отправителя они не будут списаны. По этой причине обработка ошибок также касается и обработки транзакций. В приведенном примере нам нужно обернуть операцию в BEGIN TRANSACTION и COMMIT TRANSACTION, но не только это: в случае ошибки мы должны убедиться, что транзакция откачена.

2. Основные команды

Мы начнем с обзора наиболее важных команд, которые необходимы для обработки ошибок. Во второй части я опишу все команды, относящиеся к обработке ошибок и транзакций.

2.1 TRY-CATCH

Основным механизмом обработки ошибок является конструкция TRY-CATCH, очень напоминающая подобные конструкции в других языках. Структура такова:

BEGIN TRY
   <обычный код>
END TRY
BEGIN CATCH
   <обработка ошибок>
END CATCH

Если какая-либо ошибка появится в <обычный код>, выполнение будет переведено в блок CATCH, и будет выполнен код обработки ошибок.

Как правило, в CATCH откатывают любую открытую транзакцию и повторно вызывают ошибку. Таким образом, вызывающая клиентская программа понимает, что что-то пошло не так. Повторный вызов ошибки мы обсудим позже в этой статье.

Вот очень быстрый пример:

BEGIN TRY
   DECLARE @x int
   SELECT @x = 1/0
   PRINT 'Not reached'
END TRY
BEGIN CATCH 
   PRINT 'This is the error: ' + error_message()
END CATCH

Результат выполнения: This is the error: Divide by zero error encountered.

Мы вернемся к функции error_message() позднее. Стоит отметить, что использование PRINT в обработчике CATCH приводится только в рамках экспериментов и не следует делать так в коде реального приложения.

Если <обычный код> вызывает хранимую процедуру или запускает триггеры, то любая ошибка, которая в них возникнет, передаст выполнение в блок CATCH. Если более точно, то, когда возникает ошибка, SQL Server раскручивает стек до тех пор, пока не найдёт обработчик CATCH. И если такого обработчика нет, SQL Server отправляет сообщение об ошибке напрямую клиенту.

Есть одно очень важное ограничение у конструкции TRY-CATCH, которое нужно знать: она не ловит ошибки компиляции, которые возникают в той же области видимости. Рассмотрим пример:

CREATE PROCEDURE inner_sp AS
   BEGIN TRY
      PRINT 'This prints'
      SELECT * FROM NoSuchTable
      PRINT 'This does not print'
   END TRY
   BEGIN CATCH
      PRINT 'And nor does this print'
   END CATCH
go
EXEC inner_sp

Выходные данные:

This prints
Msg 208, Level 16, State 1, Procedure inner_sp, Line 4
Invalid object name 'NoSuchTable'

Как можно видеть, блок TRY присутствует, но при возникновении ошибки выполнение не передается блоку CATCH, как это ожидалось. Это применимо ко всем ошибкам компиляции, таким как пропуск колонок, некорректные псевдонимы и тому подобное, которые возникают во время выполнения. (Ошибки компиляции могут возникнуть в SQL Server во время выполнения из-за отложенного разрешения имен – особенность, благодаря которой SQL Server позволяет создать процедуру, которая обращается к несуществующим таблицам.)

Эти ошибки не являются полностью неуловимыми; вы не можете поймать их в области, в которой они возникают, но вы можете поймать их во внешней области. Добавим такой код к предыдущему примеру:

CREATE PROCEDURE outer_sp AS
   BEGIN TRY
      EXEC inner_sp
   END TRY
   BEGIN CATCH
      PRINT 'The error message is: ' + error_message()
   END CATCH
go
EXEC outer_sp

Теперь мы получим на выходе это:

This prints
The error message is: Invalid object name 'NoSuchTable'.

На этот раз ошибка была перехвачена, потому что сработал внешний обработчик CATCH.

2.2 SET XACT_ABORT ON

В начало ваших хранимых процедур следует всегда добавлять это выражение:

SET XACT_ABORT, NOCOUNT ON

Оно активирует два параметра сессии, которые выключены по умолчанию в целях совместимости с предыдущими версиями, но опыт доказывает, что лучший подход – это иметь эти параметры всегда включенными. Поведение SQL Server по умолчанию в той ситуации, когда не используется TRY-CATCH, заключается в том, что некоторые ошибки прерывают выполнение и откатывают любые открытые транзакции, в то время как с другими ошибками выполнение последующих инструкций продолжается. Когда вы включаете XACT_ABORT ON, почти все ошибки начинают вызывать одинаковый эффект: любая открытая транзакция откатывается, и выполнение кода прерывается. Есть несколько исключений, среди которых наиболее заметным является выражение RAISERROR.

Параметр XACT_ABORT необходим для более надежной обработки ошибок и транзакций. В частности, при настройках по умолчанию есть несколько ситуаций, когда выполнение может быть прервано без какого-либо отката транзакции, даже если у вас есть TRY-CATCH. Мы видели такой пример в предыдущем разделе, где мы выяснили, что TRY-CATCH не перехватывает ошибки компиляции, возникшие в той же области. Открытая транзакция, которая не была откачена из-за ошибки, может вызвать серьезные проблемы, если приложение работает дальше без завершения транзакции или ее отката.

Для надежной обработки ошибок в SQL Server вам необходимы как TRY-CATCH, так и SET XACT_ABORT ON. Среди них инструкция SET XACT_ABORT ON наиболее важна. Если для кода на промышленной среде только на нее полагаться не стоит, то для быстрых и простых решений она вполне подходит.

Параметр NOCOUNT не имеет к обработке ошибок никакого отношения, но включение его в код является хорошей практикой. NOCOUNT подавляет сообщения вида (1 row(s) affected), которые вы можете видеть в панели Message в SQL Server Management Studio. В то время как эти сообщения могут быть полезны при работе c SSMS, они могут негативно повлиять на производительность в приложении, так как увеличивают сетевой трафик. Сообщение о количестве строк также может привести к ошибке в плохо написанных клиентских приложениях, которые могут подумать, что это данные, которые вернул запрос.

Выше я использовал синтаксис, который немного необычен. Большинство людей написали бы два отдельных выражения:

SET NOCOUNT ON
SET XACT_ABORT ON

Между ними нет никакого отличия. Я предпочитаю версию с SET и запятой, т.к. это снижает уровень шума в коде. Поскольку эти выражения должны появляться во всех ваших хранимых процедурах, они должны занимать как можно меньше места.

3. Основной пример обработки ошибок

После того, как мы посмотрели на TRY-CATCH и SET XACT_ABORT ON, давайте соединим их вместе в примере, который мы можем использовать во всех наших хранимых процедурах. Для начала я покажу пример, в котором ошибка генерируется в простой форме, а в следующем разделе я рассмотрю решения получше.

Для примера я буду использовать эту простую таблицу.

CREATE TABLE sometable(a int NOT NULL,
                       b int NOT NULL,
                       CONSTRAINT pk_sometable PRIMARY KEY(a, b))

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

CREATE PROCEDURE insert_data @a int, @b int AS 
   SET XACT_ABORT, NOCOUNT ON
   BEGIN TRY
      BEGIN TRANSACTION
      INSERT sometable(a, b) VALUES (@a, @b)
      INSERT sometable(a, b) VALUES (@b, @a)
      COMMIT TRANSACTION
   END TRY
   BEGIN CATCH
      IF @@trancount > 0 ROLLBACK TRANSACTION
      DECLARE @msg nvarchar(2048) = error_message()  
      RAISERROR (@msg, 16, 1)
      RETURN 55555
   END CATCH

Первая строка в процедуре включает XACT_ABORT и NOCOUNT в одном выражении, как я показывал выше. Эта строка – единственная перед BEGIN TRY. Все остальное в процедуре должно располагаться после BEGIN TRY: объявление переменных, создание временных таблиц, табличных переменных, всё. Даже если у вас есть другие SET-команды в процедуре (хотя причины для этого встречаются редко), они должны идти после BEGIN TRY.

Причина, по которой я предпочитаю указывать SET XACT_ABORT и NOCOUNT перед BEGIN TRY, заключается в том, что я рассматриваю это как одну строку шума: она всегда должна быть там, но я не хочу, чтобы это мешало взгляду. Конечно же, это дело вкуса, и если вы предпочитаете ставить SET-команды после BEGIN TRY, ничего страшного. Важно то, что вам не следует ставить что-либо другое перед BEGIN TRY.

Часть между BEGIN TRY и END TRY является основной составляющей процедуры. Поскольку я хотел использовать транзакцию, определенную пользователем, я ввел довольно надуманное бизнес-правило, в котором говорится, что если вы вставляете пару, то обратная пара также должна быть вставлена. Два выражения INSERT находятся внутри BEGIN и COMMIT TRANSACTION. Во многих случаях у вас будет много строк кода между BEGIN TRY и BEGIN TRANSACTION. Иногда у вас также будет код между COMMIT TRANSACTION и END TRY, хотя обычно это только финальный SELECT, возвращающий данные или присваивающий значения выходным параметрам. Если ваша процедура не выполняет каких-либо изменений или имеет только одно выражение INSERT/UPDATE/DELETE/MERGE, то обычно вам вообще не нужно явно указывать транзакцию.

В то время как блок TRY будет выглядеть по-разному от процедуры к процедуре, блок CATCH должен быть более или менее результатом копирования и вставки. То есть вы делаете что-то короткое и простое и затем используете повсюду, не особо задумываясь. Обработчик CATCH, приведенный выше, выполняет три действия:

  1. Откатывает любые открытые транзакции.
  2. Повторно вызывает ошибку.
  3. Убеждается, что возвращаемое процедурой значение отлично от нуля.

Эти три действия должны всегда быть там. Мы можете возразить, что строка

IF @@trancount > 0 ROLLBACK TRANSACTION

не нужна, если нет явной транзакции в процедуре, но это абсолютно неверно. Возможно, вы вызываете хранимую процедуру, которая открывает транзакцию, но которая не может ее откатить из-за ограничений TRY-CATCH. Возможно, вы или кто-то другой добавите явную транзакцию через два года. Вспомните ли вы тогда о том, что нужно добавить строку с откатом? Не рассчитывайте на это. Я также слышу читателей, которые возражают, что если тот, кто вызывает процедуру, открыл транзакцию, мы не должны ее откатывать… Нет, мы должны, и если вы хотите знать почему, вам нужно прочитать вторую и третью части. Откат транзакции в обработчике CATCH – это категорический императив, у которого нет исключений.

Код повторной генерации ошибки включает такую строку:

DECLARE @msg nvarchar(2048) = error_message()

Встроенная функция error_message() возвращает текст возникшей ошибки. В следующей строке ошибка повторно вызывается с помощью выражения RAISERROR. Это не самый простой способ вызова ошибки, но он работает. Другие способы мы рассмотрим в следующей главе.

Замечание: синтаксис для присвоения начального значения переменной в DECLARE был внедрен в SQL Server 2008. Если у вас SQL Server 2005, вам нужно разбить строку на DECLARE и выражение SELECT.

Финальное выражение RETURN – это страховка. RAISERROR никогда не прерывает выполнение, поэтому выполнение следующего выражения будет продолжено. Пока все процедуры используют TRY-CATCH, а также весь клиентский код обрабатывает исключения, нет повода для беспокойства. Но ваша процедура может быть вызвана из старого кода, написанного до SQL Server 2005 и до внедрения TRY-CATCH. В те времена лучшее, что мы могли делать, это смотреть на возвращаемые значения. То, что вы возвращаете с помощью RETURN, не имеет особого значения, если это не нулевое значение (ноль обычно обозначает успешное завершение работы).

Последнее выражение в процедуре – это END CATCH. Никогда не следует помещать какой-либо код после END CATCH. Кто-нибудь, читающий процедуру, может не увидеть этот кусок кода.

После прочтения теории давайте попробуем тестовый пример:

EXEC insert_data 9, NULL

Результат выполнения:

Msg 50000, Level 16, State 1, Procedure insert_data, Line 12
Cannot insert the value NULL into column 'b', table 'tempdb.dbo.sometable'; column does not allow nulls. INSERT fails.

Давайте добавим внешнюю процедуру для того, чтобы увидеть, что происходит при повторном вызове ошибки:

CREATE PROCEDURE outer_sp @a int, @b int AS
   SET XACT_ABORT, NOCOUNT ON
   BEGIN TRY
      EXEC insert_data @a, @b
   END TRY
   BEGIN CATCH
      IF @@trancount > 0 ROLLBACK TRANSACTION
      DECLARE @msg nvarchar(2048) = error_message()
      RAISERROR (@msg, 16, 1)
      RETURN 55555
   END CATCH
go
EXEC outer_sp 8, 8

Результат работы:

Msg 50000, Level 16, State 1, Procedure outer_sp, Line 9
Violation of PRIMARY KEY constraint 'pk_sometable'. Cannot insert duplicate key in object 'dbo.sometable'. The duplicate key value is (8, 8).

Мы получили корректное сообщение об ошибке, но если вы посмотрите на заголовки этого сообщения и на предыдущее поближе, то можете заметить проблему:

Msg 50000, Level 16, State 1, Procedure insert_data, Line 12
Msg 50000, Level 16, State 1, Procedure outer_sp, Line 9

Сообщение об ошибке выводит информацию о расположении конечного выражения RAISERROR. В первом случае некорректен только номер строки. Во втором случае некорректно также имя процедуры. Для простых процедур, таких как наш тестовый пример, это не является большой проблемой. Но если у вас есть несколько уровней вложенных сложных процедур, то наличие сообщения об ошибке с отсутствием указания на место её возникновения сделает поиск и устранение ошибки намного более сложным делом. По этой причине желательно генерировать ошибку таким образом, чтобы можно было определить нахождение ошибочного фрагмента кода быстро, и это то, что мы рассмотрим в следующей главе.

4. Три способа генерации ошибки

4.1 Использование error_handler_sp

Мы рассмотрели функцию error_message(), которая возвращает текст сообщения об ошибке. Сообщение об ошибке состоит из нескольких компонентов, и существует своя функция error_xxx() для каждого из них. Мы можем использовать их для повторной генерации полного сообщения, которое содержит оригинальную информацию, хотя и в другом формате. Если делать это в каждом обработчике CATCH, это будет большой недостаток — дублирование кода. Вам не обязательно находиться в блоке CATCH для вызова error_message() и других подобных функций, и они вернут ту же самую информацию, если будут вызваны из хранимой процедуры, которую выполнит блок CATCH.

Позвольте представить вам error_handler_sp:

CREATE PROCEDURE error_handler_sp AS
 
   DECLARE @errmsg   nvarchar(2048),
           @severity tinyint,
           @state    tinyint,
           @errno    int,
           @proc     sysname,
           @lineno   int
           
   SELECT @errmsg = error_message(), @severity = error_severity(),
          @state  = error_state(), @errno = error_number(),
          @proc   = error_procedure(), @lineno = error_line()
       
   IF @errmsg NOT LIKE '***%'
   BEGIN
      SELECT @errmsg = '*** ' + coalesce(quotename(@proc), '<dynamic SQL>') + 
                       ', Line ' + ltrim(str(@lineno)) + '. Errno ' + 
                       ltrim(str(@errno)) + ': ' + @errmsg
   END
   RAISERROR('%s', @severity, @state, @errmsg)

Первое из того, что делает error_handler_sp – это сохраняет значение всех error_xxx() функций в локальные переменные. Я вернусь к выражению IF через секунду. Вместо него давайте посмотрим на выражение SELECT внутри IF:

SELECT @errmsg = '*** ' + coalesce(quotename(@proc), '<dynamic SQL>') + 
                 ', Line ' + ltrim(str(@lineno)) + '. Errno ' + 
                 ltrim(str(@errno)) + ': ' + @errmsg

Цель этого SELECT заключается в форматировании сообщения об ошибке, которое передается в RAISERROR. Оно включает в себя всю информацию из оригинального сообщения об ошибке, которое мы не можем вставить напрямую в RAISERROR. Мы должны обработать имя процедуры, которое может быть NULL для ошибок в обычных скриптах или в динамическом SQL. Поэтому используется функция COALESCE. (Если вы не понимаете форму выражения RAISERROR, я рассказываю о нем более детально во второй части.)

Отформатированное сообщение об ошибке начинается с трех звездочек. Этим достигаются две цели: 1) Мы можем сразу видеть, что это сообщение вызвано из обработчика CATCH. 2) Это дает возможность для error_handler_sp отфильтровать ошибки, которые уже были сгенерированы один или более раз, с помощью условия NOT LIKE ‘***%’ для того, чтобы избежать изменения сообщения во второй раз.

Вот как обработчик CATCH должен выглядеть, когда вы используете error_handler_sp:

BEGIN CATCH
   IF @@trancount > 0 ROLLBACK TRANSACTION
   EXEC error_handler_sp
   RETURN 55555
END CATCH

Давайте попробуем несколько тестовых сценариев.

EXEC insert_data 8, NULL
EXEC outer_sp 8, 8

Результат выполнения:

Msg 50000, Level 16, State 2, Procedure error_handler_sp, Line 20
*** [insert_data], Line 5. Errno 515: Cannot insert the value NULL into column 'b', table 'tempdb.dbo.sometable'; column does not allow nulls. INSERT fails.
Msg 50000, Level 14, State 1, Procedure error_handler_sp, Line 20
*** [insert_data], Line 6. Errno 2627: Violation of PRIMARY KEY constraint 'pk_sometable'. Cannot insert duplicate key in object 'dbo.sometable'. The duplicate key value is (8, 8).

Заголовки сообщений говорят о том, что ошибка возникла в процедуре error_handler_sp, но текст сообщений об ошибках дает нам настоящее местонахождение ошибки – как название процедуры, так и номер строки.

Я покажу еще два метода вызова ошибок. Однако error_handler_sp является моей главной рекомендацией для читателей, которые читают эту часть. Это — простой вариант, который работает на всех версиях SQL Server начиная с 2005. Существует только один недостаток: в некоторых случаях SQL Server генерирует два сообщения об ошибках, но функции error_xxx() возвращают только одну из них, и поэтому одно из сообщений теряется. Это может быть неудобно при работе с административными командами наподобие BACKUPRESTORE, но проблема редко возникает в коде, предназначенном чисто для приложений.

4.2. Использование ;THROW

В SQL Server 2012 Microsoft представил выражение ;THROW для более легкой обработки ошибок. К сожалению, Microsoft сделал серьезную ошибку при проектировании этой команды и создал опасную ловушку.

С выражением ;THROW вам не нужно никаких хранимых процедур. Ваш обработчик CATCH становится таким же простым, как этот:

BEGIN CATCH
   IF @@trancount > 0 ROLLBACK TRANSACTION
   ;THROW
   RETURN 55555
END CATCH

Достоинство ;THROW в том, что сообщение об ошибке генерируется точно таким же, как и оригинальное сообщение. Если изначально было два сообщения об ошибках, оба сообщения воспроизводятся, что делает это выражение еще привлекательнее. Как и со всеми другими сообщениями об ошибках, ошибки, сгенерированные ;THROW, могут быть перехвачены внешним обработчиком CATCH и воспроизведены. Если обработчика CATCH нет, выполнение прерывается, поэтому оператор RETURN в данном случае оказывается не нужным. (Я все еще рекомендую оставлять его, на случай, если вы измените свое отношение к ;THROW позже).

Если у вас SQL Server 2012 или более поздняя версия, измените определение insert_data и outer_sp и попробуйте выполнить тесты еще раз. Результат в этот раз будет такой:

Msg 515, Level 16, State 2, Procedure insert_data, Line 5
Cannot insert the value NULL into column 'b', table 'tempdb.dbo.sometable'; column does not allow nulls. INSERT fails.
Msg 2627, Level 14, State 1, Procedure insert_data, Line 6
Violation of PRIMARY KEY constraint 'pk_sometable'. Cannot insert duplicate key in object 'dbo.sometable'. The duplicate key value is (8, 8).

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

В этом месте вы можете сказать себе: действительно ли Microsoft назвал команду ;THROW? Разве это не просто THROW? На самом деле, если вы посмотрите в Books Online, там не будет точки с запятой. Но точка с запятой должны быть. Официально они отделяют предыдущее выражение, но это опционально, и далеко не все используют точку с запятой в выражениях T-SQL. Более важно, что если вы пропустите точку с запятой перед THROW, то не будет никакой синтаксической ошибки. Но это повлияет на поведение при выполнении выражения, и это поведение будет непостижимым для непосвященных. При наличии активной транзакции вы получите сообщение об ошибке, которое будет полностью отличаться от оригинального. И еще хуже, что при отсутствии активной транзакции ошибка будет тихо выведена без обработки. Такая вещь, как пропуск точки с запятой, не должно иметь таких абсурдных последствий. Для уменьшения риска такого поведения, всегда думайте о команде как о ;THROW (с точкой с запятой).

Нельзя отрицать того, что ;THROW имеет свои преимущества, но точка с запятой не единственная ловушка этой команды. Если вы хотите использовать ее, я призываю вас прочитать по крайней мере вторую часть этой серии, где я раскрываю больше деталей о команде ;THROW. До этого момента, используйте error_handler_sp.

4.3. Использование SqlEventLog

Третий способ обработки ошибок – это использование SqlEventLog, который я описываю очень детально в третьей части. Здесь я лишь сделаю короткий обзор.

SqlEventLog предоставляет хранимую процедуру slog.catchhandler_sp, которая работает так же, как и error_handler_sp: она использует функции error_xxx() для сбора информации и выводит сообщение об ошибке, сохраняя всю информацию о ней. Вдобавок к этому, она логирует ошибку в таблицу splog.sqleventlog. В зависимости от типа приложения, которое у вас есть, эта таблица может быть очень ценным объектом.

Для использования SqlEventLog, ваш обработчик CATCH должен быть таким:

BEGIN CATCH
   IF @@trancount > 0 ROLLBACK TRANSACTION
   EXEC slog.catchhandler_sp @@procid
   RETURN 55555
END CATCH

@@procid возвращает идентификатор объекта текущей хранимой процедуры. Это то, что SqlEventLog использует для логирования информации в таблицу. Используя те же тестовые сценарии, получим результат их работы с использованием catchhandler_sp:

Msg 50000, Level 16, State 2, Procedure catchhandler_sp, Line 125
{515} Procedure insert_data, Line 5
Cannot insert the value NULL into column 'b', table 'tempdb.dbo.sometable'; column does not allow nulls. INSERT fails.
Msg 50000, Level 14, State 1, Procedure catchhandler_sp, Line 125
{2627} Procedure insert_data, Line 6
Violation of PRIMARY KEY constraint 'pk_sometable'. Cannot insert duplicate key in object 'dbo.sometable'. The duplicate key value is (8, 8).

Как вы видите, сообщение об ошибке отформатировано немного не так, как это делает error_handler_sp, но основная идея такая же. Вот образец того, что было записано в таблицу slog.sqleventlog:

logid logdate errno severity logproc linenum msgtext
1 2015-01-25 22:40:24.393 515 16 insert_data 5 Cannot insert …
2 2015-01-25 22:40:24.395 2627 14 insert_data 6 Violation of …

Если вы хотите попробовать SqlEventLog, вы можете загрузить файл sqleventlog.zip. Инструкция по установке находится в третьей части, раздел Установка SqlEventLog.

5. Финальные замечания

Вы изучили основной образец для обработки ошибок и транзакций в хранимых процедурах. Он не идеален, но он должен работать в 90-95% вашего кода. Есть несколько ограничений, на которые стоит обратить внимание:

  1. Как мы видели, ошибки компиляции не могут быть перехвачены в той же процедуре, в которой они возникли, а только во внешней процедуре.
  2. Пример не работает с пользовательскими функциями, так как ни TRY-CATCH, ни RAISERROR нельзя в них использовать.
  3. Когда хранимая процедура на Linked Server вызывает ошибку, эта ошибка может миновать обработчик в хранимой процедуре на локальном сервере и отправиться напрямую клиенту.
  4. Когда процедура вызвана как INSERT-EXEC, вы получите неприятную ошибку, потому что ROLLBACK TRANSACTION не допускается в данном случае.
  5. Как упомянуто выше, если вы используете error_handler_sp или SqlEventLog, мы потеряете одно сообщение, когда SQL Server выдаст два сообщения для одной ошибки. При использовании ;THROW такой проблемы нет.

Я рассказываю об этих ситуациях более подробно в других статьях этой серии.

Перед тем как закончить, я хочу кратко коснуться триггеров и клиентского кода.

Триггеры

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

С триггерами важно понимать, что они являются частью команды, которая запустила триггер, и в триггере вы находитесь внутри транзакции, даже если не используете BEGIN TRANSACTION.
Иногда я вижу на форумах людей, которые спрашивают, могут ли они написать триггер, который не откатывает в случае падения запустившую его команду. Ответ таков: нет способа сделать это надежно, поэтому не стоит даже пытаться. Если в этом есть необходимость, по возможности не следует использовать триггер вообще, а найти другое решение. Во второй и третьей частях я рассматриваю обработку ошибок в триггерах более подробно.

Клиентский код

У вас должна быть обработка ошибок в коде клиента, если он имеет доступ к базе. То есть вы должны всегда предполагать, что при любом вызове что-то может пойти не так. Как именно внедрить обработку ошибок, зависит от конкретной среды.

Здесь я только обращу внимание на важную вещь: реакцией на ошибку, возвращенную SQL Server, должно быть завершение запроса во избежание открытых бесхозных транзакций:

IF @@trancount > 0 ROLLBACK TRANSACTION

Это также применимо к знаменитому сообщению Timeout expired (которое является не сообщением от SQL Server, а от API).

6. Конец первой части

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

… и не забывайте добавлять эту строку в начало ваших хранимых процедур:

SET XACT_ABORT, NOCOUNT ON

We have client app that is running some SQL on a SQL Server 2005 such as the following:

BEGIN TRAN;
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
COMMIT TRAN;

It is sent by one long string command.

If one of the inserts fail, or any part of the command fails, does SQL Server roll back the transaction? If it does not rollback, do I have to send a second command to roll it back?

I can give specifics about the api and language I’m using, but I would think SQL Server should respond the same for any language.

marc_s's user avatar

marc_s

728k174 gold badges1327 silver badges1455 bronze badges

asked Nov 17, 2009 at 15:38

jonathanpeppers's user avatar

jonathanpeppersjonathanpeppers

26k21 gold badges98 silver badges182 bronze badges

1

You can put set xact_abort on before your transaction to make sure sql rolls back automatically in case of error.

Greg B's user avatar

Greg B

14.5k18 gold badges84 silver badges140 bronze badges

answered Nov 17, 2009 at 15:47

10

You are correct in that the entire transaction will be rolled back. You should issue the command to roll it back.

You can wrap this in a TRY CATCH block as follows

BEGIN TRY
    BEGIN TRANSACTION

        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);

    COMMIT TRAN -- Transaction Success!
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRAN --RollBack in case of Error

    -- <EDIT>: From SQL2008 on, you must raise error messages as follows:
    DECLARE @ErrorMessage NVARCHAR(4000);  
    DECLARE @ErrorSeverity INT;  
    DECLARE @ErrorState INT;  

    SELECT   
       @ErrorMessage = ERROR_MESSAGE(),  
       @ErrorSeverity = ERROR_SEVERITY(),  
       @ErrorState = ERROR_STATE();  

    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);  
    -- </EDIT>
END CATCH

SQL Police's user avatar

SQL Police

4,0971 gold badge24 silver badges54 bronze badges

answered Nov 17, 2009 at 15:46

Raj More's user avatar

Raj MoreRaj More

46.8k33 gold badges130 silver badges195 bronze badges

12

Here the code with getting the error message working with MSSQL Server 2016:

BEGIN TRY
    BEGIN TRANSACTION 
        -- Do your stuff that might fail here
    COMMIT
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRAN

        DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE()
        DECLARE @ErrorSeverity INT = ERROR_SEVERITY()
        DECLARE @ErrorState INT = ERROR_STATE()

    -- Use RAISERROR inside the CATCH block to return error  
    -- information about the original error that caused  
    -- execution to jump to the CATCH block.  
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH

Justin Loveless's user avatar

answered Apr 27, 2017 at 5:04

samwise's user avatar

samwisesamwise

1,8471 gold badge20 silver badges24 bronze badges

3

From MDSN article, Controlling Transactions (Database Engine).

If a run-time statement error (such as a constraint violation) occurs in a batch, the default behavior in the Database Engine is to roll back only the statement that generated the error. You can change this behavior using the SET XACT_ABORT statement. After SET XACT_ABORT ON is executed, any run-time statement error causes an automatic rollback of the current transaction. Compile errors, such as syntax errors, are not affected by SET XACT_ABORT. For more information, see SET XACT_ABORT (Transact-SQL).

In your case it will rollback the complete transaction when any of inserts fail.

default locale's user avatar

answered Jul 23, 2013 at 10:09

Vitaly's user avatar

VitalyVitaly

3103 silver badges6 bronze badges

2

If one of the inserts fail, or any part of the command fails, does SQL server roll back the transaction?

No, it does not.

If it does not rollback, do I have to send a second command to roll it back?

Sure, you should issue ROLLBACK instead of COMMIT.

If you want to decide whether to commit or rollback the transaction, you should remove the COMMIT sentence out of the statement, check the results of the inserts and then issue either COMMIT or ROLLBACK depending on the results of the check.

answered Nov 17, 2009 at 15:45

Quassnoi's user avatar

QuassnoiQuassnoi

411k91 gold badges613 silver badges613 bronze badges

4

Reading Time: 8 minutes

In SQL Server, there are plenty of instances where you might want to undo a statement that was just ran.

Maybe you wrote an UPDATE statement that modified too many rows, or maybe an INSERT statement wasn’t quite right.

In these instances, it is great when the work is performed within a transaction and can therefore be undone.

Do you need to know how the ROLLBACK statement works? Do you need to learn a thing or two about error handling? You’ve come to the right place!

In this tutorial, we’ll teach you everything you need to know about the ROLLBACK statement. We’ll learn a few tricks and tools we can use to help identity problems in a transaction that would trigger a ROLLBACK to occur, so that our data remains consistent.

If you need a rundown on what a transaction is in the first place, check out my introduction to transactions here: SQL Server Transactions: An Introduction for Beginners

Once you get the hang of transactions, you should also make sure you understand these 6 rules about transactions.

Here are the topics we will cover in this tutorial:

  1. How to issue a ROLLBACK in an explicit transaction
  2. Understanding @@ROWCOUNT and @@ERROR
  3. Can I ROLLBACK a transaction that was already committed?
  4. Tips, tricks, and links

Let’s roll it over:

1. How to issue a ROLLBACK in an explicit transaction

In the introduction to transactions, we learned how to write an explicit transaction that can be either committed or rolled back. We’ll touch on that topic here, too.

Let’s go ahead and create a table and populate it with data for us to do some ROLLBACK tests with:

CREATE TABLE Books
(
BookID INT PRIMARY KEY IDENTITY,
Title VARCHAR(50),
Author VARCHAR(20),
Pages SMALLINT
)

INSERT INTO Books(Title, Author, Pages)
VALUES 
('As a man thinketh','Allen',45),
('From poverty to power','Allen',56),
('Eat that frog','Tracy',108),
('The war of art','Pressfield',165)

Here’s the data in our table:

SQL Server rollback Books table

So far, so good.

Let’s think about how to wrap a simple UPDATE statement around an explicit transaction. Let’s say I messed up the Pages value for the book “From Poverty to Power“, which is BookID # 2. I meant to give it a Pages value of 156.

If we want to perform an UPDATE statement within a transaction, it’s very easy. First, we put the words BEGIN TRANSACTION before the UPDATE statement, like this:

BEGIN TRANSACTION
UPDATE Books SET Pages = 156 where BookID = 2

You could abbreviate the word “TRANSACTION” and just say “BEGIN TRAN” if you want.

If we wanted to go ahead and COMMIT this transaction, we would just put the word COMMIT after the UPDATE statement, like this:

BEGIN TRANSACTION
UPDATE Books SET Pages = 156 where BookID = 2
COMMIT

You could also be very explicit and say “COMMIT TRANSACTION“, or “COMMIT TRAN“, or even “COMMIT WORK” if you wanted.

But what if you wanted to ROLLBACK?

Instead of committing the transaction, we can undo the UPDATE statement performed in the transaction by using the ROLLBACK keyword, like this:

BEGIN TRANSACTION
UPDATE Books SET Pages = 156 where BookID = 2
ROLLBACK

Similarly, you could be explicit and say “ROLLBACK TRANSACTION, or just “ROLLBACK TRAN, or even “ROLLBACK WORK.

Either way, the effect is the same. The work done inside the transaction will be undone.

Let’s test it out. We’ll run that transaction with the ROLLBACK, then run a quick SELECT statement to see that the data is unchanged:

SQL Server rollback ROLLBACK explicit transaction

We see the change was basically undone and we’re back to where we started.

Folks, that’s basically all there is to it. When it comes to an explicit transaction (I.E. a transaction you create using the BEGIN TRANSACTION statement), the way you would undo any work performed inside that transaction is by issuing a ROLLBACK statement after the work.

2. Understanding @@ROWCOUNT and @@ERROR

The example in that last section was a bit silly because we knew we wanted to undo the work.

What if we want to make sure a ROLLBACK occurs if some unknown or unexpected error occurs in our transaction?

This is known as error handling. We want to protect our data in the event that some error occurs that we weren’t aware of.

We can use the extremely handy @@ROWCOUNT or @@ERROR system functions to help us incorporate error handling within our transactions.

Understanding @@ROWCOUNT

The @@ROWCOUNT system function simply tells us the number of rows effected by the last statement.

This can be extremely useful for writing bulletproof transactions. For example, if we want to run an UPDATE statement, and we want to make sure only one row gets updated, we can use the @@ROWCOUNT function to make sure only one row was updated.

Here’s the code, then we’ll discuss it:

BEGIN TRANSACTION
UPDATE Books SET Pages = 156 WHERE BookID = 2
IF (@@ROWCOUNT = 1)
COMMIT
ELSE
ROLLBACK

Remember, @@ROWCOUNT get’s the number of rows affected by the previous statement. The previous statement in our example is, of course, the UPDATE statement.

We want and expect our UPDATE statement to update only one row. But before we issue a COMMIT, we want to make sure only one row was updated.

We use the handy IF…ELSE block to see how many rows were affected by the UPDATE statement. If in fact we only updated one row, we know all is well and we issue the COMMIT.

Otherwise, if @@ROWCOUNT returns a number larger than 1, we know something went wrong and therefore issue a ROLLBACK.

Here’s our code executing the UPDATE statement and the COMMIT successfully:

BEGIN TRANSACTION UPDATE Books SET Pages = 156 WHERE BookID = 2 IF (@@ROWCOUNT = 1) COMMIT ELSE ROLLBACK

Let’s see if we can force the ROLLBACK to run. We’ll do another UPDATE statement, but this time leave off the WHERE clause (which you should NEVER do):

sql server rollback no where clause successfully rolled back

Evidently our UPDATE statement modified more than one row, meaning @@ROWCOUNT was greater than 1.  Because we have the error handling in place, the accidental work was successfully rolled back.

All is well.

Understanding @@ERROR

The @@ERROR system function is another great function to use for error handling. We can use this function to see if an error occurred during the last execution of the most recent SQL statement.

This function will return the error number of the error if one occurred. Otherwise, if no error occurred in the previous statement, it returns 0.

The @@ERROR function, combined with an IF…ELSE block, is a great way to roll back the transaction if any bad thing happened.

Let’s take a look at an example. Here’s an example of an UPDATE statement that has a deliberate error in place:

BEGIN TRANSACTION
UPDATE Books SET Pages = 200 WHERE BookID = 4/0
IF (@@ERROR = 0)
COMMIT
ELSE
ROLLBACK

The error is in the WHERE clause, where I’m trying to divide an integer by 0. This is a mathematic violation and will give us an error.

In the IF block, if the value of @@ERROR is 0, we know there was no error generated by the UPDATE statement and it’s safe to COMMIT the transaction.

However, we would enter the ELSE block if an error did occur. In that case, @@ERROR would hold a value greater than 0. In that case, we ROLLBACK the transaction because something went wrong.

Let’s test it out:

sql server rollback divide by zero

We see that the error occurs. Now let’s check the data to see if anything changed:

sql server rollback checking content after rollback

Nope, looks good. But the most important thing to check is that we’re not in an open transaction. We use the handy @@TRANCOUNT system function for that. This function tells us how many open transactions exist in the current connection. Let’s make sure it’s zero:

sql server rollback trancount is zero

Nice.

3. Can I ROLLBACK a transaction that was already committed?

I have a notion that some of you are reading this tutorial in the hope you can learn how to undo something you didn’t mean to do in SQL Server.

Maybe you ran and UPDATE statement and forgot to add a WHERE clause, or maybe the WHERE clause existed but it pulled more rows than you expected.

Or worse, you ran a DELETE statement without specifying a WHERE clause. Yikes!

Ok, here’s how we can check if your work can be undone:

  1. Is the connection still open? It would be open if you didn’t close the query window or close SQL Server completely. If you’re still in the same connection, we can proceed to step 2.
  2. Check the value of @@TRANCOUNT by running the following query: SELECT @@TRANCOUNT

If @@TRANCOUNT is greater than 0. We’re still in an open transaction. You should be able to just execute a ROLLBACK statement. After running that, if you check @@TRANCOUNT again, it will be and the changes made in error should be undone.

But if @@TRANCOUNT is already 0, I’m afraid to say the changes are already committed to the database.

But fear not. If your database team is worth their sand, they will have prepared for something like this. They ought to have a backup plan for scenarios where bad SQL was executed. They would need to put the database back in a state close to what it was before the bad SQL was executed.

Things happen. It will be okay!

4. Tips, tricks, and links

Here is a list of tips and tricks you should know when working with the ROLLBACK command:

Tip # 1: ROLLBACK will set @@TRANCOUNT to zero, regardless of what it was before.

If you have 35 open transactions, then you issue a single ROLLBACK, guess what, all the work done in all those transactions was just undone.

This is different from COMMIT which will just reduce @@TRANCOUNT by 1 each time it’s ran. So if you wanted to commit all 35 transactions instead, you would need to run COMMIT 35 times.

(By the way, none of the data is actually committed to the database until @@TRANCOUNT is reduced from 1 to 0. Running COMMIT once when there are 35 open transaction literally does nothing except reduce @@TRANCOUNT to 34)

Learn more about this factoid and others in this tutorial:

SQL Server Transactions: Do you understand these 6 rules?

Tip # 2: Before running an UPDATE or DELETE statement, run it as a SELECT statement first

If I’m updating data in a particularly important database, a trick I like to do is simply write my statement as a SELECT statement first.

That SELECT statement will likely have a WHERE clause. I’ll run that SELECT statement and verify the returned rows are the rows I expect. These are, of course, rows I intend to update or delete.

If everything checks out, I’ll use that same WHERE clause in my UPDATE or DELETE statement. That way, I can be confident the rows getting updated or deleted are those same rows that were returned in my SELECT statement.

This tip doesn’t exactly deal with performing a ROLLBACK, but it does deal with writing accurate DML (Data Manipulation Language) statements that hopefully won’t need to be rolled back.

Links

One of the most helpful books you should own is T-SQL Querying by Itsik Ben-Gan, Dejan Sarka, Adam Machanic and Kevin Farlee. It has discussions about transactions in SQL Server, as well as nearly everything else you need to know about querying SQL Server databases. You definitely won’t regret owning this book, trust me. Get it today!

Also, here is a list of links to the official Microsoft documentation on the ROLLBACK statement, and the @@ROWCOUNT@@ERROR and @@TRANCOUNT system functions:

  • ROLLBACK TRANSACTION (Transact-SQL)
  • @@ROWCOUNT (Transact-SQL)
  • @@ERROR (Transact-SQL)
  • @@TRANCOUNT (Transact-SQL)

Next Steps:

Leave a comment if you found this tutorial helpful!

If you missed the introduction tutorial on SQL Server transactions, check it out here!

We discuss everything you need to know about transactions, including using ROLLBACK to undo work performed within a transaction.

Also, there are many important factoids about transactions many people don’t know. If you want to be a transactional master, you should read this tutorial too:

SQL Server Transactions: Do you understand these 6 rules?

Thank you very much for reading!

Make sure you subscribe to my newsletter to receive special offers and notifications anytime a new tutorial is released!

If you have any questions, please leave a comment. Or better yet, send me an email!

SQL ROLLBACK Statement

SQL Rollback Begin

This specifies the beginning of an explicit, local transaction. The BEGIN TRANSACTION statement initiates an explicit transaction, which ends with the COMMIT or ROLLBACK statement.

Example 1: PostgreSQL transactions via BEGIN, COMMIT, and ROLLBACK statements.

--begin the transaction
BEGIN;

-- deduct the amount from the account 1
UPDATE accounts 
SET balance = balance - 1500
WHERE id = 1;

-- add the amount from the account 3 (instead of 2)
UPDATE accounts
SET balance = balance + 1500
WHERE id = 3; 

-- roll back the transaction
ROLLBACK;

Example 2: begin transaction in sql:

BEGIN TRANSACTION [Tran1]
BEGIN TRY

INSERT INTO [Test].[dbo].[T1] ([Title], [AVG])
VALUES ('Tidd130', 130), ('Tidd230', 230)

UPDATE [Test].[dbo].[T1]
SET [Title] = N'az2' ,[AVG] = 1
WHERE [dbo].[T1].[Title] = N'az'

COMMIT TRANSACTION [Tran1]
END TRY
BEGIN CATCH

ROLLBACK TRANSACTION [Tran1]
END CATCH

Example 3: Begin Commit Transaction ROLLBACK in a Stored procedure SQL:

You can use dynamic parameters to create a stored procedure. How can we undo a transaction if it fails for whatever reason.

Create PROCEDURE  [dbo].[Save_PurchaseOrder]  
(
@UserId int,
@Vendorid int,
@CounterPerson varchar(50),
@ShippedTo varchar(20),
@ShippingCost  varchar(20),
@CustomerName  varchar(50),
@Location  varchar(50),
@OrderStatus char(1),
@ChangeStatusById int=0,
@ChangeStatusByName  varchar(50)='',
@MAXID nvarchar(200) output,
@OtherVendor nvarchar(50),
@OtherTechnician nvarchar(50)
) 
AS 

BEGIN
  
BEGIN TRANSACTION 
INSERT INTO [tblPurchaseOrder]
 ([UserId],[Vendorid],[PartsStatus]
 ,[ShippedTo],[ShippingCost],[CustomerName],[Location]
 ,[OrderStatus],[OrderDate],[ChangeStatusById],[ChangeStatusByName],OtherVendor,OtherTechnician)
  VALUES
 (@UserId ,@PartsStatus
 ,@ShippedTo,@ShippingCost,@CustomerName,@Location
 ,@OrderStatus,GETDATE(),@ChangeStatusById,@ChangeStatusByName,@OtherVendor,@OtherTechnician)
 
SELECT @MAXID=SCOPE_IDENTITY()
 IF @@ERROR<>0 
 BEGIN 
 ROLLBACK 
 SET @MAXID=0
 RETURN     
 END 
 COMMIT TRANSACTION 
END

The SQL ROLLBACK transaction statement returns an explicit or implicit transaction to its start point, or to a savepoint within the transaction. You can use ROLLBACK TRANSACTION to undo all data changes made since the transaction began or to a savepoint. It also releases transaction-held resources.

The ROLLBACK statement can be used to end a unit of recovery and roll back all of the relational database modifications made by that unit. ROLLBACK also stops the unit of work if relational databases are the sole recoverable resources used in the application process.

The rollback command is used to restore the table to its prior permanent status (undo state). If commit is used in the query, we will be unable to reverse the modifications made by the query.

ROLLBACK can also be used to undo only the modifications performed after a savepoint was set within the recovery unit without terminating the recovery unit. Select modifications can be undone by rolling back to a previous savepoint.

The BEGIN TRANSACTION keyword indicates where the transaction begins. It is used to cancel the entire transaction starting from the given Begin Transaction. Before running the delete command, use the ROLLBACK command to provide the original table that we stored.

NOTE: The ROLLBACK command is only useful if the transaction has not yet been committed.

Syntax:

ROLLBACK { TRAN | TRANSACTION }   
     [ transaction_name | @tran_name_variable  
     | savepoint_name | @savepoint_variable ]   
[ ; ]

Example 1: The effect of rolling back a named transaction is demonstrated in the following instance. The following statements create a table, initiate a specified transaction, insert two rows, and then roll back the transaction identified in the variable @TransactionName. Two rows are inserted by another statement outside of the named transaction. The results of the previous statements are returned by the query.

USE tempdb;  
GO  
CREATE TABLE ValueTable ([value] INT);  
GO  
  
DECLARE @TransactionName VARCHAR(20) = 'Transaction1';  
  
BEGIN TRAN @TransactionName  
       INSERT INTO ValueTable VALUES(1), (2);  
ROLLBACK TRAN @TransactionName;  
  
INSERT INTO ValueTable VALUES(3),(4);  
  
SELECT [value] FROM ValueTable;  
  
DROP TABLE ValueTable;

Output:

Example 2:

BEGIN TRAN T
SELECT * FROM dbo.EmpJobTitle 
WHERE EmpId =3;

UPDATE dbo.EmpJobTitle SET JobTitle ='Sr. HR Manager'
WHERE EmpId =3;

SELECT * FROM dbo.EmpJobTitle 
WHERE EmpId =3;

ROLLBACK TRAN T

SELECT * FROM dbo.EmpJobTitle 
WHERE EmpId =3;

SQL Rollback After Commit

While we use Commit in a query, the changes that it makes are permanent and visible. We are unable to undo the Commit.
The name of the transaction is tranName, and the command for the operation is the SQL statement that is used to perform the operation,
such as changing or inserting data.

Rolls back an explicit or implicit transaction to its start point, or to a savepoint within the transaction.

You can use ROLLBACK TRANSACTION to undo all data changes made since the transaction began or to a savepoint.
It also releases transaction-held resources.

A commit cannot be undone, rolled back, or reversed.


SQL Rollback After Delete

In the cases of Delete, Truncate, and Drop, we can rewind the data. When I perform queries, I can correctly rollback delete, drop, and truncate. Begin Transaction must be used before running the Delete, Drop, and Truncate queries.

While the database is in full recovery mode, it can use log files to rollback any changes made by DELETE. In complete recovery mode, TRUNCATE cannot be rolled back using log files.

If the current session is not closed, DELETE and TRUNCATE can both be rolled back while wrapped by TRANSACTION. TRUNCATE cannot be rolled back if it is written in Query Editor surrounded by TRANSACTION and the session is closed, however DELETE may.

Example 1: Create Database Ankit:

Create Table Tbl_Ankit(Name varchar(11))

insert into tbl_ankit(name) values('ankit');
insert into tbl_ankit(name) values('ankur');
insert into tbl_ankit(name) values('arti');

Select * From Tbl_Ankit

/*======================For Delete==================*/
Begin Transaction
Delete From Tbl_Ankit where Name='ankit'

Rollback
Select * From Tbl_Ankit

/*======================For Truncate==================*/
Begin Transaction
Truncate Table Tbl_Ankit 

Rollback
Select * From Tbl_Ankit

/*======================For Drop==================*/
Begin Transaction
Drop Table Tbl_Ankit 

Rollback
Select * From Tbl_Ankit

Example 2: Let us understand this concept in detail.

DELETE removes all entries from the table and records them in the Log file in case a rollback is required in the future. As a result, it moves slowly.

When TRUNCATE is used, SQL Server dealslocates the data files in the table and records the deallocation in the log files. Rollback can be used to recover deallocated data files that have been overwritten by other data. In the case of TRUNCATE, there is no guarantee of a rollback. However, using T-SQL, the following code shows that TRUNCATE can be rolled back for that specific session.

Create a test table with some data first. After that, execute the following T-SQL code in Query Editor to see how TRUNCATE affects the constructed test table.

BEGIN TRAN
TRUNCATE TABLE TestTable

-- Following SELECT will return TestTable empty
SELECT *
FROM TestTable

-- Following SELECT will return TestTable with original data
ROLLBACK
SELECT *
FROM TestTable

SQL Rollback After Update

You can revert the updated data while the transaction is open.

The rollback statement can be used to undo the transaction :

BEGIN TRANSACTION 
   --Update statement 
Rollback Transaction 

Example: The base table linked to a view can be updated. There are certain limitations to this, but they are outside the scope of this post for the beginning. We create a simple view of the EMPLOYEES table in the given description, then modify it.

CREATE OR REPLACE VIEW employees_v AS
SELECT * FROM employees;

UPDATE employees_v
SET    salary = 1000
WHERE  employee_id = 7369;

1 row updated.

ROLLBACK;

SQL Rollback After Table

With ROLLBACK, you can use many ALTER TABLE instructions in a single transaction. Certain statements are irreversible.

In particular, these statements contain data definition language (DDL) statements such as those that build or drop databases,
tables, or stored procedures.

When I changed a table to add a column with the same name, I got an error and had to rollback (run the alter script twice on
purpose to test the rollback).

I got an error saying that the column should distinct (since the column was already added during the first execution of the script).

Example 1: I want the sql execution to display the Failed instead and rollback the transaction. My code is as follows:

BEGIN TRANSACTION
 
ALTER TABLE dbo.tbl_name

ADD  [column1] [varchar] (20) NULL 
 
--Error handling
IF @@Error = 0  
  Begin
   COMMIT TRANSACTION
   print '-Success'   
END
ELSE
 Begin
  ROLLBACK TRANSACTION
  print '-Failed'
End
GO

Example 2:

BEGIN TRANSACTION
 BEGIN TRY
 ALTER TABLE1...
 ALTER TABLE2...
  
-- Additional data/structural changes
 COMMIT
 END TRY
 BEGIN CATCH
 ROLLBACK;
  THROW; -- Only if you want reraise an exception (to determine the reason of the exception)
END CATCH

SQL Rollback Create Table

Example 1: In our TestDB database, there are two tables with data. Now let’s begin a transaction in the TestDB database and make some DDL modifications:

USE TestDB
GO

BEGIN TRANSACTION
 
TRUNCATE TABLE TableA

DROP TABLE TableB

CREATE TABLE TableC(ID INT) 

ROLLBACK

SELECT * FROM TableA
SELECT * FROM TableB
SELECT * FROM TableC

Example 2: creation of those tables survive the rollback of the transaction? How can I get them not to?

USE tempdb;
GO

CREATE DATABASE example;
GO

USE example;
GO

CREATE TABLE foo (a INT);
GO

INSERT INTO foo
VALUES (100);
GO

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;
GO

ALTER TABLE foo ADD CHECK (a < 10);-- Gives error "The ALTER TABLE statement conflicted with the CHECK constraint…", 
as expected
GO

CREATE TABLE bar (b INT);
GO

ROLLBACK;-- Gives error "The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION."  Huh?  
Where did our transaction go?
GO

SELECT *
FROM bar;-- Gives no error.  Table still exists!  NOT expected!
GO

USE tempdb;
GO

DROP DATABASE example;

SQL Rollback Drop Table

ROLLBACK reverses the updates made in the previous transaction block.

Roll back the current transaction, removing any modifications it would have caused.

Syntax:

ROLLBACK [ WORK | TRANSACTION ]

Example 1: Because the ROLLBACK command finishes the transaction, the season table is not removed:

premdb=# begin;
BEGIN
premdb=# drop table season;
DROP TABLE
premdb=# rollback;
ROLLBACK
premdb=# d season

Table «public.season»

Column Type Modifiers
seasonid smallint
season_name character(9)
numteams smallint
winners character varying(30)

Example 2:

postgres=# begin;

BEGIN

postgres=# delete from test;

DELETE 1

postgres=# drop table test;

DROP TABLE

postgres=# rollback;

ROLLBACK

postgres=# 

Despite the DROP TABLE statement, the table «test» exists in the database and retains its data because the ROLLBACK statement
was sent at the completion of the transaction block.


SQL Rollback if Insert Fails

If a trigger on an insert statement fails for some reason, roll back the insert statement.

Example 1: For example trigger on insert into employe(id,name) values(1,’ali’)

CREATE TRIGGER [dbo].[Customer_INSERT]
  ON [dbo].[employe]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
 
DECLARE @id INT
 
SELECT @id = INSERTED.id       
FROM INSERTED
 
INSERT INTO CustomerLogs
VALUES(@id, 'Inserted')
END

Example 2: I have several inserts on various tables, and I want them all to roll back if one fails.

DECLARE @TransactionName varchar(20) = 'Transaction1';
BEGIN TRY
BEGIN TRAN @TransactionName

INSERT INTO [dbo].Test1
(A --varchar
,B --Int
)
VALUES
('Test Fail' ,1)

DECLARE @ID bigint
SELECT @ID = SCOPE_IDENTITY()

INSERT INTO [dbo].Test2
(T1ID --Test1 ID
,A --varchar)
VALUES(@ID ,14)

COMMIT TRAN @TransactionName
END TRY
BEGIN CATCH
		
DECLARE @ErrorMessage NVARCHAR(4000);
DECLARE @ErrorSeverity INT;
DECLARE @ErrorState INT;
IF @@TRANCOUNT > 0
ROLLBACK TRAN @TransactionName

SELECT @ErrorMessage=ERROR_MESSAGE(),
@ErrorSeverity=ERROR_SEVERITY(),
@ErrorState=ERROR_STATE();
RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState)
END CATCH ", );

close database connection(dbc);

SQL Rollback Multiple Transaction

Nested transactions are possible. By checking at @@TRANCOUNT, we can see how many layers of transactions we have. When we construct an explicit transaction, @@TRANCOUNT is increased by one. When we commit a transaction, @@TRANCOUNT is reduced by one. When we roll back a transaction, however, @@TRANCOUNT is set to 0. This means that no matter how deep we go in nested transactions, one ROLLBACK will undo everything and bring us back to the beginning.

Example 1: I’m having trouble understanding how to apply rollback when dealing with several transactions with distinct names. I’m using a try catch to force a rollback in the event of a mistake.

CREATE PROCEDURE InsertFromPDP
AS
BEGIN

DECLARE @tempTable TABLE
 (
  Id INT PRIMARY KEY,
  Referencia VARCHAR(15),
  UAP NVARCHAR(20),
  ConsumoInicialWeek01 FLOAT,
  ConsumoInicialWeek02 FLOAT,
  Stock INT,
  PecasPorCaixa INT,
  NumTurnos INT DEFAULT 3,
  NumPab INT DEFAULT 6,
  AlcanceAbastecimento INT DEFAULT 3,
  QtdMin INT DEFAULT 4,
  QtdMax INT DEFAULT 12,
  NumDias INT DEFAULT 5
  UNIQUE (Id)
 )

INSERT INTO 
@tempTable  
 (
  Id,
  Referencia,
  UAP,
  ConsumoInicialWeek01,
  ConsumoInicialWeek02,
  Stock,
  PecasPorCaixa
  )

SELECT * 
 FROM 
 viewConsumoPDP

 BEGIN TRY
  BEGIN TRAN InsertNotExistsReferenciasFromPDP;
INSERT INTO 
  Parametros
    
SELECT  M.Referencia, 
 M.UAP,      
 M.NumTurnos,
 M.NumPab,
 M.AlcanceAbastecimento,
 M.QtdMin,
 M.QtdMax,
 M.NumDias
 FROM 
 @tempTable M    
 WHERE 
 NOT EXISTS 
 (
 SELECT * 
 FROM Parametros P 
 WHERE 
 M.Referencia <> P.Referencia
 AND 
 M.UAP <> P.UAP 
 )

 BEGIN TRAN InsertConsumoFromPDP

 -- TODO--

 COMMIT InsertNotExistsReferenciasFromPDP
 COMMIT InsertConsumoFromPDP
 END TRY
 BEGIN CATCH
 IF @@TRANCOUNT > 0
 ROLLBACK InsertNotExistsReferenciasFromPDP
 ROLLBACK InsertConsumoFromPDP
 -- RAISE ERROR --
 END CATCH
END

Example 2:

-- Create a table to use during the tests
CREATE TABLE tb_TransactionTest (value int)
GO

-- Test using 2 transactions and a rollback on the 

-- outer transaction
BEGIN TRANSACTION
  PRINT @@TRANCOUNT
  INSERT INTO tb_TransactionTest VALUES (1)

  BEGIN TRANSACTION -- inner transaction
   PRINT @@TRANCOUNT
   INSERT INTO tb_TransactionTest VALUES (2)

  COMMIT -- commit the inner transaction
   PRINT @@TRANCOUNT
   INSERT INTO tb_TransactionTest VALUES (3)

ROLLBACK -- roll back the outer transaction
PRINT @@TRANCOUNT

SELECT * FROM tb_TransactionTest
GO

SQL Rollback Savepoint

A SAVEPOINT is a point in a transaction where you can revert to a previous state without reverting the entire transaction.

The rollback to savepoint command issues a java.sql.Connection.rollback request that has been overloaded to operate with a
savepoint inside the current transaction and returns all work in the current transaction to the provided savepoint.

Syntax for a SAVEPOINT:

SAVEPOINT SAVEPOINT_NAME;

This command is only used to create a SAVEPOINT between all transactional statements. To undo a collection of transactions, use the ROLLBACK command.

The following is the syntax for rolling back to a SAVEPOINT.

ROLLBACK TO SAVEPOINT_NAME;

Example 1: The example below shows how to remove three separate records from the CUSTOMERS database. Before each delete,
you should generate a SAVEPOINT so that you can ROLLBACK to any SAVEPOINT at any moment to restore the required data.

 

Consider the CUSTOMERS table having the following records.

ID NAME AGE ADDRESS SALARY
1 Ramesh 32 Ahmedabad 2000.00
2 Khilan 25 Delhi 1500.00
3 kaushik 23 Kota 2000.00
4 Chaitali 25 Mumbai 6500.00
5 Hardik 27 Bhopal 8500.00
6 Komal 22 MP 4500.00
7 Muffy 24 Indore 10000.00
SAVEPOINT SP1;
Savepoint created.

DELETE FROM CUSTOMERS WHERE ID=1;
1 row deleted.

SAVEPOINT SP2;
Savepoint created.

DELETE FROM CUSTOMERS WHERE ID=2;
1 row deleted.

SAVEPOINT SP3;
Savepoint created.

DELETE FROM CUSTOMERS WHERE ID=3;
1 row deleted.

Now that the three removals have occurred, let us pretend you have changed your mind and have chosen to ROLLBACK to the SAVEPOINT you designated as SP2. The last two deletions are undone because SP2 was established after the initial deletion :−

ROLLBACK TO SP2;
Rollback complete.

Notice that only the first deletion took place since you rolled back to SP2.

SELECT * FROM CUSTOMERS;
ID NAME AGE ADDRESS SALARY
2 Khilan 25 Delhi 1500.00
3 kaushik 23 Kota 2000.00
4 Chaitali 25 Mumbai 6500.00
5 Hardik 27 Bhopal 8500.00
6 Komal 22 MP 4500.00
7 Muffy 24 Indore 10000.00

Example 2: The values 102 and 103 that were input after the savepoint, my savepoint, was set are rolled back in the given description. At commit, just the values 101 and 104 are added.

INSERT INTO product_key VALUES (101);
SAVEPOINT my_savepoint;
INSERT INTO product_key VALUES (102);
INSERT INTO product_key VALUES (103);
ROLLBACK TO SAVEPOINT my_savepoint;
INSERT INTO product_key VALUES (104);
COMMIT;

Example 3: roll back to the savepoint, and verify that the rollback worked:

splice> ROLLBACK TO SAVEPOINT savept1;
0 rows inserted/updated/deleted
splice> SELECT * FROM myTbl;

Output:

Example 4: The following example performs two INSERTs and an UPDATE; it includes two save points:

BEGIN
 INSERT INTO books (ISBN, CATEGORY,
 TITLE, NUM_PAGES,--  w w   w  . d  e    m  o  2s  .  c  o   m 
 PRICE, COPYRIGHT, AUTHOR1)
VALUES ('1234567893', 'Oracle Server',
 'Oracle Certification 071', 440, 35.99, 2015, 44);

SAVEPOINT A;

 INSERT INTO inventory (isbn, status, status_date, amount)
 VALUES ('1234567893', 'BACKORDERED', null, 1100);

SAVEPOINT B;

 UPDATE inventory
 SET status = 'IN STOCK'
 WHERE isbn = '1234567893';

ROLLBACK TO SAVEPOINT B;

COMMIT;
END;

We can see the impact of the ROLLBACK TO SAVEPOINT command with the following select:

SELECT b.title, i.status
FROM books b, inventory we
WHERE b.isbn = '1234567893'
AND b.isbn = i.isbn;

This returns the name of the book, as well as the status.

TITLE STATUS
Oracle Certification 071 BACKORDERED

SQL Rollback Transaction on Error

Example 1: rollback the transaction if an error has occurred in any SQL transaction:

BEGIN TRANSACTION NAME T1;
 
SELECT CURRENT_TRANSACTION();

delete from mytable;

insert into mytable values (456);
insert into mytable values ('Hello!');

ROLLBACK;

Example 2: The following example will perfectly explain you what the error happens :

START TRANSACTION;
 SET @c=0;

 INSERT INTO tbl_chart (acode,adesc)  VALUES (2,'3');
 INSERT INTO tbl_pics  (ID ,adesc) VALUES (1,'1');
 select ROW_COUNT() into @c ;

 IF ( c > 0) THEN
   COMMIT ;
 ELSE 
 ROLLBACK;
   END IF

and trying to check row_count and save its value in variable c . But this query gives error and it also saves the data after showing the error . where is the problem ?

And the error is this :

/* SQL Error (1064): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘IF ( c > 0) THEN
COMMIT’ at line 1 */

Example 3: query using Try/Catch block. First, I will delete all the records from the table.

TRUNCATE TABLE Test_tran  
BEGIN TRY  
 BEGIN TRAN  
 INSERT INTO Test_Tran ( ID, Name) VALUES (1,'Amit')  
 INSERT INTO Test_Tran ( ID, Name) VALUES (2,'Kapil')  
 INSERT INTO Test_Tran ( ID, Name) VALUES (1,'Aditya')  
 COMMIT TRAN  
END TRY  
BEGIN CATCH  
ROLLBACK TRAN  
END CATCH 

We can see that no rows will be placed into the table after running the above query because it was rolled back when an error occurred, and we ensured atomicity by utilising the try/catch block.

Example 4: Rollback transaction on error:

/*SQL SERVER 2000 Error Handling*/
BEGIN TRANSACTION
 UPDATE MyChecking SET Amount = Amount - $990.00
 WHERE AccountNum = 12345
 IF @@ERROR != 0
 BEGIN
 ROLLBACK TRANSACTION
 RETURN
 END
 ELSE
 UPDATE MySavings SET Amount = Amount + $990.00
 WHERE AccountNum = 12345
 IF @@ERROR != 0
 BEGIN
 ROLLBACK TRANSACTION
 RETURN
 END
 ELSE
COMMIT TRANSACTION
GO

Output:

Msg 547, Level 16, State 1, Server JAVA2SSQLEXPRESS, Line 4

The UPDATE statement conflicted with the CHECK constraint «ckMinBalance». The conflict occurred in database «master», table «dbo.MyChecking» the statement has been terminated.


SQL Rollback Transaction

In the current session, rolls back an open transaction.

In SQL, a rollback transaction can be used to go back to the start of a transaction or to a save point. You can use this SQL Rollback to fix problems or erase half-completed entries. Alternatively, it might be used at the conclusion of the transaction.

For example, if your transaction inserts a new record and then fails, you can use this rollback to restore the table to its previous state.

Example 1: Begin a transaction by inserting some data into a table, and then finish the transaction by rolling back the modifications:

select count(*) from a1;

Output:

begin name t4;
select current_transaction();

Output:

CURRENT_TRANSACTION()
1432071523422
insert into a1 values (1), (2);

Output:

number of rows inserted
2
rollback;
select count(*) from a1;

Output:

select current_transaction();

Output:

CURRENT_TRANSACTION()
[NULL]
select last_transaction();

Output:

LAST_TRANSACTION()
1432071523422

Example 2: In this instance, we’ll add a new record to the Employee database and then perform a rollback transaction. We’ll use Select Statement both within and outside of the transaction to accomplish this. Before the transaction is completed, the first select statement displays the table records.

BEGIN TRANSACTION

INSERT INTO [dbo].[EmployeeRecords] (
[EmpID], [FirstName], [LastName], [Education], [Occupation], [YearlyIncome], [Sales])
VALUES (7, 'SQL Server', 'Tutorial', 'Masters', 'Learn', 55000, 1250)

SELECT * FROM [dbo].[EmployeeRecords]

ROLLBACK TRANSACTION

SELECT * FROM [dbo].[EmployeeRecords]

Though there is no error in the above statement, it has not inserted the record.

Example 3: Transaction code and you want to undo it, you can rollback your transaction:

BEGIN TRY
 BEGIN TRANSACTION
 INSERT INTO Users(ID, Name, Age)
 VALUES(1, 'Bob', 24)
        
DELETE FROM Users WHERE Name = 'Todd'
 COMMIT TRANSACTION
END TRY
BEGIN CATCH
 ROLLBACK TRANSACTION
END CATCH

SQL Rollback vs Commit

Main Article :- Sql difference between ROLLBACK and COMMIT Transaction

Rollback Commit
The COMMIT statement allows a user to save any modifications or modifications to the current transaction. These modifications are then permanent. The ROLLBACK statement allows a user to undo all changes and updates made to the current transaction since the last COMMIT.
The current transaction can not be undone once it has been fully processed with the COMMIT instruction. Once you’ve used the COMMIT command to (fully) execute the current transaction, it’s impossible to undo and return it to its original state.
After the planned transaction has been completed successfully, the COMMIT statement is used. A rollback statement is issued when a transaction is terminated, there is a power outage, or the system executes incorrectly.
If all of the statements are completed properly and without errors, the COMMIT statement will store the current state. If any operations fail during the transaction’s conclusion, it means that all of the modifications were not completed correctly, and
we can undo them with the ROLLBACK statement.
When you use the commit command, the current transaction statement is saved and available to all users. The transaction state remains available to all viewers following the ROLLBACK command, but the current transaction may include
incorrect data (it may also be right).
The syntax of COMMIT is: Commit; The syntax of ROLLBACK is: Rollback;

In my stored procedure, I have three insert statements.

On duplicate key value insertion first two queries generate the error

Violation of PRIMARY KEY constraint

and third query runs as usual.

Now I want that if any query generates any exception, everything should get rolled back.

If there isn’t any exception generate by any query, it should get committed.

declare @QuantitySelected as char
    set @QuantitySelected = 2

    declare @sqlHeader as varchar(1000)
    declare @sqlTotals as varchar(1000)
    declare @sqlLine as varchar(1000)

    select @sqlHeader = 'Insert into tblKP_EstimateHeader '
    select @sqlHeader = @sqlHeader + '(CompanyID,CompanyName,ProjectName,EstimateID,EstimateHeader,QuoteDate,ValidUntil,RFQNum,Revision,Contact,Status,NumConfigurations) '
    select @sqlHeader = @sqlHeader + ' select CompanyID,CompanyName,ProjectName,EstimateID,EstimateHeader,QuoteDate,ValidUntil,RFQNum,Revision,Contact,Status,NumConfigurations '
    select @sqlHeader = @sqlHeader +  'from V_EW_Estimate_Header where EstimateID = 2203'



    select @sqlTotals = 'Insert into tblKP_Estimate_Configuration_Totals '
    select @sqlTotals = @sqlTotals + '(ConfigRecId,RecId,SellQty,ConfigNum,ConfigDesc,SortOrder,OptionsInMainPrice,MarkupPctQty,'
    select @sqlTotals = @sqlTotals + ' SellPriceQty,RubberStamp,OptPriceQty,StatusRecid,LastUpdate_Date,LastUpdate_User,TotalCost,QuantityBracketSelected)'
    select @sqlTotals = @sqlTotals + ' select ConfigRecId,RecId,SellQty' + @QuantitySelected + ',ConfigNum,ConfigDesc,SortOrder,OptionsInMainPrice'
    select @sqlTotals = @sqlTotals + ' ,MarkupPctQty' + @QuantitySelected + ',SellPriceQty' + @QuantitySelected + ',RubberStamp,OptPriceQty' + @QuantitySelected + ',StatusRecid,LastUpdate_Date,LastUpdate_User,TotalCost' + @QuantitySelected + ',' + @QuantitySelected
    select @sqlTotals = @sqlTotals + ' from v_EW_Estimate_Configuration_Totals where ConfigRecId = -3'


    select @sqlLine = 'Insert into tblKP_Estimate_Configuration_Lines'
    select @sqlLine = @sqlLine + '(MstrRfqRecId,RfqRecId,RfqLineRecId,CompanyId,VendorQuoteNum,LineGrp,LineNum,StatusRecId,'
    select @sqlLine = @sqlLine + ' LineDesc,LineSize,LineMatl,LineDeco,LineFinish,CopyFromRecId,PerPieceCost,IsOptional,'
    select @sqlLine = @sqlLine + ' CopyToNewRev,RecId,UnitPrice,LineQty,LinePrice,CustOrVend,SellQty1,RfqNum,ConfigLineIsOptional,ConfigLinePerPieceCost,ConfigLineRecid,SellPrice,SaleQty)'
    select @sqlLine = @sqlLine + ' select distinct MstrRfqRecId,RfqRecId,RfqLineRecId,CompanyId,VendorQuoteNum,LineGrp,LineNum,'
    select @sqlLine = @sqlLine + ' StatusRecId,LineDesc,LineSize,LineMatl,LineDeco,LineFinish,CopyFromRecId,PerPieceCost,IsOptional,'
    select @sqlLine = @sqlLine + ' CopyToNewRev,RecId,UnitPrice' + @QuantitySelected + ',LineQty' + @QuantitySelected + ', isnull(LinePrice' + @QuantitySelected + ', 0.0000),CustOrVend,SellQty' + @QuantitySelected + ',RfqNum,ConfigLineIsOptional,ConfigLinePerPieceCost,ConfigLineRecid,SellPrice' + @QuantitySelected + ',SaleQty' + @QuantitySelected
    select @sqlLine = @sqlLine + ' from v_EW_EstimateLine  where rfqlinerecid in (select RfqLineRecID from kp_tblVendorRfqConfigLine where ConfigRecID = -3) '

    exec( @sqlHeader)
    exec(@sqlTotals)
    exec(@sqlLine)

Понравилась статья? Поделить с друзьями:
  • Spore 1001 ошибка
  • Setracker2 ошибка привязки неверный регистрационный номер
  • Spn 791 fmi 5 ошибка kamaz
  • Setracker ошибка сети
  • Setracker обратный звонок ошибка отправки