Как вызвать ошибку сим карты

message_text — сообщение, которое вы хотите показать при ошибке. Замечание: вы можете добавлять пользовательские сообщения для вывода информации об ошибке. Смотрите следующий раздел статьи.
message_id — id сообщения об ошибке. Если вы хотите вывести пользовательское сообщение, вы можете определить этот идентификатор. Посмотрите список идентификаторов сообщений в sys.messages DMV.
Запрос:

select * from sys.messages

Вывод:

severity — серьезность ошибки. Тип данных переменной severity — smallint, значения находятся в диапазоне от 0 до 25. Допустимыми значениями серьезности ошибки являются:

  • 0-10
  • — информационные сообщения

  • 11-18
  • — ошибки

  • 19-25
  • — фатальные ошибки

Замечание: Если вы создаете пользовательское сообщение, сложность, указанная в этом сообщении, будет перебиваться сложностью, заданной в операторе RAISERROR.
state — уникальное идентификационное число, которое может использоваться для раздела кода, вызывающего ошибку. Тип данных параметра state — smallint, и допустимые значения между 0 и 255.

Теперь давайте перейдем к практическим примерам.

Пример 1: использование оператора SQL Server RAISERROR для вывода сообщения

В этом примере вы можете увидеть, как можно отобразить ошибку или информационное сообщение с помощью оператора RAISERROR.

Предположим, что вы хотите отобразить сообщение после вставки записей в таблицу. Мы можем использовать операторы PRINT или RAISERROR. Ниже — код:

SET nocount ON 
INSERT INTO tblpatients
(patient_id,
patient_name,
address,
city)
VALUES ('OPD00006',
'Nimesh Upadhyay',
'AB-14, Ratnedeep Flats',
'Mehsana')
RAISERROR ( 'Patient detail added successfully',1,1)

Вывод:

Как видно на рисунке выше, ID сообщения равно 50000, поскольку это пользовательское сообщение.

Пример 2: оператор SQL RAISERROR с текстом динамического сообщения

Теперь посмотрите, как мы можем создать текст динамического сообщения для оператора SQL RAISERROR.

Предположим, что мы хотим напечатать в сообщении ID пациента. Я описал локальную переменную с именем @PatientID, которая содержит patient_id. Чтобы отобразить значение переменной @PatientID в тексте сообщения, мы можем использовать следующий код:

DECLARE @PatientID VARCHAR(15) 
DECLARE @message NVARCHAR(max)
SET @PatientID='OPD00007'
SET @message ='Patient detail added successfully. The OPDID is %s'
INSERT INTO tblpatients
(patient_id,
patient_name,
address,
city)
VALUES ('' + @PatientID + '',
'Nimesh Upadhyay',
'AB-14, Ratnedeep Flats',
'Mehsana')
RAISERROR ( @message,1,1,@patientID)

Вывод:

Для отображения строки в операторе RAISERROR, мы должны использовать операторы print в стиле языка Си.

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

Использование SQL RAISERROR в блоке TRY..CATCH

В этом примере мы добавляем SQL RAISERROR в блок TRY. При запуске этого кода он выполняет связанный блок CATCH. В блоке CATCH мы будем выводить подробную информацию о возникшей ошибке.

BEGIN try 
RAISERROR ('Error invoked in the TRY code block.',16,1 );
END try
BEGIN catch
DECLARE @ErrorMsg NVARCHAR(4000);
DECLARE @ErrSeverity INT;
DECLARE @ErrState INT;
SELECT @ErrorMsg = Error_message(),
@ErrSeverity = Error_severity(),
@ErrState = Error_state();
RAISERROR (@ErrorMsg,
@ErrSeverity,
@ErrState
);
END catch;

Так мы добавили оператор RAISERROR с ВАЖНОСТЬЮ МЕЖДУ 11 И 19. Это вызывает выполнение блока CATCH.

В блоке CATCH мы показываем информацию об исходной ошибке, используя оператор RAISERROR.
Вывод:

Как вы можете увидеть, код вернул информацию об исходной ошибке.

Теперь давайте разберемся, как добавить пользовательское сообщение, используя хранимую процедуру sp_addmessage.

Хранимая процедура sp_addmessage

Мы можем добавить пользовательское сообщение, выполнив хранимую процедуру sp_addmessages. Синтаксис процедуры:

EXEC Sp_addmessage 
@msgnum= 70001,
@severity=16,
@msgtext='Please enter the numeric value',
@lang=NULL,
@with_log='TRUE',
@replace='Replace';

@msgnum: задает номер сообщения. Тип данных параметра — integer. Это ID пользовательского сообщения.
@severity: указывает уровень серьезности ошибки. Допустимые значения от 1 до 25. Тип данных параметра — smallint.
@messagetext: задает текст сообщения, который вы хотите выводить. Тип данных параметра nvarchar(255), значение по умолчанию NULL.
@lang: задает язык, который вы хотите использовать для вывода сообщения об ошибке. Значение по умолчанию NULL.
@with_log: этот параметр используется для записи сообщения в просмотрщик событий. Допустимые значения TRUE и FALSE. Если вы задаете TRUE, сообщение об ошибке будет записано в просмотрщик событий Windows. Если выбрать FALSE, ошибка не будет записана в журнал ошибок Windows.
@replace: если вы хотите заменить существующее сообщение об ошибке на пользовательское сообщение и уровень серьезности, вы можете указать это в хранимой процедуре.

Предположим, что вы хотите создать сообщение об ошибке, которое возвращает ошибку о недопустимом качестве (invalid quality). В операторе INSERT значение invalid_quality находится в диапазоне между 20 и 100. Сообщение следует рассматривать как ошибку с уровнем серьезности 16.

Чтобы создать такое сообщение, выполните следующий запрос:

USE master;
go
EXEC Sp_addmessage
70001,
16,
N'Product Quantity must be between 20 and 100.';
go

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

USE master 
go
SELECT * FROM sys.messages WHERE message_id = 70001

Вывод:

Как использовать пользовательские сообщения об ошибках

Как упоминалось выше, мы должны использовать message_id в операторе RAISERROR для пользовательских сообщений.

Мы создали сообщение с ID = 70001. Оператор RAISERROR должен быть таким:

USE master 
go
RAISERROR (70001,16,1 );
go

Вывод:

Оператор RAISERROR вернул пользовательское сообщение.

Хранимая процедура sp_dropmessage

Хранимая процедура sp_dropmessage используется для удаления пользовательских сообщений. Синтаксис оператора:

EXEC Sp_dropmessage @msgnum

Здесь @msgnum задает ID сообщения, которое вы хотите удалить.

Теперь мы хотим удалить сообщение, с ID = 70001. Запрос:

EXEC Sp_dropmessage 70001

Выполним следующий запрос для просмотра сообщения после его удаления:

USE master 
go
SELECT * FROM sys.messages WHERE message_id = 70001

Вывод:

Как видно, сообщение было удалено.

title description author ms.author ms.date ms.service ms.subservice ms.topic f1_keywords helpviewer_keywords dev_langs monikerRange

RAISERROR (Transact-SQL)

RAISERROR (Transact-SQL)

rwestMSFT

randolphwest

08/09/2022

sql

t-sql

reference

RAISERROR

RAISERROR_TSQL

RAISEERROR_TSQL

sysmessages system table

errors [SQL Server], RAISERROR statement

user-defined error messages [SQL Server]

system flags

generating errors [SQL Server]

TRY block [SQL Server]

recording errors

ad hoc messages

RAISERROR statement

CATCH block

messages [SQL Server], RAISERROR statement

TSQL

>= aps-pdw-2016 || = azuresqldb-current || = azure-sqldw-latest || >= sql-server-2016 || >= sql-server-linux-2017 || = azuresqldb-mi-current||=fabric

[!INCLUDE sql-asdb-asdbmi-asa-pdw-fabricse-fabricdw]

[!NOTE]
The RAISERROR statement does not honor SET XACT_ABORT. New applications should use THROW instead of RAISERROR.

Generates an error message and initiates error processing for the session. RAISERROR can either reference a user-defined message stored in the sys.messages catalog view, or build a message dynamically. The message is returned as a server error message to the calling application or to an associated CATCH block of a TRY...CATCH construct. New applications should use THROW instead.

:::image type=»icon» source=»../../includes/media/topic-link-icon.svg» border=»false»::: Transact-SQL syntax conventions

Syntax

Syntax for SQL Server and Azure SQL Database:

RAISERROR ( { msg_id | msg_str | @local_variable }
    { , severity, state }
    [ , argument [ , ...n ] ] )
    [ WITH option [ , ...n ] ]

Syntax for Azure Synapse Analytics and Parallel Data Warehouse:

RAISERROR ( { msg_str | @local_variable }
    { , severity, state }
    [ , argument [ , ...n ] ] )
    [ WITH option [ , ...n ] ]

[!INCLUDEsql-server-tsql-previous-offline-documentation]

Arguments

msg_id

A user-defined error message number stored in the sys.messages catalog view using sp_addmessage. Error numbers for user-defined error messages should be greater than 50000. When msg_id is not specified, RAISERROR raises an error message with an error number of 50000.

msg_str

A user-defined message with formatting similar to the printf function in the C standard library. The error message can have a maximum of 2,047 characters. If the message contains 2,048 or more characters, only the first 2,044 are displayed and an ellipsis is added to indicate that the message has been truncated. Note that substitution parameters consume more characters than the output shows because of internal storage behavior. For example, the substitution parameter of %d with an assigned value of 2 actually produces one character in the message string but also internally takes up three additional characters of storage. This storage requirement decreases the number of available characters for message output.

When msg_str is specified, RAISERROR raises an error message with an error number of 50000.

msg_str is a string of characters with optional embedded conversion specifications. Each conversion specification defines how a value in the argument list is formatted and placed into a field at the location of the conversion specification in msg_str. Conversion specifications have this format:

% [[flag] [width] [. precision] [{h | l}]] type

The parameters that can be used in msg_str are:

flag

A code that determines the spacing and justification of the substituted value.

Code Prefix or justification Description
— (minus) Left-justified Left-justify the argument value within the given field width.
+ (plus) Sign prefix Preface the argument value with a plus (+) or minus (-) if the value is of a signed type.
0 (zero) Zero padding Preface the output with zeros until the minimum width is reached. When 0 and the minus sign (-) appear, 0 is ignored.
# (number) 0x prefix for hexadecimal type of x or X When used with the o, x, or X format, the number sign (#) flag prefaces any nonzero value with 0, 0x, or 0X, respectively. When d, i, or u are prefaced by the number sign (#) flag, the flag is ignored.
‘ ‘ (blank) Space padding Preface the output value with blank spaces if the value is signed and positive. This is ignored when included with the plus sign (+) flag.

width

An integer that defines the minimum width for the field into which the argument value is placed. If the length of the argument value is equal to or longer than width, the value is printed with no padding. If the value is shorter than width, the value is padded to the length specified in width.

An asterisk (*) means that the width is specified by the associated argument in the argument list, which must be an integer value.

precision

The maximum number of characters taken from the argument value for string values. For example, if a string has five characters and precision is 3, only the first three characters of the string value are used.

For integer values, precision is the minimum number of digits printed.

An asterisk (*) means that the precision is specified by the associated argument in the argument list, which must be an integer value.

{h | l} type

Used with character types d, i, o, s, x, X, or u, and creates shortint (h) or longint (l) values.

Type specification Represents
d or i Signed integer
o Unsigned octal
s String
u Unsigned integer
x or X Unsigned hexadecimal

These type specifications are based on the ones originally defined for the printf function in the C standard library. The type specifications used in RAISERROR message strings map to [!INCLUDEtsql] data types, while the specifications used in printf map to C language data types. Type specifications used in printf are not supported by RAISERROR when [!INCLUDEtsql] does not have a data type similar to the associated C data type. For example, the %p specification for pointers is not supported in RAISERROR because [!INCLUDEtsql] does not have a pointer data type.

To convert a value to the [!INCLUDEtsql] bigint data type, specify %I64d.

@local_variable

Is a variable of any valid character data type that contains a string formatted in the same manner as msg_str. @local_variable must be char or varchar, or be able to be implicitly converted to these data types.

severity

The user-defined severity level associated with this message. When using msg_id to raise a user-defined message created using sp_addmessage, the severity specified on RAISERROR overrides the severity specified in sp_addmessage.

For severity levels from 19 through 25, the WITH LOG option is required. Severity levels less than 0 are interpreted as 0. Severity levels greater than 25 are interpreted as 25.

[!CAUTION]
Severity levels from 20 through 25 are considered fatal. If a fatal severity level is encountered, the client connection is terminated after receiving the message, and the error is logged in the error and application logs.

You can specify -1 to return the severity value associated with the error as shown in the following example.

RAISERROR (15600, -1, -1, 'mysp_CreateCustomer');

[!INCLUDEssResult]

Msg 15600, Level 15, State 1, Line 1
An invalid parameter or option was specified for procedure 'mysp_CreateCustomer'.

state

An integer from 0 through 255. Negative values default to 1. Values larger than 255 should not be used.

If the same user-defined error is raised at multiple locations, using a unique state number for each location can help find which section of code is raising the errors.

argument

The parameters used in the substitution for variables defined in msg_str or the message corresponding to msg_id. There can be 0 or more substitution parameters, but the total number of substitution parameters cannot exceed 20. Each substitution parameter can be a local variable or any of these data types: tinyint, smallint, int, char, varchar, nchar, nvarchar, binary, or varbinary. No other data types are supported.

option

A custom option for the error and can be one of the values in the following table.

Value Description
LOG Logs the error in the error log and the application log for the instance of the [!INCLUDEmsCoName] [!INCLUDEssNoVersion] [!INCLUDEssDE]. Errors logged in the error log are currently limited to a maximum of 440 bytes. Only a member of the sysadmin fixed server role or a user with ALTER TRACE permissions can specify WITH LOG.

[!INCLUDEapplies] [!INCLUDEssNoVersion]

NOWAIT Sends messages immediately to the client.

[!INCLUDEapplies] [!INCLUDEssNoVersion], [!INCLUDEssSDS]

SETERROR Sets the @@ERROR and ERROR_NUMBER values to msg_id or 50000, regardless of the severity level.

[!INCLUDEapplies] [!INCLUDEssNoVersion], [!INCLUDEssSDS]

Remarks

The errors generated by RAISERROR operate the same as errors generated by the [!INCLUDEssDE] code. The values specified by RAISERROR are reported by the ERROR_LINE, ERROR_MESSAGE, ERROR_NUMBER, ERROR_PROCEDURE, ERROR_SEVERITY, ERROR_STATE, and @@ERROR system functions. When RAISERROR is run with a severity of 11 or higher in a TRY block, it transfers control to the associated CATCH block. The error is returned to the caller if RAISERROR is run:

  • Outside the scope of any TRY block.

  • With a severity of 10 or lower in a TRY block.

  • With a severity of 20 or higher that terminates the database connection.

CATCH blocks can use RAISERROR to rethrow the error that invoked the CATCH block by using system functions such as ERROR_NUMBER and ERROR_MESSAGE to retrieve the original error information. @@ERROR is set to 0 by default for messages with a severity from 1 through 10.

When msg_id specifies a user-defined message available from the sys.messages catalog view, RAISERROR processes the message from the text column using the same rules as are applied to the text of a user-defined message specified using msg_str. The user-defined message text can contain conversion specifications, and RAISERROR will map argument values into the conversion specifications. Use sp_addmessage to add user-defined error messages and sp_dropmessage to delete user-defined error messages.

RAISERROR can be used as an alternative to PRINT to return messages to calling applications. RAISERROR supports character substitution similar to the functionality of the printf function in the C standard library, while the [!INCLUDEtsql] PRINT statement does not. The PRINT statement is not affected by TRY blocks, while a RAISERROR run with a severity of 11 to 19 in a TRY block transfers control to the associated CATCH block. Specify a severity of 10 or lower to use RAISERROR to return a message from a TRY block without invoking the CATCH block.

Typically, successive arguments replace successive conversion specifications; the first argument replaces the first conversion specification, the second argument replaces the second conversion specification, and so on. For example, in the following RAISERROR statement, the first argument of N'number' replaces the first conversion specification of %s; and the second argument of 5 replaces the second conversion specification of %d.

RAISERROR (N'This is message %s %d.', -- Message text.
           10, -- Severity,
           1, -- State,
           N'number', -- First argument.
           5); -- Second argument.
-- The message text returned is: This is message number 5.
GO

If an asterisk (*) is specified for either the width or precision of a conversion specification, the value to be used for the width or precision is specified as an integer argument value. In this case, one conversion specification can use up to three arguments, one each for the width, precision, and substitution value.

For example, both of the following RAISERROR statements return the same string. One specifies the width and precision values in the argument list; the other specifies them in the conversion specification.

RAISERROR (N'<<%*.*s>>', -- Message text.
           10, -- Severity,
           1, -- State,
           7, -- First argument used for width.
           3, -- Second argument used for precision.
           N'abcde'); -- Third argument supplies the string.
-- The message text returned is: <<    abc>>.
GO
RAISERROR (N'<<%7.3s>>', -- Message text.
           10, -- Severity,
           1, -- State,
           N'abcde'); -- First argument supplies the string.
-- The message text returned is: <<    abc>>.
GO

Permissions

Severity levels from 0 through 18 can be specified by any user. Severity levels from 19 through 25 can only be specified by members of the sysadmin fixed server role or users with ALTER TRACE permissions.

Examples

A. Returning error information from a CATCH block

The following code example shows how to use RAISERROR inside a TRY block to cause execution to jump to the associated CATCH block. It also shows how to use RAISERROR to return information about the error that invoked the CATCH block.

[!NOTE]
RAISERROR only generates errors with state from 1 through 127. Because the [!INCLUDEssDE] may raise errors with state 0, we recommend that you check the error state returned by ERROR_STATE before passing it as a value to the state parameter of RAISERROR.

BEGIN TRY
    -- RAISERROR with severity 11-19 will cause execution to
    -- jump to the CATCH block.
    RAISERROR ('Error raised in TRY block.', -- Message text.
               16, -- Severity.
               1 -- State.
               );
END TRY
BEGIN CATCH
    DECLARE @ErrorMessage NVARCHAR(4000);
    DECLARE @ErrorSeverity INT;
    DECLARE @ErrorState INT;

    SELECT
        @ErrorMessage = ERROR_MESSAGE(),
        @ErrorSeverity = ERROR_SEVERITY(),
        @ErrorState = 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, -- Message text.
               @ErrorSeverity, -- Severity.
               @ErrorState -- State.
               );
END CATCH;

B. Creating an ad hoc message in sys.messages

The following example shows how to raise a message stored in the sys.messages catalog view. The message was added to the sys.messages catalog view by using the sp_addmessage system stored procedure as message number 50005.

EXEC sp_addmessage @msgnum = 50005,
              @severity = 10,
              @msgtext = N'<<%7.3s>>';
GO
RAISERROR (50005, -- Message id.
           10, -- Severity,
           1, -- State,
           N'abcde'); -- First argument supplies the string.
-- The message text returned is: <<    abc>>.
GO
EXEC sp_dropmessage @msgnum = 50005;
GO

C. Using a local variable to supply the message text

The following code example shows how to use a local variable to supply the message text for a RAISERROR statement.

DECLARE @StringVariable NVARCHAR(50);
SET @StringVariable = N'<<%7.3s>>';

RAISERROR (@StringVariable, -- Message text.
           10, -- Severity,
           1, -- State,
           N'abcde'); -- First argument supplies the string.
-- The message text returned is: <<    abc>>.
GO

See also

  • Built-in Functions (Transact-SQL)
  • DECLARE @local_variable (Transact-SQL)
  • PRINT (Transact-SQL)
  • sp_addmessage (Transact-SQL)
  • sp_dropmessage (Transact-SQL)
  • sys.messages (Transact-SQL)
  • xp_logevent (Transact-SQL)
  • @@ERROR (Transact-SQL)
  • ERROR_LINE (Transact-SQL)
  • ERROR_MESSAGE (Transact-SQL)
  • ERROR_NUMBER (Transact-SQL)
  • ERROR_PROCEDURE (Transact-SQL)
  • ERROR_SEVERITY (Transact-SQL)
  • ERROR_STATE (Transact-SQL)
  • TRY…CATCH (Transact-SQL)

Summary: in this tutorial, you will learn how to use the SQL Server RAISERROR statement to generate user-defined error messages.

If you develop a new application, you should use the THROW statement instead.

SQL Server RAISEERROR statement overview

The RAISERROR statement allows you to generate your own error messages and return these messages back to the application using the same format as a system error or warning message generated by SQL Server Database Engine. In addition, the RAISERROR statement allows you to set a specific message id, level of severity, and state for the error messages.

The following illustrates the syntax of the RAISERROR statement:

RAISERROR ( { message_id | message_text | @local_variable }  
    { ,severity ,state }  
    [ ,argument [ ,...n ] ] )  
    [ WITH option [ ,...n ] ];
Code language: SQL (Structured Query Language) (sql)

Let’s examine the syntax of the RAISERROR for better understanding.

message_id

The message_id is a user-defined error message number stored in the sys.messages catalog view.

To add a new user-defined error message number, you use the stored procedure sp_addmessage. A user-defined error message number should be greater than 50,000. By default, the RAISERROR statement uses the message_id 50,000 for raising an error.

The following statement adds a custom error message to the sys.messages view:

EXEC sp_addmessage 
    @msgnum = 50005, 
    @severity = 1, 
    @msgtext = 'A custom error message';
Code language: SQL (Structured Query Language) (sql)

To verify the insert, you use the following query:

SELECT    
    *
FROM    
    sys.messages
WHERE 
    message_id = 50005;
Code language: SQL (Structured Query Language) (sql)

To use this message_id, you execute the RAISEERROR statement as follows:

RAISERROR ( 50005,1,1)
Code language: SQL (Structured Query Language) (sql)

Here is the output:

A custom error message
Msg 50005, Level 1, State 1
Code language: SQL (Structured Query Language) (sql)

To remove a message from the sys.messages, you use the stored procedure sp_dropmessage. For example, the following statement deletes the message id 50005:

EXEC sp_dropmessage 
    @msgnum = 50005;  
Code language: SQL (Structured Query Language) (sql)

message_text

The message_text is a user-defined message with formatting like the printf function in C standard library. The message_text can be up to 2,047 characters, 3 last characters are reserved for ellipsis (…). If the message_text contains 2048 or more, it will be truncated and is padded with an ellipsis.

When you specify the message_text, the RAISERROR statement uses message_id 50000 to raise the error message.

The following example uses the RAISERROR statement to raise an error with a message text:

RAISERROR ( 'Whoops, an error occurred.',1,1)
Code language: SQL (Structured Query Language) (sql)

The output will look like this:

Whoops, an error occurred.
Msg 50000, Level 1, State 1
Code language: SQL (Structured Query Language) (sql)

severity

The severity level is an integer between 0 and 25, with each level representing the seriousness of the error.

0–10 Informational messages
11–18 Errors
19–25 Fatal errors
Code language: SQL (Structured Query Language) (sql)

state

The state is an integer from 0 through 255. If you raise the same user-defined error at multiple locations, you can use a unique state number for each location to make it easier to find which section of the code is causing the errors. For most implementations, you can use 1.

WITH option

The option can be LOG, NOWAIT, or SETERROR:

  • WITH LOG logs the error in the error log and application log for the instance of the SQL Server Database Engine.
  • WITH NOWAIT sends the error message to the client immediately.
  • WITH SETERROR sets the ERROR_NUMBER and @@ERROR values to message_id or 50000, regardless of the severity level.

SQL Server RAISERROR examples

Let’s take some examples of using the RAISERROR statement to get a better understanding.

A) Using SQL Server RAISERROR with TRY CATCH block example

In this example, we use the RAISERROR inside a TRY block to cause execution to jump to the associated CATCH block. Inside the CATCH block, we use the RAISERROR to return the error information that invoked the CATCH block.

DECLARE 
    @ErrorMessage  NVARCHAR(4000), 
    @ErrorSeverity INT, 
    @ErrorState    INT;

BEGIN TRY
    RAISERROR('Error occurred in the TRY block.', 17, 1);
END TRY
BEGIN CATCH
    SELECT 
        @ErrorMessage = ERROR_MESSAGE(), 
        @ErrorSeverity = ERROR_SEVERITY(), 
        @ErrorState = ERROR_STATE();

    -- return the error inside the CATCH block
    RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH;
Code language: SQL (Structured Query Language) (sql)

Here is the output:

Msg 50000, Level 17, State 1, Line 16
Error occurred in the TRY block.
Code language: SQL (Structured Query Language) (sql)

B) Using SQL Server RAISERROR statement with a dynamic message text example

The following example shows how to use a local variable to provide the message text for a RAISERROR statement:

DECLARE @MessageText NVARCHAR(100);
SET @MessageText = N'Cannot delete the sales order %s';

RAISERROR(
    @MessageText, -- Message text
    16, -- severity
    1, -- state
    N'2001' -- first argument to the message text
);
Code language: SQL (Structured Query Language) (sql)

The output is as follows:

Msg 50000, Level 16, State 1, Line 5
Cannot delete the sales order 2001
Code language: SQL (Structured Query Language) (sql)

When to use RAISERROR statement

You use the RAISERROR statement in the following scenarios:

  • Troubleshoot Transact-SQL code.
  • Return messages that contain variable text.
  • Examine the values of data.
  • Cause the execution to jump from a TRY block to the associated CATCH block.
  • Return error information from the CATCH block to the callers, either calling batch or application.

In this tutorial, you will learn how to use the SQL Server RAISERROR statement to generate user-defined error messages.

Приветствую всех посетителей сайта Info-Comp.ru! Сегодня мы с Вами поговорим о том, чем же отличается инструкция THROW от инструкции RAISERROR в языке T-SQL, который используется в Microsoft SQL Server.

Для наглядности мы сформируем итоговую таблицу отличий.

Чем отличается инструкция THROW от RAISERROR в T-SQL (Microsoft SQL Server)

Содержание

  1. Инструкции THROW и RAISERROR в языке T-SQL
  2. Функция RAISERROR
  3. Пример использования RAISERROR
  4. Инструкция THROW
  5. Пример использования THROW
  6. Отличия инструкции THROW от RAISERROR

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

Заметка! Visual Studio Code (VS Code) для разработки на Transact-SQL.

К конструкциям, которые позволяют нам отслеживать и обрабатывать ошибки, можно отнести:

  • TRY…CATCH
  • RAISERROR
  • THROW

Если конструкция TRY…CATCH предназначена для перехвата ошибок в коде, то инструкции RAISERROR и THROW предназначены для создания сообщений об ошибках, т.е. они выполняют примерно одинаковую функцию.

Однако, у многих может возникнуть вопрос, а чем они отличаются? А эти отличия на самом деле есть, и мы их сейчас рассмотрим.

Функция RAISERROR

RAISERROR – это системная функция, позволяющая создавать сообщение об ошибке и возвращать его как сообщение об ошибке сервера вызывающему приложению.

С помощью RAISERROR мы можем сослаться на уже определенное сообщение, которое находится в системном представлении sys.messages, либо самостоятельно динамически создавать сообщение.

Пример использования RAISERROR

   
   RAISERROR('Возникла ошибка!', 16, 1);

Скриншот 1

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

Инструкция THROW

THROW – инструкция, которая вызывает исключение и передает выполнение блоку CATCH конструкции TRY…CATCH.

Пример использования THROW

   
   THROW 51000, 'Возникло исключение!', 1;

Скриншот 2

Заметка! Чем отличаются функции от хранимых процедур в T-SQL.

Отличия инструкции THROW от RAISERROR

Давайте перейдем к рассмотрению основных отличный инструкции THROW от RAISERROR, а чтобы было более наглядно, сделаем это в виде таблицы.

RAISERROR THROW
Если передается номер ошибки, то идентификатор этой ошибки должен быть определен в системном представлении sys.messages В данном случае номер ошибки не требуется определять в системном представлении sys.messages
Есть параметр, который указывает уровень серьезности ошибки Параметр для указания уровня серьезности отсутствует. В THROW уровень серьезности всегда равен 16
Если передается текст ошибки, то он может содержать стили форматирования, аналогично формату, используемому в функции printf В данном случае параметр с текстом ошибки не может принимать форматирование стиля printf
Не учитывает SET XACT_ABORT Учитывает SET XACT_ABORT

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

Вот мы с Вами и рассмотрели основные отличия инструкции THROW от RAISERROR.

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

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

Transact-SQL provides facilities to raise errors and warnings, as well as handling them. Here’s an introduction to this subject.

Raising errors and warnings

In SQL Server, we can generate an error or a warning with the RAISERROR statement. The most common usage looks like this:

RAISERROR (
    'Invalid user id',
    20, -- severity
    0   -- state
);

In this example, we’re raising a fatal error with severity 25 and state 0.

Severity must be in the range between 0 and 19. 0-18 represents a warning, 19-25 represents an error.

State must be in the range between 0 and 255. has no effect on SQL Server itself, but it’s useful for handling the error programmatically. If an error with the same message and severity can be raised in several places, the state can be used to find out where the error was raised.

Severity and state may be adjusted by SQL Server. If they’re less then 0 the value will be 0. If they exceed the maximum value, the maximum value will be used.

An error is a fatal error that terminates the current operation, and a warning is an anomaly that could indicate a problem or not, depending on the situation. Let’s see the differences, and how to generate them.

Errors

A mentioned, an error has a severity from 19 to 25.

An error must be written into the logs, so the WITH LOG option is required, as follows:

RAISERROR (...) WITH LOG;

An error is considered fatal, so it has some effects:

  • It rolls back the current transaction;
  • It terminates the execution of the current T-SQL program (a trigger, a procedure, etc).

Warnings

As mentioned, a warning must have a severity from 0 to 18. Warnings can only be handled by a TRY ... CATCH statement if their severity is bigger than 10.

The WITH LOG option discussed above is optional for warnings.

A warning does not rollback the transaction and does not terminate a program execution. We can, however, do one of these things or both:

ROLLBACK;
RAISERROR (...);
RETURN 1;

The order of the statements is important here:

  • ROLLBACK must precede RAISERROR, because if the warning is handled by a TRY ... CATCH block, anything after RAISERROR won’t be executed.
  • RETURN should follow both ROLLBACK and RAISERROR, or they won’t be executed.

RETURN always returns an integer value, 0 by default. This value can be used to indicate if the procedure succeeded or not, and why it failed. Conventionally, 0 indicates success and any other value indicates failure. We can use different non-zero returned values to indicate different problems: for example 1 for wrong arguments, 2 for missing rows, etc.

Other options

RAISERROR has a few more options that can occasionally be useful.

NOWAIT

Errors are normally sent to the client (and shown to the user) when the current program or T-SQL statement ends. To send the error to the client immediately, specify WITH NOWAIT:

RAISERROR (...) WITH NOWAIT;

SETERROR

You can also use SETERROR to set the error number to 50000, regardless of the severity level. 50000 is the code reserver to general user defined errors, by convention.

Reference

More articles:

  • SQL Server: Handle errors

Подождите!

Не закрывайте страницу. Нейросеть пишет ответ. Он появится тут.

Этот ответ написан нейросетью.

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

Ответ нейросети:

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

Чтобы вызвать ошибку при помощи RAISERROR, необходимо указать сообщение об ошибке, уровень серьезности и номер состояния, в котором произошла ошибка. Второй параметр отвечает за уровень серьезности, который может быть от 1 до 25, где 1 означает наименьший уровень серьезности ошибки, а 25 – самый высокий уровень. Чем выше уровень серьезности, тем более критическая ошибка.

Пример команды RAISERROR:

RAISERROR(‘Произошла ошибка’, 16, 1)

Эта команда вызовет ошибку уровня серьезности 16 и сообщение «Произошла ошибка» будет выведено пользователю.

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

⚠️Зарепортить пост!

I’ve created a MySQL function and would like to raise an error if the values passed for the parameters are invalid. What are my options for raising an error within a MySQL function?

dolmen's user avatar

dolmen

8,1565 gold badges40 silver badges42 bronze badges

asked Jan 21, 2009 at 15:22

Dónal's user avatar

MySQL 5.5 introduces signals, which are similar to exceptions in other languages:

http://dev.mysql.com/doc/refman/5.5/en/signal.html

For example, in the mysql command line client:

mysql> SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Custom error';
ERROR 1644 (45000): Custom error

answered Aug 6, 2012 at 13:39

Austin Hyde's user avatar

Austin HydeAustin Hyde

26.4k28 gold badges96 silver badges129 bronze badges

It’s actually a combination of all three answers. You call a non-existent procedure to raise the error, and then declare an exit handler that catches the error you generated. Here’s an example, using SQLSTATE 42000 (procedure does not exist) to throw an error before deletion if the row to be deleted has a foreign key id set:

DROP PROCEDURE IF EXISTS decount_test;

DELIMITER //

CREATE DEFINER = 'root'@'localhost' PROCEDURE decount_test ( p_id bigint )
DETERMINISTIC MODIFIES SQL DATA
BEGIN
  DECLARE EXIT HANDLER FOR SQLSTATE '42000'
    SELECT 'Invoiced barcodes may not have accounting removed.';
  IF (SELECT invoice_id 
       FROM accounted_barcodes
       WHERE id = p_id
    ) THEN
    CALL raise_error;
 END IF;
 DELETE FROM accounted_barcodes WHERE id = p_id;
END //

DELIMITER ;

Output:

call decount_test(123456);
+----------------------------------------------------+
| Invoiced barcodes may not have accounting removed. |
+----------------------------------------------------+
| Invoiced barcodes may not have accounting removed. | 
+----------------------------------------------------+

Kev's user avatar

Kev

15.9k15 gold badges80 silver badges112 bronze badges

answered Apr 22, 2010 at 18:31

Ryan M's user avatar

Ryan MRyan M

6886 silver badges12 bronze badges

2

Why not just store a VARCHAR in a declared INTEGER variable?

DELIMITER $$ DROP FUNCTION IF EXISTS `raise_error` $$
CREATE FUNCTION `raise_error`(MESSAGE VARCHAR(255)) 
RETURNS INTEGER DETERMINISTIC BEGIN
  DECLARE ERROR INTEGER;
  set ERROR := MESSAGE;
  RETURN 0;
END $$ DELIMITER ;
-- set @foo := raise_error('something failed'); -- or within a query

Error message is:

Incorrect integer value: ‘something failed’ for column ‘ERROR’ at row
1

It’s not perfect, but it gives a pretty descriptive message and you don’t have to write any extension DLLs.

answered Feb 12, 2013 at 19:56

3

You can also call an existing function with an invalid number of arguments.

answered Jul 6, 2009 at 17:45

Andrew CharneskiAndrew Charneski

answered Jan 21, 2009 at 20:24

Jorge Niedbalski R.'s user avatar

1

Пересказ статьи Jared Westover. Raising Exceptions and Error Handling with SQL Server THROW

Рассматривали ли вы возможность добавления обработки ошибок в код Transact-SQL (T-SQL)? Если вы спросите опытных разработчиков, большинство из них согласится с тем, что это хорошая идея. Возможно, вам достался по наследству далеко не идеальный код. Или ваш код можно было бы немного привести в порядок. Основной причиной для добавления обработки ошибок является управление возникновением исключений. Было бы прекрасно, если бы ошибки не возникали, но такой мир не существует. Есть пара способов для вызова исключений в T-SQL. Более старый метод — это с использованием RAISERROR. Теперь RAISERROR все еще используется, но, начиная с SQL Server 2012 в городе появился новый игрок, которого зовут THROW.

Здесь мы рассмотрим использование THROW. Я укажу на некоторые преимущества и недостатки. В итоге вы сможете принять обоснованное решение о выборе способа реализации для ваших данных на SQL Server.

Использование оператора THROW в SQL Server

Обычно вы встречаете THROW внутри блока TRY…CATCH. Однако вы можете использовать THROW с параметрами отдельно. Давайте рассмотрим простой пример. Вы можете выполнять все приведенные здесь скрипты в SQL Server Management Studio (SSMS) без установки базы данных SQL Server.

THROW 50000, 'Houston, we have a problem.', 1;

Для работы вышеприведенного примера мы должны указать ID сообщения, текст и состояние. Что хорошо в случае THROW, не требуется, чтобы ID сообщения имелось в sys.messages. Кажется, я всегда использую 1 для состояния.

Как упоминалось выше, вы обычно используете THROW в сочетании с блоком TRY…CATCH, как показано в следующем примере.

BEGIN TRY -- Блок TRY
SELECT 1 / 0; -- Оператор SELECT
END TRY
BEGIN CATCH -- Блок CATCH
THROW;
END CATCH;

Замечательной особенностью THROW при использовании внутри TRY…CATCH является то, что вам нужно всего лишь напечатать THROW. Если бы существовала простая кнопка для вызова исключения, то это была бы она.

Преимущества THROW

Давайте потратим минутку и рассмотрим самые известные преимущества THROW, особенно в сравнении с RAISERROR. Сразу скажу, что простота THROW не имеет себе равных. Тем не менее, вот несколько других замечательных особенностей.

Сообщение об ошибке и номер строки

Посмотрите снова на скриншот выше. Вы получаете реальное сообщение, возвращаемое, если просто напечатать THROW. Вам не нужно беспокоиться об использовании функций обработки ошибок. Обратите внимание также на номер строки. Это номер строки, где произошло исключение, а не там, где мы вызывали THROW в блоке кода. Вообразите себе отладку кода в 1000+ строк. Получение точного номера критично при поиске возникающих ошибок.

ID сообщения

Если вы используете THROW вне блока TRY…CATCH, вам не нужно беспокоиться о наличии ID сообщения. Вы можете использовать любой старый номер. При использовании RAISERROR вам нужно было добавить сообщение в sys.messages, чтобы это работало.

Прерывание пакета

Когда я встречаю исключение, мне обычно хочется откатить транзакцию (ROLLBACK) и прервать выполнение пакета. Когда вы выполняете THROW, пакет останавливается, и SQL Server не выполняет никаких последующих операторов. Обратите внимание, что в примере ниже PRINT не выполняется. Вот синтаксис:

BEGIN TRY
SELECT 1 / 0;
END TRY
BEGIN CATCH
THROW;
PRINT 'Я очень надеюсь, что это сработает!';
END CATCH;

Рекомендации Microsoft

Наконец, начиная с SQL Server 2012, Microsoft рекомендовало использование THROW. Конечно, это, в первую очередь, зависит от того, зачем вы используете THROW. Я все еще использую RAISERROR в специальных случаях, что вы увидите в следующем разделе.

Недостатки THROW

Давайте потратим немного времени на рассмотрение некоторых недостатков THROW. Вот некоторые, которые обычно выделяют разработчики.

Информационные сообщения

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

RAISERROR ('Now you know. And knowing is half the battle.',10,1)

Прерывание пакета

Я упоминал прерывание пакета в списке преимуществ, но это также и недостаток. Если вы хотите продолжить после возникновения исключения, для вас это недостаток. Такое поведение является еще одной причиной, почему я временами все еще использую RAISERROR. Говоря о THROW, я иногда сравниваю его с футбольной игрой, когда судья бросает флаг на поле. Когда флаг брошен, игра останавливается.

Отсутствие NOWAIT и WITHLOG

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

Завершение оператора

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

BEGIN TRY
BEGIN TRANSACTION;
SELECT 1 / 0;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0)
ROLLBACK TRANSACTION
THROW;
END CATCH;

Чтобы обойти это, добавьте точку с запятой после ROLLBACK TRANSACTION или перед THROW. Я использовал ограничители операторов, по крайней мере, 10 лет, и советую другим делать то же самое.

Что использовать?

Фильм «Горец» был одним из лучших фильмов 80-х. Его слоганом было: «Должен остаться только один». В отличие от кино, вы не должны выбрать одно из. Вы можете использовать код ниже, если вы ищите ванильный метод для вызова исключений. Можете скопировать и вставить его в свой шаблон.

BEGIN TRY
BEGIN TRANSACTION;
-- Что-то делаем важное;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0)
ROLLBACK TRANSACTION;
THROW;
END CATCH;

Однако из-за упомянутых выше недостатков не забывайте о RAISERROR. Ваш выбор зависит от цели. Для меня было бы много легче сказать, чтобы вы всегда делали то или это, но, как и для большинство вещей в SQL Server, ответ — «это зависит от…».

Данная статья является переводом. Ссылка на оригинал.

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

Простая платежная система

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

        db=# CREATE TABLE payment (
    id      INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    method  TEXT NOT NULL
        CONSTRAINT payment_method_check CHECK (method IN ('credit_card', 'cash')),
    amount  INT NOT NULL
);
CREATE TABLE
    

Вы предоставляете своим пользователям два варианта оплаты: наличными или кредитной картой:

        db=# INSERT INTO payment (method, amount) VALUES
    ('cash', 10000),
    ('credit_card', 12000),
    ('credit_card', 5000);
INSERT 0 3

db=# SELECT * FROM payment;
 id │   method    │ amount
────┼─────────────┼────────
  1 │ cash        │  10000
  2 │ credit_card │  12000
  3 │ credit_card │   5000
(3 rows)
    

Расчет комиссии

Используйте следующий запрос для расчета комиссии за каждый платеж в зависимости от способа оплаты:

        -- calculate_commission.sql
SELECT
    COUNT(*) AS payments,
    SUM(
        CASE method
            WHEN 'cash' THEN 100
            WHEN 'credit_card' THEN 30 + amount * 0.02
        END
    ) AS commission
FROM
    payment;
    

При оплате наличными вы взимаете фиксированную комиссию в размере 1 доллара США (100 центов), а при оплате кредитной картой вы взимаете фиксированную плату в размере 30 центов плюс 2% от взимаемой суммы.

Это комиссия за первые 3 платежных процесса:

        db=# \i calculate_commission.sql
 payments  │ commission
───────────┼────────────
        3  │     500.00
(1 row)
    

Поздравляю! Вы только что заработали свои первые 5$.

Добавление нового способа оплаты

Время идет, и ваша платежная система становится настоящим хитом! Спрос на ваши услуги стремительно растет, и ваши клиенты просят больше способов оплаты. Вы хорошенько все обдумываете и решаете ввести новый способ оплаты — банковский перевод:

        db=# ALTER TABLE payment DROP CONSTRAINT payment_method_check;
ALTER TABLE

db=# ALTER TABLE payment ADD CONSTRAINT payment_method_check
    CHECK (method IN ('credit_card', 'cash', 'bank_transfer'));
ALTER TABLE
    

Прошло еще несколько месяцев, и новый способ оплаты оказался настоящим хитом:

        INSERT INTO payment (method, amount) VALUES
    ('bank_transfer', 9000),
    ('bank_transfer', 15000),
    ('bank_transfer', 30000);
INSERT 0 3
    

Вы обрабатываете больше платежей, чем могли себе представить, но что-то не так:

        db=# \i calculate_commission.sql
 payments │ commission
──────────┼────────────
        6 │     500.00
(1 row)
    

Вы обрабатываете все эти платежи, но ваш доход остается прежним, почему?

При добавлении нового способа оплаты вы не редактировали запрос, рассчитывающий комиссию. Запрос никогда не завершался ошибкой, не возникало никаких исключений или предупреждений, и вы полностью забыли об этом!

Этот тип сценария довольно распространен. SQL обычно не проверяется статически, поэтому, если у вас нет автоматических тестов для этого конкретного запроса, он может легко остаться незамеченным!

Совершаем ошибки намеренно

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

Напомним запрос для расчета комиссии:

        SELECT
    COUNT(*) AS payments,
    SUM(
        CASE method
            WHEN 'cash' THEN 100
            WHEN 'credit_card' THEN 30 + amount * 0.02
        END
    ) AS commission
FROM
    payment;
    

В запросе используется CASE-выражение для расчета комиссии для каждого способа оплаты. Выражение не определяет, что должно произойти, если метод не соответствует ни одному из WHEN-выражений, поэтому выражение неявно оценивается как NULL, а агрегатная функция игнорирует его.

Что, если вместо неявной оценки NULL мы получим ошибку?

Assert never в SQL

Чтобы вызвать ошибку в PostgreSQL, мы можем использовать простую функцию:

        CREATE OR REPLACE FUNCTION assert_never(v anyelement)
RETURNS anyelement
LANGUAGE plpgsql AS
$$
BEGIN
    RAISE EXCEPTION 'Unhandled value "%"', v;
END;
$$;
    

Функция принимает аргумент любого типа и вызывает исключение:

        db=# SELECT assert_never(1);
ERROR:  Unhandled value "1"
CONTEXT:  PL/pgSQL function assert_never(anyelement) line 3 at RAISE
    

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

        db=# SELECT
    COUNT(*) AS payments,
    SUM(
        CASE method
            WHEN 'cash' THEN 100
            WHEN 'credit_card' THEN 30 + amount * 0.02
            ELSE assert_never(method)::int
        END
    ) AS commission
FROM
    payment;

ERROR:  Unhandled value "bank_transfer"
CONTEXT:  PL/pgSQL function assert_never(anyelement) line 3 at RAISE
    

Это круто! Запрос обнаружил необработанный способ оплаты bank_transfer и завершился ошибкой. К ошибке также относятся значения, которые мы забыли учесть, что делает его особенно полезным для отладки.

Ошибка заставляет разработчика предпринять следующие действия при обработке исключения:

Явно исключить необработанное значение:

        SELECT
    COUNT(*) AS payments,
    SUM(
        CASE method
            WHEN 'cash' THEN 100
            WHEN 'credit_card' THEN 30 + amount * 0.02
            ELSE assert_never(method)::int
        END
    ) AS commission
FROM
    payment
WHERE
    method IN ('cash', 'credit_card');

 payments │ commission
──────────┼────────────
        3 │     500.00
    

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

Обработать новое значение:

        SELECT
    COUNT(*) AS payments,
    SUM(
        CASE method
            WHEN 'cash' THEN 100
            WHEN 'credit_card' THEN 30 + amount * 0.02
            WHEN 'bank_transfer' THEN 50
            ELSE assert_never(method)::int
        END
    ) AS commission
FROM
    payment;

 payments │ commission
──────────┼────────────
        6 │     650.00
    

Разработчик заметил ошибку и добавил в запрос комиссию за необработанный способ оплаты. Ошибка предотвращена!

В обоих случаях результаты теперь точны, а запрос безопаснее.

Assert never

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

К счастью, после публикации статьи функция assert_never была встроена в модуль ввода в Python 3.11, и ее можно использовать для выполнения исчерпывающей проверки:

        from typing import assert_never, Literal

def calculate_commission(
    method: Literal['cash', 'credit_card', 'bank_transfer'],
    amount: int,
) -> float:
    if method == 'cash':
        return 100
    elif method == 'credit_card':
        return 30 + 0.02 * amount
    else:
        assert_never(method)
    

Запуская код в Mypy, программа проверки опциональных статических типов для Python выдаст следующую ошибку:

        error: Argument 1 to "assert_never" has incompatible type "Literal['bank_transfer']";
expected "NoReturn"
    

Как и функция assert_never в SQL, ошибка предупреждает о необработанном значении bank_transfer. В отличие от функции в SQL, это произойдет не во время выполнения, а во время статического анализа.

Ошибка без функции

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

Злоупотребление делением на ноль

Самый простой способ вызвать ошибки в любом языке программирования — это разделить некоторое число на ноль:

        SELECT
    COUNT(*) AS payments,
    SUM(
        CASE method
            WHEN 'cash' THEN 100
            WHEN 'credit_card' THEN 30 + amount * 0.02
            ELSE 1/0 -- intentional
        END
    ) AS commission
FROM
    payment;

ERROR:  division by zero
    

Вместо возврата NULL, когда метод не обрабатывается, мы делим 1 на 0, чтобы вызвать ошибку деления на ноль. Запрос не удался, как мы и хотели, но это не работает так, как мы могли бы ожидать.

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

        SELECT
    COUNT(*) AS payments,
    SUM(
        CASE method
            WHEN 'cash' THEN 100
            WHEN 'credit_card' THEN 30 + amount * 0.02
            WHEN 'bank_transfer' THEN 50
            ELSE 1/0 -- fail on purpose
        END
    ) AS commission
FROM
    payment;

ERROR:  division by zero
    

Этот запрос обрабатывал все возможные способы оплаты, но все равно возникли ошибки. Если посмотреть документацию про CASE, то становится понятно почему:

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

В документации можно найти объяснение этому. Хотя CASE обычно оценивает только необходимые выражения, бывают случаи, когда выражения, использующие только константы, такие как 1/0, оцениваются во время планирования. Вот почему запрос завершился ошибкой, хотя базе данных не нужно было оценивать выражение в ELSE-условии.

Злоупотребление приведением типов

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

        SELECT
    COUNT(*) AS payments,
    SUM(
        CASE method
            WHEN 'cash' THEN 100
            WHEN 'credit_card' THEN 30 + amount * 0.02
            ELSE method::int
        END
    ) AS commission
FROM
    payment;

ERROR:  invalid input syntax for type integer: "bank_transfer"

    

Мы пытались преобразовать текстовое значение в столбце method в целое число, но запрос не был выполнен. В качестве бонуса сообщение об ошибке предоставляет нам значение bank_transfer, что позволяет легко идентифицировать необработанное значение.

Давайте также проверим, что запрос не завершается ошибкой при обработке всех методов:

        SELECT
    COUNT(*) AS payments,
    SUM(
        CASE method
            WHEN 'cash' THEN 100
            WHEN 'credit_card' THEN 30 + amount * 0.02
            WHEN 'bank_transfer' THEN 50
            ELSE method::int
        END
    ) AS commission
FROM
    payment;

 payments │ commission
──────────┼────────────
        6 │     650.00
    

Когда запрос обрабатывает все возможные значения для method, он не завершается ошибкой!

Злоупотребление приведением типов для нетекстовых типов

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

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

        SELECT
    CASE n
        WHEN 1 THEN 'one'
        WHEN 2 THEN 'two'
        ELSE ('Unhandled value ' || n)::int::text
    END as v
FROM (VALUES
    (1),
    (2),
    (3)
) AS t(n);

ERROR:  invalid input syntax for type integer: "Unhandled value 3"
    

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

***

Материалы по теме

  • 🗄️ ✔️ 10 лучших практик написания SQL-запросов
  • 🐘 Руководство по SQL для начинающих. Часть 1: создание базы данных, таблиц и установка связей между таблицами
  • 📜 Основные SQL-команды и запросы с примерами, которые должен знать каждый разработчик

Like this post? Please share to your friends:
  • Как выявить ошибки на компьютере
  • Как выявить ошибки на ваз 2114
  • Как вычисляются приборные ошибки
  • Как вычисляется стандартная ошибка
  • Как вызвать ошибку cmd