Як відновити той самий виняток у SQL Server


86

Я хочу відновити той самий виняток у SQL Server, який щойно стався у моєму блоці try. Я можу скинути одне і те ж повідомлення, але хочу викинути ту саму помилку.

BEGIN TRANSACTION
    BEGIN TRY
        INSERT INTO Tags.tblDomain (DomainName, SubDomainId, DomainCode, Description)
            VALUES(@DomainName, @SubDomainId, @DomainCode, @Description)
        COMMIT TRANSACTION
    END TRY
    
    BEGIN CATCH
        declare @severity int; 
        declare @state int;

        select @severity=error_severity(), @state=error_state();

        RAISERROR(@@Error,@ErrorSeverity,@state);
        ROLLBACK TRANSACTION
    END CATCH

RAISERROR(@@Error, @ErrorSeverity, @state);

У цьому рядку буде показано помилку, але я хочу щось подібне до функціональності. Це спричиняє помилку з номером помилки 50000, але я хочу, щоб номер помилки був виданий я передаю @@error,

Я хочу зафіксувати цю помилку на фронтенді.

тобто

catch (SqlException ex)
{
    if ex.number==2627
    MessageBox.show("Duplicate value cannot be inserted");
}

Я хочу цю функціональність. чого неможливо досягти за допомогою raiseerror. Я не хочу видавати власне повідомлення про помилку на задній панелі.

RAISEERROR повинен повернутися нижче згаданої помилки, коли я передаю ErrorNo, щоб бути кинутим в catch

Msg 2627, Level 14, State 1, Procedure spOTest_DomainInsert,

Рядок 14 Порушення УНІКАЛЬНОГО КЛЮЧОВОГО обмеження 'UK_DomainCode'. Не вдається вставити дублікат ключа в об'єкт 'Tags.tblDomain'. Заява припинена.

РЕДАГУВАТИ:

Що може бути недоліком невикористання блоку try catch, якщо я хочу, щоб виняток оброблявся на фронтенді, враховуючи, що збережена процедура містить кілька запитів, які потрібно виконати?

Відповіді:


120

Ось повністю функціональний зразок чистого коду для відкату серії операторів у разі виникнення помилки та повідомлення про помилку.

begin try
    begin transaction;

    ...

    commit transaction;
end try
begin catch
    if @@trancount > 0 rollback transaction;
    throw;
end catch

До SQL 2012

begin try
    begin transaction;
    
    ...
    
    commit transaction;
end try
begin catch
    declare @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int;
    select @ErrorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5)), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();
    if @@trancount > 0 rollback transaction;
    raiserror (@ErrorMessage, @ErrorSeverity, @ErrorState);
end catch

8
Я використовував це в середині збереженої процедури і виявив, що вона буде продовжувати виконуватися після raiserror, що відрізняється від того, як c # виходить після a throw. Тому я додав returnвнутрішню catchчастину, тому що хотів відповідати цій поведінці.
Brian J

@BogdanBogdanov Я відкотив ваше редагування, тому що суть цього коду повинна бути мінімальною і не
погіршувати

Гаразд, не біда, @Ben Gripka. Я намагаюся зробити його більш читабельним на екрані. Дякуємо, що вказали причину відмови.
Богдан Богданов

1
@BrianJ: як правило, зупинка виконання чи ні залежить від тяжкості початкової помилки. Якщо ступінь тяжкості> 11, то виконання слід зупинити. Це насправді дивно, тому що похибка riser всередині блоку catch з тяжкістю> = 11 більше не зупиняє виконання. Ваші спостереження дуже хороші, і вони показують, наскільки мертвим є SQL Server, принаймні 2008r2. Нові версії здаються кращими.
Коста

1
@costa Див RAISERROR()«s документи . Серйозність ≥11 переходить до CATCHблоку, лише якщо він знаходиться всередині TRYблоку. Отже, ви повинні мати BEGIN TRY…END CATCHнавколо коду, якщо хочете, щоб ваш RAISERROR()вплив впливав на управління потоком.
binki

137

SQL 2012 представляє оператор throw:

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

Якщо оператор THROW вказано без параметрів, він повинен з'явитися всередині блоку CATCH. Це призводить до вилученого винятку.

BEGIN TRY
    BEGIN TRANSACTION
    ...
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    THROW
END CATCH

2
Обережно, схоже, це рішення працює лише з SQL Server 2012 і вище: msdn.microsoft.com/en-us/library/ee677615.aspx
Аді

3
@BogdanBogdanov Тож, щоб ви могли реєструвати помилку, можливо, вирішувати деякі ситуації, але якщо ви не можете, то ви хочете відновити помилку, щоб будь-яка вища спроба / зловити могла мати шанс впоратися з нею
Роберт Маккі

Так, @ Роберт Маккі. Я це зрозумів. Вибачте, це забули очистити цей коментар.
Богдан Богданов

3
Точка з комою на ROLLBACKрядку важлива! Без цього ви можете отримати SQLException: Cannot roll back THROW.
idontevenseethecode

5

Повторне потрапляння всередину блоку CATCH (код перед SQL2012, використовуйте оператор THROW для SQL2012 і пізніших версій):

DECLARE
    @ErrorMessage nvarchar(4000) = ERROR_MESSAGE(),
    @ErrorNumber int = ERROR_NUMBER(),
    @ErrorSeverity int = ERROR_SEVERITY(),
    @ErrorState int = ERROR_STATE(),
    @ErrorLine int = ERROR_LINE(),
    @ErrorProcedure nvarchar(200) = ISNULL(ERROR_PROCEDURE(), '-');
SELECT @ErrorMessage = N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' + 'Message: ' + @ErrorMessage;
RAISERROR (@ErrorMessage, @ErrorSeverity, 1, @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine)

4

Я думаю, що ваш вибір:

  • Не вловлюйте помилку (нехай вона спливає)
  • Підніміть нестандартний

У якийсь момент SQL, ймовірно, введе команду reraise або можливість виявити лише певні помилки. Але поки що використовуйте обхідний шлях. Вибачте.


7
у sql 2012 ви можете повторно викликати виняток, використовуючи нове ключове слово THROW
sergiom

5
Так. Звичайно, це не було доступно, коли було задано це питання.
Роб Фарлі

Було б важливіше вловити та викинути нову помилку, ніж не вловити її і не дати їй «пузиритися», тому що для належної обробки винятку вам, швидше за все, знадобляться дії з очищення, коригування та закриття. Очевидним прикладом може бути закриття курсора та розпорядження ним. Іншими прикладами може бути виконання процедури реєстрації або скидання деяких даних.
Ентоні Бут,

1

Ви не можете: лише движок може видавати помилки менше 50000. Все, що ви можете зробити, - це викинути вигляд, схожий на це ...

Дивіться мою відповідь тут, будь ласка

Допитувач тут використовував транзакції на стороні клієнта, щоб робити те, що він хотів, що, на мою думку, трохи дурне ...


0

Гаразд, це обхідний шлях ... :-)

DECLARE @Error_Number INT
BEGIN TRANSACTION 
    BEGIN TRY
    INSERT INTO Test(Id, Name) VALUES (newID(),'Ashish') 
    /* Column 'Name' has unique constraint on it*/
    END TRY
    BEGIN CATCH

            SELECT ERROR_NUMBER()
            --RAISERROR (@ErrorMessage,@Severity,@State)
            ROLLBACK TRAN
    END CATCH

Якщо ви зазначаєте блок catch, це не викликає помилку, а повертає фактичний номер помилки (а також відмовляє транзакцію). Тепер у коді .NET замість того, щоб ловити виняток, якщо ви використовуєте ExecuteScalar (), ви отримуєте фактичний номер помилки, який хочете, і показує відповідний номер.

int errorNumber=(int)command.ExecuteScalar();
if(errorNumber=<SomeNumber>)
{
    MessageBox.Show("Some message");
}

Сподіваюся, це допомагає,

РЕДАГУВАТИ: - Просто примітка. Якщо ви хочете отримати кількість записів, на які впливає та намагаєтесь використовувати ExecuteNonQuery, вищевказане рішення може не спрацювати для вас. В іншому випадку, я думаю, це підходило б до того, що вам потрібно. Дай мені знати.


@Ashish Gupta: Thx за допомогою, але мені потрібен виняток, який буде перекинуто з бази даних на інтерфейс, інакше у мене відкрито багато опцій, таких як print error_number (), return error_number та запропонований 1 u
Шантану Гупта

0

Спосіб зупинити виконання в збереженій процедурі після того, як сталася помилка, і повернути помилку назад до програми, що викликає, - слідувати кожному висловлюванню, яке може спричинити помилку з цим кодом:

If @@ERROR > 0
Return

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

Цей тип обробки помилок паралелей (до .Net) Visual Basic 6. З нетерпінням чекаємо команди Throw у SQL Server 2012.


0

Враховуючи те, що ви ще не перейшли на 2012 рік, одним із способів реалізувати спливаючі дії оригінального коду помилки є використання частини текстового повідомлення винятку, який ви (повторно) кидаєте з блоку catch. Пам'ятайте, що він може містити якусь структуру, наприклад, XML-текст для коду, що викликає, для синтаксичного аналізу в блоці catch.


0

Ви також можете створити процедуру обгортки, що зберігається для цих сценаріїв, коли потрібно, щоб оператор SQL виконувався в рамках транзакції та передавав помилку до вашого коду.

CREATE PROCEDURE usp_Execute_SQL_Within_Transaction
(
    @SQL nvarchar(max)
)
AS

SET NOCOUNT ON

BEGIN TRY
    BEGIN TRANSACTION
        EXEC(@SQL)
    COMMIT TRANSACTION
END TRY

BEGIN CATCH
    DECLARE @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int
    SELECT @ErrorMessage = N'Error Number: ' + CONVERT(nvarchar(5), ERROR_NUMBER()) + N'. ' + ERROR_MESSAGE() + ' Line ' + CONVERT(nvarchar(5), ERROR_LINE()), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE()
    ROLLBACK TRANSACTION
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState)
END CATCH

GO

-- Test it
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1/0; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'EXEC usp_Another_SP'

-2

З точки зору дизайну, який сенс створювати винятки з оригінальними номерами помилок та спеціальними повідомленнями? Певною мірою це порушує інтерфейсний контракт між програмами та базою даних. Якщо ви хочете виявити оригінальні помилки та обробляти їх у вищому коді, не обробляйте їх у базі даних. Потім, коли ви вловите виняток, ви можете змінити повідомлення, представлене користувачеві, на все, що завгодно. Однак я б цього не зробив, оскільки це робить код вашої бази даних хммм "неправильним". Як казали інші, вам слід визначити набір власних кодів помилок (вище 50000) і замість них кинути їх. Тоді ви можете скасувати проблеми цілісності («Повторювані значення не дозволяються») окремо від потенційних проблем бізнесу - «Поштовий індекс недійсний», «Не знайдено рядків, що відповідають критеріям» тощо.


9
Який сенс кидати винятки з оригінальними номерами помилок та спеціальними повідомленнями? Припустимо, ви хочете обробити одну або дві конкретні (очікувані) помилки безпосередньо в блоці catch, а решту залишити для вищих шарів. Таким чином, ви повинні мати можливість відновити винятки, з якими ви не обробляли ... бажано, не вдаючись до звітування та обробки помилок якимось іншим, особливим способом.
Jenda

1
На додаток до того, що пояснив @Jenda, я люблю використовувати функцію try-catch, щоб переконатися, що виконання коду не продовжується після винятку, подібно до того, як це робиться в C #:, try { code(); } catch (Exception exc) { log(exc); throw; } finally { cleanup(); }де throw;просто буде піднято вихідне виняток із початковим контекстом.
R. Schreurs

Я виявляю помилки і повторно викидаю власні повідомлення про помилки в SQL, щоб додати деталі, що описують, в якому рядку сталася помилка, або інші деталі (наприклад, дані, які намагаються вставити), щоб допомогти мені відстежити помилку пізніше.
Рассел Хенкінс,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.