Як повідомити про помилку від визначеної користувачем функції 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.