Як повідомити про помилку від визначеної користувачем функції SQL Server


146

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

Відповіді:


223

Ви можете використовувати 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.

112
Чудова відповідь, але JEEZ wotta hack. > :(
— JohnL4

5
Для функції вбудованої таблиці-значення, де RETURN є простим вибором, це одне не працює, тому що нічого не повертається - навіть недійсне, і в моєму випадку я хотів викинути помилку, коли нічого не було знайдено. Я не хотів розбивати вбудовану функцію на багатошарову з очевидних причин продуктивності. Натомість я використав ваше рішення плюс ISNULL та MAX. Тепер повернення RETURN виглядає приблизно так: SELECT ISNULL (MAX (E.EntityID), CAST ('Пошук (' + @LookupVariable + ') не існує.' Як Int)) [EntityID] FROM Entity як E WHERE E. Lookup = @ LookupVariable
— MikeTeeVee

Так, ви можете кинути помилку, але не здається, що ви можете умовно кинути помилку. Функція виконується незалежно від шляху коду.
— Satnhak

10
Чудове рішення, але для тих, хто використовує TVF, це не може бути просто частиною повернення. Для тих:declare @error int; set @error = 'Error happened here.';
— Тім Ленер

20
Я ненавиджу це силою тисячі палаючих сонців. Немає інших варіантів? Чудово. Але крипси ...
— Ремі Депресс-Сміт

18

Звичайний трюк - примусити ділити на 0. Це призведе до помилки і перерве поточне твердження, що оцінює функцію. Якщо розробник чи особа, яка надає підтримку, знає про таку поведінку, дослідити та вирішити проблему досить просто, оскільки поділ на 0 помилок розуміється як симптом іншої, непов'язаної проблеми.

Як би це погано не виглядало з будь-якої точки зору, на жаль, дизайн SQL-функцій на даний момент не дозволяє зробити кращий вибір. Використання RAISERROR має бути абсолютно дозволено у функціях.


7

Виходячи з відповіді Володимира Корольова, ідіома умовно кинути помилку є

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)    

6

Я думаю, що найчистіший спосіб - просто прийняти, що функція може повернути 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
        FromCurrencyCode=@CurrencyFrom AND
        ToCurrencyCode=@CurrencyTo AND
        DateID=dbo.DateToIntegerKey(@OnDate)

    RETURN @ClosingRate 

END
GO

5

RAISEERRORабо @@ERRORне дозволені в АДС. Чи можете ви перетворити UDF на процедуру, що вимагає строгості?

З статті Ерланда Соммарського Поводження з помилками в SQL Server - передумова :

Зазначені користувачем функції зазвичай викликаються як частина оператора SET, SELECT, INSERT, UPDATE або DELETE. Що я виявив - це те, що якщо помилка з'являється в функції багатозначних таблиць, що оцінюються в таблиці, або в скалярній функції, виконання функції припиняється негайно, і так само є твердженням, частиною якого є функція. Виконання продовжується в наступному рядку, якщо помилка не припинила партію. В будь-якому випадку помилка @@ дорівнює 0. Таким чином, не існує можливості виявити, що в функції T-SQL сталася помилка.

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

Ви також можете виконувати скалярні функції за допомогою оператора EXEC. У цьому випадку виконання продовжується, якщо виникає помилка (якщо це не помилка, яка перериває пакет). Встановлено помилку @@, і ви можете перевірити значення помилки @@ у межах функції. Повідомлення про помилку абоненту може бути проблематичним.


4

Верхня відповідь, як правило, найкраща, але не працює для функцій, що оцінюються в таблиці.

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

1
для тих, хто читає, я не дивився на потенційні ефекти від продуктивності ... я не здивуюсь, якщо хакерська спілка + заява про ситуацію сповільнить ...
— davec

4

Декілька людей запитували про помилки в функціях, оцінених у таблиці, оскільки ви не можете використовувати подібні речі " 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.


2

Я не можу коментувати відповідь 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

-3

Один із способів (хак) - це функція / збережена процедура, яка виконує недійсну дію. Наприклад, наступний псевдо 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, але варто зробити це.


5
Цікава ідея, але INSERT також не дозволений у функції.
— EMP
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.