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 Serverはいくつかのヘルプ情報を表示します。

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

112
すばらしい答えですが、JEEZはハックしました。> :(
JohnL4

5
RETURNが単純な選択であるinline-table-valued-functionの場合、何も返されないため、これだけでは機能しません。nullでもありません。私の場合、何も見つからなかったときにエラーをスローします。明らかなパフォーマンス上の理由から、インライン関数を複数ステートメントに分解したくありませんでした。代わりに、私はあなたのソリューションとISNULLおよびMAXを使用しました。RETURNステートメントは次のようになります:SELECT ISNULL(MAX(E.EntityID)、CAST( 'The Lookup(' + @LookupVariable + ')does not exist。' as Int))[EntityID] FROM Entity as E WHERE E. Lookup = @ LookupVariable
MikeTeeVee

はい、エラーをスローすることはできますが、条件付きでエラーをスローすることはできないようです。関数は、コードパスに関係なく実行されます。
satnhak 2012年

10
優れたソリューションですが、TVFを使用しているユーザーにとって、これは簡単には見返りになりません。それらの場合:declare @error int; set @error = 'Error happened here.';
Tim Lehner、2012年

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では許可されていません。UDFを保存された手順に変えることができますか?

Erland Sommarskogの記事「SQL Serverでのエラー処理–背景」より

ユーザー定義関数は通常、SET、SELECT、INSERT、UPDATE、またはDELETEステートメントの一部として呼び出されます。私が見つけたのは、複数ステートメントのテーブル値関数またはスカラー関数にエラーが表示された場合、関数の実行が即座に中止され、関数が含まれているステートメントも同様に中止されるということです。エラーがバッチを中止しない限り、実行は次の行で続行されます。どちらの場合も、@@ errorは0です。したがって、T-SQLから関数でエラーが発生したことを検出する方法はありません。

インラインテーブル値関数は基本的にクエリプロセッサがクエリに貼り付けるマクロであるため、インラインテーブル関数では問題は発生しません。

EXECステートメントを使用してスカラー関数を実行することもできます。この場合、エラーが発生しても(バッチ中止エラーでない限り)実行が続行されます。@@ errorが設定され、関数内で@@ errorの値を確認できます。ただし、呼び出し元にエラーを通知することは問題になる可能性があります。


4

一般的には、トップの答えが最適ですが、インラインテーブル値関数では機能しません。

MikeTeeVeeは、トップアンサーに関するコメントでこれに対する解決策を示しましたが、MAXのような集計関数を使用する必要があり、私の状況ではうまく機能しませんでした。

集計の代わりにselect *のようなものを返すインラインテーブル値udfが必要な場合の代替ソリューションをいじりました。この特定のケースを解決するサンプルコードを以下に示します。誰かがすでに指摘したように... "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 [無効なキャスト]」のようなものを使用できないため、数人がテーブル値関数でエラーを発生させることについて質問していました。変数への無効なキャストの割り当ても同様に機能します。

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に。


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

1つの方法(ハック)は、無効なアクションを実行する関数/ストアドプロシージャを持つことです。たとえば、次の疑似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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.