it-swarm.com.ru

Как сообщить об ошибке из пользовательской функции 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, но для любого разработчика было бы трудно устранить эту проблему. Я также мог бы вызвать деление на ноль или что-то в этом роде - это сгенерировало бы сообщение об ошибке, но вводящее в заблуждение Могу ли я как-нибудь сообщить о своем сообщении об ошибке?

138
EMP

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

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

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

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

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

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

15
Remus Rusanu

Исходя из ответа Владимира Королева, условно выдать ошибку

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)    
7
briantyler

Я думаю, что самый простой способ - просто принять, что функция может возвращать 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
AndyM

RAISEERROR или @@ERROR недопустимы в UDF. Можете ли вы превратить UDF в пошаговую процедуру?

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

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

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

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

5
Mitch Wheat

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

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
4
davec

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

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

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

RETURN

END

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

Сообщение 245, Уровень 16, Состояние 1, Строка 14 Преобразование не удалось при преобразовании значения varchar 'booooom!' к типу данных int.

4
NightShovel

Я не могу комментировать ответ 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
2
Michal Zglinski