Sql отключить ошибки

Anyone know how to suppress / ignore errors in a select statement?

My select statement uses the STLineFromText function, and when it hits an invalid line, it raises an error. I’m ok with ignoring the bad rows, and cannot really change my source data.

Here is a sample query that demonstrates my problem:

SELECT geography::STLineFromText('LINESTRING(-74.2204037952351 40.4283173372472,-74.2204851952350 40.4283519372471)', 4326) UNION ALL
SELECT geography::STLineFromText('LINESTRING(-74.2316367952177 40.4386102038979,-74.2313671952181 40.4388540705641)', 4326) UNION ALL
SELECT geography::STLineFromText('LINESTRING(-74.2229282618978 40.4252709372519,-74.2229171285645 40.4252638039186,-74.2229282618978 40.4252709372519,-74.2227441952315 40.4251499372521,-74.2231121285642 40.4243291372534)', 4326) UNION ALL
SELECT geography::STLineFromText('LINESTRING(-74.2418989952017 40.4417621372263,-74.2417773285352 40.4417915372263)', 4326) UNION ALL
SELECT geography::STLineFromText('LINESTRING(-74.2166069952410 40.4334496039059,-74.2158269952422 40.4336396039056)', 4326)

and here is the error:

Msg 6522, Level 16, State 1, Line 2
A .NET Framework error occurred during execution of user-defined routine or aggregate "geography": 
System.ArgumentException: 24200: The specified input does not represent a valid geography instance.
System.ArgumentException: 
   at Microsoft.SqlServer.Types.SqlGeography.ConstructGeographyFromUserInput(GeoData g, Int32 srid)
   at Microsoft.SqlServer.Types.SqlGeography.GeographyFromText(OpenGisType type, SqlChars taggedText, Int32 srid)

The ideal situation would be to just return #1, #2, #4 & #5, ignoring #3

Thanks!

Привет, Хабр! Представляю вашему вниманию перевод статьи «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


Не прерывать скрипт при ошибках, mssql

От: Аноним

 
Дата:  23.06.10 07:57
Оценка:
--1
insert tbl values( 1 )
--2
insert tbl values( 2 )

Сейчас, если в триггере происходит ошибка в первом insert, то второй не отрабатывает. Я хочу, чтоб отрабатывал. Есть способ добиться такого поведения? ms sql 2000, query analyzer.


Re: Не прерывать скрипт при ошибках, mssql

От:

igor_ku

 
Дата:  23.06.10 08:41
Оценка:

Здравствуйте, Аноним, Вы писали:

Может так:

—1
insert tbl values( 1 )
go
—2
insert tbl values( 2 )
go

А>

А>--1
А>insert tbl values( 1 )
А>--2
А>insert tbl values( 2 )

А>


А>Сейчас, если в триггере происходит ошибка в первом insert, то второй не отрабатывает. Я хочу, чтоб отрабатывал. Есть способ добиться такого поведения? ms sql 2000, query analyzer.


Re[2]: Не прерывать скрипт при ошибках, mssql

От: Аноним

 
Дата:  23.06.10 08:53
Оценка:

Здравствуйте, igor_ku, Вы писали:

_>Может так:

Спасибо, но не подходит (мой вопрос был неполон, каюсь)

declare @p int
begin transaction
 --1
 boo1( @p )
 insert tbl values( @p )
 --2
 boo2( @p )
 insert tbl values( @p )
commit transaction

то есть «go» не подходит. увы.


Re[3]: Не прерывать скрипт при ошибках, mssql

От:

alexey.kostylev

Новая Зеландия

http://alexeykostylev.livejournal.com/
Дата:  23.06.10 10:41
Оценка:

Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, igor_ku, Вы писали:


_>>Может так:


А>Спасибо, но не подходит (мой вопрос был неполон, каюсь)


А>

А>declare @p int
А>begin transaction
А> --1
А> boo1( @p )
А> insert tbl values( @p )
А> --2
А> boo2( @p )
А> insert tbl values( @p )
А>commit transaction
А>



А>то есть «go» не подходит. увы.

может dynamic sql поможет — sp_executesql?


Re: Не прерывать скрипт при ошибках, mssql

От:

Sergei MO

Россия

 
Дата:  23.06.10 10:45
Оценка:

Здравствуйте, Аноним, Вы писали:

А>Сейчас, если в триггере происходит ошибка в первом insert, то второй не отрабатывает. Я хочу, чтоб отрабатывал. Есть способ добиться такого поведения? ms sql 2000, query analyzer.

Посмотри SET XACT_ABORT OFF.
2000-го под рукой нет, но на 2005 работает.


Re[2]: Не прерывать скрипт при ошибках, mssql

От:

alexey.kostylev

Новая Зеландия

http://alexeykostylev.livejournal.com/
Дата:  23.06.10 10:49
Оценка:

Здравствуйте, Sergei MO, Вы писали:

SM>Здравствуйте, Аноним, Вы писали:


А>>Сейчас, если в триггере происходит ошибка в первом insert, то второй не отрабатывает. Я хочу, чтоб отрабатывал. Есть способ добиться такого поведения? ms sql 2000, query analyzer.


SM>Посмотри SET XACT_ABORT OFF.

SM>2000-го под рукой нет, но на 2005 работает.

первый только первый insert проходит…

SET XACT_ABORT OFF

insert into table_1 (val, s) values(1, 1)
insert into table_1 (val, s) values(1, 'A1')
insert into table_1 (val, s) values(1, 1)

SQL express 2005


Re[3]: Не прерывать скрипт при ошибках, mssql

От:

Sergei MO

Россия

 
Дата:  23.06.10 11:04
Оценка:

Здравствуйте, alexey.kostylev, Вы писали:

AK>первый только первый insert проходит…

AK>

AK>SET XACT_ABORT OFF

AK>insert into table_1 (val, s) values(1, 1)
AK>insert into table_1 (val, s) values(1, 'A1')
AK>insert into table_1 (val, s) values(1, 1)
AK>



AK>SQL express 2005

Зависит от уровня ошибки. Для несовпадения типов данных и для нарушения уникального ключа будет разное поведение.


Re[4]: Не прерывать скрипт при ошибках, mssql

От: Аноним

 
Дата:  23.06.10 11:11
Оценка:

Здравствуйте, Sergei MO, Вы писали:

SM>Зависит от уровня ошибки. Для несовпадения типов данных и для нарушения уникального ключа будет разное поведение.

Прихожу к выводу, что универсального решения нет.
Благодаря всем вам ситуация прояснилась, большое всем спасибо!


Re[3]: Не прерывать скрипт при ошибках, mssql

От:

VVP

Россия

http://web.icq.com/whitepages/online?icq=67524421&img=567524421
Дата:  23.06.10 11:18
Оценка:

+1

Здравствуйте, Аноним, Вы писали:

А>Спасибо, но не подходит (мой вопрос был неполон, каюсь)

А>

А>declare @p int
А>begin transaction
А> --1
А> boo1( @p )
А> insert tbl values( @p )
А> --2
А> boo2( @p )
А> insert tbl values( @p )
А>commit transaction
А>

то есть «go» не подходит. увы.

Выдвинутое требование противоречит понятию транзации (http://en.wikipedia.org/wiki/Database_transaction). Исключительная ситуация внутри транзакции вызывает откат всей транзакции — это поведение по умолчанию.
Можете попробовать использовать savepoint, if errors, вложенные транзакции или разделить транзакцию на две.

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

Никогда не бойся браться делать то, что делать не умеешь. Помни, ковчег был построен любителем. Профессионалы построили Титаник…


Re[3]: Не прерывать скрипт при ошибках, mssql

От:

MasterZiv

СССР

 
Дата:  23.06.10 20:56
Оценка:

Аноним 114 wrote:

> Спасибо, но не подходит (мой вопрос был неполон, каюсь)

>
> declare @p int
> begin transaction
> —1
> boo1( @p )
> insert tbl values( @p )
> —2
> boo2( @p )
> insert tbl values( @p )
> commit transaction
>
>
>
> то есть «go» не подходит. увы.

Батчи и транзакции вообще-то абсолютно независимы.
Так что GO можно было бы после первого INSERT ставить.

Но это тебе не поможет, потому что транзакция всё равно одна.

Ну так не пихай всё в одну транзакцию, делай в разных.

Posted via RSDN NNTP Server 2.1 beta


Re[5]: Не прерывать скрипт при ошибках, mssql

От:

MasterZiv

СССР

 
Дата:  23.06.10 20:59
Оценка:

Аноним 685 wrote:

> Прихожу к выводу, что универсального решения нет.

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

Posted via RSDN NNTP Server 2.1 beta


Re[6]: Не прерывать скрипт при ошибках, mssql

От: Аноним

 
Дата:  24.06.10 04:33
Оценка:

Здравствуйте, MasterZiv, Вы писали:

MZ>Аноним 685 wrote:


>> Прихожу к выводу, что универсального решения нет.


MZ>Да есть решение. Есть.

MZ>Я вот наоборот не понимаю, как ты такого поведения добился.
MZ>Триггер только текущий стейтмент откатывает, а не
MZ>всю транзакцию. Так что последующий iNSERT должен
MZ>проходить, если ты ничего не делаешь с ошибкой.

Попытался смоделировать:

-- create database testing
-- go
use testing
go
set nocount on
go

if object_id( 'dbo.Test') is not null drop table dbo.Test
create table dbo.Test
(
    id int primary key,
    name varchar(60),
)
go

create trigger validationTestValues on dbo.Test for insert, update
as
begin
    if update(name) begin
        if exists(select * from inserted where name = 'zz') begin
            raiserror('zz fail', 16, 1)
            rollback transaction
            return
        end
    end
end
go

-- Testing

declare @p int
set @p = 1 insert dbo.Test values( @p, '' )
set @p = 2 insert dbo.Test values( @p, 'zz' )
set @p = 3 insert dbo.Test values( @p, '' )
go

update dbo.Test set name = 'aa' where id = 1
update dbo.Test set name = 'zz' where id = 2
update dbo.Test set name = 'bb' where id = 3 
go
print 'Надо дойти сюда...'

select * from dbo.Test

От: Аноним

 
Дата:  24.06.10 04:45
Оценка:

Здравствуйте, Аноним, Вы писали:

...
set @p = 3 insert dbo.Test values( @p, '' )
-- go

update dbo.Test set name = 'aa' where id = 1
update dbo.Test set name = 'zz' where id = 2
update dbo.Test set name = 'bb' where id = 3 
-- go
print 'Надо дойти сюда...'
...


Re[7]: Не прерывать скрипт при ошибках, mssql

От:

MasterZiv

СССР

 
Дата:  24.06.10 05:20
Оценка:

Аноним 136 wrote:

> update dbo.Test set name = ‘aa’ where id = 1

> update dbo.Test set name = ‘zz’ where id = 2
> update dbo.Test set name = ‘bb’ where id = 3
> go
> print ‘Надо дойти сюда…’
>
> select * from dbo.Test

update dbo.Test set name = ‘aa’ where id = 1
go
update dbo.Test set name = ‘zz’ where id = 2
go
update dbo.Test set name = ‘bb’ where id = 3
go
print ‘Надо дойти сюда…’

select * from dbo.Test

Posted via RSDN NNTP Server 2.1 beta


Re[7]: Не прерывать скрипт при ошибках, mssql

От:

MasterZiv

СССР

 
Дата:  24.06.10 05:23
Оценка:

Аноним 136 wrote:

Кстати, до туда оно дойдёт и в этом варианте

> update dbo.Test set name = ‘aa’ where id = 1

> update dbo.Test set name = ‘zz’ where id = 2
> update dbo.Test set name = ‘bb’ where id = 3
— этот батч будет остановлен при ошибке в триггере
> go

— А этот батч будет послан консолькой следующим независимо от
— того, как завершился предыдущий.
> print ‘Надо дойти сюда…’
>
> select * from dbo.Test

Но я так понимаю, что реальная цель — попытаться выполнить
все три UPDATE-а, а не дойти до select * from dbo.Test

Posted via RSDN NNTP Server 2.1 beta


Re[7]: Не прерывать скрипт при ошибках, mssql

От:

MasterZiv

СССР

 
Дата:  24.06.10 05:26
Оценка:

Аноним 136 wrote:

Источник просветления тут:
http://msdn.microsoft.com/en-us/library/aa213074%28SQL.80%29.aspx

Posted via RSDN NNTP Server 2.1 beta


Re[8]: Не прерывать скрипт при ошибках, mssql

От: Аноним

 
Дата:  24.06.10 05:43
Оценка:

Здравствуйте, MasterZiv, Вы писали:

MZ>update dbo.Test set name = ‘aa’ where id = 1

MZ>go
MZ>update dbo.Test set name = ‘zz’ where id = 2
MZ>go
MZ>update dbo.Test set name = ‘bb’ where id = 3
MZ>go
MZ>print ‘Надо дойти сюда…’

MZ>select * from dbo.Test

MasterZiv, я понимаю, что в моем сценарии не все гладко, но go не подходит, еще один патч:

declare @nm varchar(2)
set @nm = 'aa' update dbo.Test set name = @nm where id = 1
set @nm = 'zz' update dbo.Test set name = @nm where id = 2 -- (2)
set @nm = 'bb' update dbo.Test set name = @nm where id = 3

Поэтому я и написал, что, видимо, не получится сделать так чтобы (2) не рвал батч. (Кстати в 2008 прямо так и говорится: «..Выполнение пакета прервано»)

Уже занимаюсь разработкой специального (внешнего по отношению к T-SQL) функционала для такого сценария.
Я надеялся получить просветление и узнать о каких-либо параметрах set (вроде xactabort) которые позволят игнорировать откат в триггере и продолжить батч.

MasterZiv, спасибо за ответы.


Re[9]: Не прерывать скрипт при ошибках, mssql

От:

alexey.kostylev

Новая Зеландия

http://alexeykostylev.livejournal.com/
Дата:  24.06.10 05:45
Оценка:

Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, MasterZiv, Вы писали:


MZ>>update dbo.Test set name = ‘aa’ where id = 1

MZ>>go
MZ>>update dbo.Test set name = ‘zz’ where id = 2
MZ>>go
MZ>>update dbo.Test set name = ‘bb’ where id = 3
MZ>>go
MZ>>print ‘Надо дойти сюда…’

MZ>>select * from dbo.Test


А>MasterZiv, я понимаю, что в моем сценарии не все гладко, но go не подходит, еще один патч:


А>

А>declare @nm varchar(2)
А>set @nm = 'aa' update dbo.Test set name = @nm where id = 1
А>set @nm = 'zz' update dbo.Test set name = @nm where id = 2 -- (2)
А>set @nm = 'bb' update dbo.Test set name = @nm where id = 3
А>


А>Поэтому я и написал, что, видимо, не получится сделать так чтобы (2) не рвал батч. (Кстати в 2008 прямо так и говорится: «..Выполнение пакета прервано»)

А>Уже занимаюсь разработкой специального (внешнего по отношению к T-SQL) функционала для такого сценария.

А>Я надеялся получить просветление и узнать о каких-либо параметрах set (вроде xactabort) которые позволят игнорировать откат в триггере и продолжить батч.

А>MasterZiv, спасибо за ответы.

может я чего-то не понимаю, почему не использовать динамический SQL?


Re[8]: Не прерывать скрипт при ошибках, mssql

От: Аноним

 
Дата:  24.06.10 05:49
Оценка:

Здравствуйте, MasterZiv, Вы писали:

MZ>Но я так понимаю, что реальная цель — попытаться выполнить

MZ>все три UPDATE-а, а не дойти до select * from dbo.Test

Ну, чтобы расставить все точки над i, должен сказать, что как раз наоборот: цель — добраться до конца.
(это некое подобие unit-тестирования схемы бд)


Re[10]: Не прерывать скрипт при ошибках, mssql

От: Аноним

 
Дата:  24.06.10 05:54
Оценка:

Здравствуйте, alexey.kostylev, Вы писали:

AK>может я чего-то не понимаю, почему не использовать динамический SQL?

Почему же, вполне рабочий вариант. Но очень громоздко синтаксически. Для меня проще, в данном случае, embedded sql решения.


Re[11]: Не прерывать скрипт при ошибках, mssql

От:

alexey.kostylev

Новая Зеландия

http://alexeykostylev.livejournal.com/
Дата:  24.06.10 05:55
Оценка:

Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, alexey.kostylev, Вы писали:


AK>>может я чего-то не понимаю, почему не использовать динамический SQL?


А>Почему же, вполне рабочий вариант. Но очень громоздко синтаксически. Для меня проще, в данном случае, embedded sql решения.

ну так заворачивать в sp и сопровождать легче будет


Re[9]: Не прерывать скрипт при ошибках, mssql

От:

MasterZiv

СССР

 
Дата:  24.06.10 06:09
Оценка:

Аноним 136 wrote:

> Я надеялся получить просветление и узнать о каких-либо параметрах set

> (вроде xactabort) которые позволят игнорировать откат в триггере и
> продолжить батч.

Так посылай каждый оператор отдельным батчем, в чём проблема ?

Posted via RSDN NNTP Server 2.1 beta


Re[9]: Не прерывать скрипт при ошибках, mssql

От:

MasterZiv

СССР

 
Дата:  24.06.10 06:11
Оценка:

Аноним 136 wrote:

> Ну, чтобы расставить все точки над i, должен сказать, что как раз

> наоборот: цель — добраться до конца.

Тогда в чём проблема, я опять не понимаю. Последний батч должен
отрабатывать, он отдельный.

Posted via RSDN NNTP Server 2.1 beta


Re[10]: Не прерывать скрипт при ошибках, mssql

От: Аноним

 
Дата:  24.06.10 06:12
Оценка:

Здравствуйте, MasterZiv, Вы писали:

MZ>Аноним 136 wrote:


>> Я надеялся получить просветление и узнать о каких-либо параметрах set

>> (вроде xactabort) которые позволят игнорировать откат в триггере и
>> продолжить батч.

MZ>Так посылай каждый оператор отдельным батчем, в чём проблема ?

Я же уже объяснил, вопрос не в том как обойти ограничение. А в том — как его снять.


Re[12]: Не прерывать скрипт при ошибках, mssql

От: Аноним

 
Дата:  24.06.10 06:17
Оценка:

Здравствуйте, alexey.kostylev, Вы писали:

AK>ну так заворачивать в sp и сопровождать легче будет

Сомневаюсь честно говоря. Пример можешь небольшой? Embedded выглядит примерно так:

...
int value = 10;
BEGIN_SQL
insert dbo.test values ( :value )
END_SQL

value = 11;
BEGIN_SQL
insert dbo.test values ( :value )
END_SQL
...


Re: Не прерывать скрипт при ошибках, mssql

От: Аноним

 
Дата:  24.06.10 10:54
Оценка:

Здравствуйте, Аноним, Вы писали:

А>

А>--1
А>insert tbl values( 1 )
А>--2
А>insert tbl values( 2 )

А>



А>Сейчас, если в триггере происходит ошибка в первом insert, то второй не отрабатывает. Я хочу, чтоб отрабатывал. Есть способ добиться такого поведения? ms sql 2000, query analyzer.

Может в TRY/CATCH завернуть?

Подождите ...

Wait...

  • Переместить
  • Удалить
  • Выделить ветку

Пока на собственное сообщение не было ответов, его можно удалить.

Slavaga

Можно ли как-нибудь запретить SQL Query Analyzer’у выводить строку (1 row(s) affected) после каждого INSERT’а?
Чтобы выводил только ошибки выполнения запроса?
И еще. Существует ли какая-нибудь альтернатива SQL QA?

evgen5555

Enterprise Manager вроде умеет визуально запросы строить.
Я в прошлом семестре полностью Drag&Drop-ом прогу на C# накатал, со всеми XSLT и SQL-запросами.

foxie84

>Я в прошлом семестре
Сколько лет назад он был?

Slavaga

Enterprise Manager вроде умеет визуально запросы строить.

Это-то понятно, но нужно именно скрипт написать, который будет все сам делать.

Marikun

В sql analyzere в меню tools->options->connection properties надо поставить галку set nocount. То же самое можно сделать с помощью sql: SET NOCOUNT ON
MSDN

I have a table in my database with a string for a primary_key, which I set. I need to check frequently if around 1000 «items» exist in that table before adding them, because there must not be any duplicates. The result is 2 queries per Item, or 2000 total, which is 1-2 seconds of extra loading time.

If I try to insert the new row anyway, without checking for duplicates it doesn’t get inserted and that’s great, but mysql returns an error, which crashes my service.

My questions:

  1. Can I turn off those errors?
  2. Is there a better way to prevent inserting duplicates than making an extra query?

asked Jun 18, 2013 at 12:15

Nikolay Dyankov's user avatar

Nikolay DyankovNikolay Dyankov

6,32111 gold badges56 silver badges79 bronze badges

5

You could use the IGNORE keyword to have duplicates dropped from your inserts:

INSERT IGNORE INTO yourTable Values (...)

answered Jun 18, 2013 at 12:17

Sirko's user avatar

0

mysql returns an error, which crashes my service.

it’s actually your own code that crashes your service as mysql error cannot crash anything.

Is there a better way to prevent inserting duplicates

INSERT IGNORE

answered Jun 18, 2013 at 12:17

Your Common Sense's user avatar

Your Common SenseYour Common Sense

158k40 gold badges213 silver badges339 bronze badges

0

  • Remove From My Forums
  • Question

  • My project have to deal with huge database. At worst situation, it can be
    more than 80 millions row.

    Now, I have 2 table T1 and T2. I have to copy data from table T1 to table T2

    • if a row in table T1 already exist in table T2 (have the same primary key), then update data of other columns of the row in T1 to T2
    • else insert new row to T2

    At first, I use while loop to loop through 80 millions row in T1 then update or insert to T2. This is very very very slow, it take about >10 hours to finish — But, if any row is error, i can ignore it and also catch the error.

    After that, I use a query like:

    update Table2 set T2.Column1 = T1.Column1,T2.Column2=T1.Column2 from Table2 T2 JOIN Table1 T1 ON T1.ID=T2.ID

    This is much faster, only take about 1->2 hours to finish. But, if any row is error, the query can not execute at all.

    So, my question is:

    — Is there any way that above query can ignore error row and continue execute with valid row.

    — If there is no way I can do that, what can I do to run faster than the first method and also can catch error row.

    p/s: I have try to split the table to multiple small part then update or insert all small part in the same time, but it didn’t faster at all.

Всем привет! Сегодня я расскажу об ошибке «Сохранение изменений запрещено», которая возникает в среде SQL Server Management Studio при работе с конструктором таблиц, будут рассмотрены причины ее возникновения и, конечно же, способы исправления данной ошибки.

Ошибка «Сохранение изменений запрещено» в среде SSMS. Причины и способы устранения

Заметка! Обзор функционала SQL Server Management Studio (SSMS).

Содержание

  1. Ошибка «Сохранение изменений запрещено»
  2. Причины возникновения ошибки «Сохранение изменений запрещено»
  3. Способы устранения ошибки «Сохранение изменений запрещено»
  4. Использовать T-SQL
  5. Отключить параметр «Запретить сохранение изменений, требующих повторного создания таблицы»

Ошибка «Сохранение изменений запрещено»

Итак, ситуация: Вы вносите изменения в таблицу с помощью конструктора в среде SQL Server Management Studio, однако при попытке сохранить изменения Вы получаете следующую ошибку

Сохранение изменений запрещено. Чтобы сохранить изменения, необходимо удалить и повторно создать следующие таблицы. Либо изменения вносятся в таблицу, которую невозможно создать повторно, либо включен параметр «Запретить сохранение изменений, требующих повторного создания таблицы».

Скриншот 1

Причины возникновения ошибки «Сохранение изменений запрещено»

Дело в том, что при изменении таблицы с помощью конструктора с изменением структуры ее метаданных, чтобы сохранить все изменения, необходимо пересоздать таблицу на основе этих изменений, т.е. создать ее заново. Вы этого не видите, но это будет делать сама среда Management Studio.

Однако это действие может привести к потере метаданных и прямой потере данных во время повторного создания таблицы.

Поэтому по умолчанию в среде SQL Server Management Studio включен параметр «Запретить сохранение изменений, требующих повторного создания таблицы». И если Вы используете графический конструктор таблиц, чтобы внести изменения в таблицу, например, Вы выполняете следующие действия:

  • Меняете параметр «Разрешить значения NULL» для столбца;
  • Изменяете порядок столбцов в таблице;
  • Изменяете тип данных столбца;
  • Добавляете новый столбец.

то в этих случаях Вы будете получать именно такую ошибку.

Способы устранения ошибки «Сохранение изменений запрещено»

Вы можете спросить, «а как же тогда вносить изменения в таблицы, если существует прямой запрет на внесения изменений?».

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

Использовать T-SQL

Первый, и рекомендованный – это использовать инструкции T-SQL.

Заметка! Что такое T-SQL. Подробное описание для начинающих.

В качестве примера давайте представим, что у нас есть таблица Goods, и она имеет следующие данные.

   
   CREATE TABLE Goods(
	  ProductId INT IDENTITY(1,1) NOT NULL,
	  Category INT NOT NULL,
	  ProductName VARCHAR(100) NOT NULL,
	  Price MONEY NULL
   );

   INSERT INTO Goods (Category, ProductName, Price)
	    VALUES (1, 'Системный блок', 50),
		   (1, 'Клавиатура', 30),
		   (1, 'Монитор', 100),
		   (2, 'Планшет', 150),
		   (2, 'Смартфон', 100);

   SELECT * FROM Goods;

Скриншот 2

Заметка! Если Вас интересует язык SQL, то рекомендую почитать книгу «SQL код» – это самоучитель по языку SQL для начинающих программистов. В ней очень подробно рассмотрены основные конструкции языка.

Изменяем параметр «Разрешить значения NULL»

Как видим, столбец Price на текущий момент у нас может принимать значения NULL, однако мы решили сделать этот столбец обязательным к заполнению и запретить хранение в нем значений NULL.

Курс по SQL для начинающих

Если мы будем использовать конструктор таблиц, то мы получим ошибку «Сохранение изменений запрещено».

Чтобы запретить хранение NULL значений, мы можем выполнить следующую инструкцию SQL

   
   ALTER TABLE Goods ALTER COLUMN Price MONEY NOT NULL;

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

Изменяем тип данных столбца

Если необходимо изменить тип данных столбца, то нужно написать практически точно такую же инструкцию, только при этом указав новый тип данных.

   
   ALTER TABLE Goods ALTER COLUMN Price NUMERIC(18,2) NOT NULL;

В данном случае мы изменили тип данных столбца Price с MONEY на NUMERIC.

Добавляем новый столбец

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

   
   ALTER TABLE Goods ADD ProductDescription VARCHAR(300) NULL;

В этом примере мы добавили столбец ProductDescription с типом данных VARCHAR.

К сожалению, изменение порядка столбцов в таблице на языке T-SQL не поддерживается, это возможно только путем пересоздания таблицы.

Заметка! Транзакции в T-SQL – основы для новичков с примерами.

Отключить параметр «Запретить сохранение изменений, требующих повторного создания таблицы»

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

Однако Microsoft не рекомендует отключать этот параметр, ссылаясь на то, что при определенных обстоятельствах сохранение изменений, требующих повторного создания таблицы, может привести к потере метаданных и прямой потере данных. Например, если у таблицы включен функционал «Отслеживания изменений».

В большинстве случаев потери данных, конечно же, не будет происходить, поэтому данный параметр отключить можно, но лучше использовать SQL.

Чтобы отключить данный параметр, зайдите в SSMS в меню «Сервис -> Параметры» и на вкладке «Конструкторы» снимите галочку «Запретить сохранение изменений, требующих повторного создания таблицы» и нажмите «ОК».

Скриншот 3

После этого Вы сможете сохранять любые изменения в таблицах с помощью графического конструктора.

Заметка! Курсы по Transact-SQL для начинающих.

На сегодня это все, надеюсь, материал был Вам полезен, пока!

Понравилась статья? Поделить с друзьями:
  • Sql access ошибка синтаксиса пропущен оператор
  • Sql 303 ошибка
  • Spyder python ошибка
  • Spv69t70ru ошибка e09
  • Spu orb произошли некритические ошибки