web-dev-qa-db-ja.com

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を返すこともできますが、この関数を使用している開発者がこれをトラブルシューティングすることは困難です。また、ゼロまたはそのようなものによる除算を引き起こす可能性があります-これはエラーメッセージを生成しますが、誤解を招くものです。自分のエラーメッセージを何らかの形で報告する方法はありますか?

141
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.
212

通常のトリックは、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
        FromCurrencyCode=@CurrencyFrom AND
        ToCurrencyCode=@CurrencyTo AND
        DateID=dbo.DateToIntegerKey(@OnDate)

    RETURN @ClosingRate 

END
GO
6
AndyM

RAISEERRORまたは@@ERRORはUDFでは使用できません。 UDFを記憶された手順に変えることができますか?

Erland Sommarskogの記事から SQL Serverでのエラー処理-背景

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

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

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

5
Mitch Wheat

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

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

"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へ。

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