Как сообщить об ошибке из пользовательской функции SQL Server

Я пишу пользовательскую функцию в SQL Server 2008. Я знаю, что функции не могут поднять ошибки обычным способом - если вы попытаетесь включить оператор RAISERROR SQL возвращает:

Msg 443, Level 16, State 14, Procedure ..., Line ...
Invalid use of a side-effecting operator 'RAISERROR' within a function.

Но факт в том, что функция принимает некоторый ввод, который может быть недействительным, и если это так, то нет значимого значения, которое функция может вернуть. Что мне делать тогда?

Я мог бы, конечно, вернуть NULL, но для любого разработчика, использующего эту функцию, было бы сложно устранить эту проблему. Я мог бы также вызвать деление на ноль или что-то вроде этого - это создаст сообщение об ошибке, но вводит в заблуждение. Можно ли каким-либо образом сообщить свое сообщение об ошибке?

Ответ 1

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

create function dbo.throwError()
returns nvarchar(max)
as
begin
    return cast('Error happened here.' as int);
end

Затем сервер Sql отобразит некоторую справочную информацию:

Msg 245, Level 16, State 1, Line 1
Conversion failed when converting the varchar value 'Error happened here.' to data type int.

Ответ 2

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

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

Ответ 3

После ответа Владимира Королева, идиома условно выдает ошибку

CREATE FUNCTION [dbo].[Throw]
(
    @error NVARCHAR(MAX)
)
RETURNS BIT
AS
BEGIN
    RETURN CAST(@error AS INT)
END
GO

DECLARE @error NVARCHAR(MAX)
DECLARE @bit BIT

IF `error condition` SET @error = 'My Error'
ELSE SET @error = '0'

SET @bit = [dbo].[Throw](@error)    

Ответ 4

RAISEERROR или @@ERROR не разрешены в UDF. Можете ли вы включить UDF в обработанную процедуру?

Из статьи Эрланда Соммарскога Обработка ошибок в SQL Server - справочная информация:

Пользовательские функции обычно вызывается как часть SET, SELECT, INSERT, UPDATE или DELETE. Я обнаружил, что если ошибка появляется в мультирекламе табличная функция или скаляр функции, выполнение функция прерывается немедленно, и так и утверждение, что функция часть. Выполнение продолжается следующей строке, если ошибка не прервана пакет. В любом случае ошибка @@ 0. Таким образом, невозможно обнаружить, что в функции возникла ошибка от T-SQL.

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

Вы также можете выполнять скалярные функции с выражением EXEC. В этом случае, выполнение продолжается, если возникает ошибка (если это ошибка прерывания партии). @@установлена ​​ошибка, и вы можете проверить значение ошибки @@в функции. Это может быть проблематично для общения ошибка для вызывающего абонента.

Ответ 5

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

-- =============================================
-- Author: AM
-- Create date: 03/02/2010
-- Description: Returns the appropriate exchange rate
-- based on the input parameters.
-- If the rate cannot be found, returns NULL
-- (RAISEERROR can't be used in UDFs)
-- =============================================
ALTER FUNCTION [dbo].[GetExchangeRate] 
(
    @CurrencyFrom char(3),
    @CurrencyTo char(3),
    @OnDate date
)
RETURNS decimal(18,4)
AS
BEGIN

  DECLARE @ClosingRate as decimal(18,4)

    SELECT TOP 1
        @ClosingRate=ClosingRate
    FROM
        [FactCurrencyRate]
    WHERE
        [email protected] AND
        [email protected] AND
        DateID=dbo.DateToIntegerKey(@OnDate)

    RETURN @ClosingRate 

END
GO

Ответ 6

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

MikeTeeVee дал решение для этого в своем комментарии к верхнему ответу, но для этого потребовалось использование агрегатной функции, такой как MAX, что не помогло мне в моих обстоятельствах.

Я столкнулся с альтернативным решением для случая, когда вам нужна встроенная таблица с оценкой udf, которая возвращает нечто вроде select * вместо агрегата. Пример кода, разрешающий этот конкретный случай, приведен ниже. Как уже указывал кто-то... "JEEZ wotta hack" :) Я приветствую любое лучшее решение для этого случая!

create table foo (
    ID nvarchar(255),
    Data nvarchar(255)
)
go

insert into foo (ID, Data) values ('Green Eggs', 'Ham')
go

create function dbo.GetFoo(@aID nvarchar(255)) returns table as return (
    select *, 0 as CausesError from foo where ID = @aID

    --error checking code is embedded within this union
    --when the ID exists, this second selection is empty due to where clause at end
    --when ID doesn't exist, invalid cast with case statement conditionally causes an error
    --case statement is very hack-y, but this was the only way I could get the code to compile
    --for an inline TVF
    --simpler approaches were caught at compile time by SQL Server
    union

    select top 1 *, case
                        when ((select top 1 ID from foo where ID = @aID) = @aID) then 0
                        else 'Error in GetFoo() - ID "' + IsNull(@aID, 'null') + '" does not exist'
                    end
    from foo where (not exists (select ID from foo where ID = @aID))
)
go

--this does not cause an error
select * from dbo.GetFoo('Green Eggs')
go

--this does cause an error
select * from dbo.GetFoo('Yellow Eggs')
go

drop function dbo.GetFoo
go

drop table foo
go

Ответ 7

Я не могу комментировать ответ davec относительно функции, связанной с таблицей, но по моему скромному мнению это более простое решение:

CREATE FUNCTION dbo.ufn_test (@a TINYINT)
RETURNS @returns TABLE(Column1 VARCHAR(10), Value1 TINYINT)
BEGIN
    IF @a>50 -- if @a > 50 - raise an error
    BEGIN
      INSERT INTO @returns (Column1, Value1)
      VALUES('error','@a is bigger than 50!') -- reminder Value1 should be TINYINT
    END

    INSERT INTO @returns (Column1, Value1)
    VALUES('Something',@a)
    RETURN;
END

SELECT Column1, Value1 FROM dbo.ufn_test(1) -- this is okay
SELECT Column1, Value1 FROM dbo.ufn_test(51) -- this will raise an error

Ответ 8

Несколько человек спрашивали о повышении ошибок в табличных значениях, поскольку вы не можете использовать " RETURN [invalid cast]. Присвоение неверного нажатия переменной также работает.

CREATE FUNCTION fn()
RETURNS @T TABLE (Col CHAR)  
AS
BEGIN

DECLARE @i INT = CAST('booooom!' AS INT)  

RETURN

END

Это приводит к:

Msg 245, уровень 16, состояние 1, строка 14 Ошибка конверсии при преобразовании значения varchar 'booooom!' к типу данных int.

Ответ 9

Один из способов (взломать) - иметь функцию/хранимую процедуру, которая выполняет недопустимое действие. Например, следующий псевдо SQL

create procedure throw_error ( in err_msg varchar(255))
begin
insert into tbl_throw_error (id, msg) values (null, err_msg);
insert into tbl_throw_error (id, msg) values (null, err_msg);
end;

Где в таблице tbl_throw_error существует уникальное ограничение на столбец err_msg. Побочным эффектом этого (по крайней мере, для MySQL) является то, что значение err_msg используется как описание исключения, когда оно возвращается в объект исключения уровня приложения.

Я не знаю, можете ли вы сделать что-то подобное с SQL Server, но стоит сделать снимок.