2009-09-28 3 views
120

Ich schreibe eine benutzerdefinierte Funktion in SQL Server 2008. Ich weiß, dass Funktionen Fehler auf die übliche Weise nicht auslösen können - wenn Sie versuchen, die RAISERROR-Anweisung aufzunehmen SQL zurückgibt:So melden Sie einen Fehler von einer benutzerdefinierten SQL Server-Funktion

Msg 443, Level 16, State 14, Procedure ..., Line ... 
Invalid use of a side-effecting operator 'RAISERROR' within a function. 

Aber die Tatsache ist, nimmt die Funktion eine Eingabe, die ungültig sein kann, und wenn es ist, gibt es keinen sinnvollen Wert der Funktion zurückgeben kann. Was mache ich dann?

Ich könnte natürlich NULL zurückgeben, aber es wäre schwierig für jeden Entwickler, der die Funktion verwendet, um dies zu beheben. Ich könnte auch eine Division durch Null oder Ähnliches verursachen - das würde eine Fehlermeldung erzeugen, aber eine irreführende. Kann ich irgendwie meine eigene Fehlermeldung melden?

Antwort

182

Sie CAST sinnvolle Fehler werfen können:

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

Dann wird SQL Server etwas Hilfe Informationen zeigen:

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

JA !!! Es klappt! Das ist hervorragend! – EMP

+86

Große Antwort, aber JEEZ Wotta Hack. > :( – JohnL4

+3

Für eine Inline-Tabelle-Wert-Funktion, wo die RETURN ist eine einfache Auswahl, das allein funktioniert nicht, weil nichts zurückgegeben wird - nicht einmal null, und in meinem Fall wollte ich einen Fehler werfen, wenn nichts gefunden wurde Ich wollte die Inline-Funktion aus offensichtlichen Performance-Gründen nicht in eine Multi-Statement-Funktion zerlegen, stattdessen habe ich Ihre Lösung plus ISNULL und MAX verwendet. Die RETURN-Anweisung sieht nun so aus: SELECT ISNULL (MAX (E.EntityID) , CAST ('Das Lookup (' + @LookupVariable + ') existiert nicht.' As Int)) [EntityID] FROM Entity als E WHERE E.Lookup = @ LookupVariable – MikeTeeVee

5

RAISEERROR oder @@ERROR sind in UDFs nicht erlaubt. Kannst du die UDF in eine unterstützte Prozedur umwandeln?

Von Erland Sommarskog des Artikels Error Handling in SQL Server – a Background:

User-defined functions are usually invoked as part of a SET, SELECT, INSERT, UPDATE or DELETE statement. What I have found is that if an error appears in a multi-statement table-valued function or in a scalar function, the execution of the function is aborted immediately, and so is the statement the function is part of. Execution continues on the next line, unless the error aborted the batch. In either case, @@error is 0. Thus, there is no way to detect that an error occurred in a function from T-SQL.

The problem does not appear with inline table-functions, since an inline table-valued function is basically a macro that the query processor pastes into the query.

You can also execute scalar functions with the EXEC statement. In this case, execution continues if an error occurs (unless it is a batch-aborting error). @@error is set, and you can check the value of @@error within the function. It can be problematic to communicate the error to the caller though.

-3

Eine Möglichkeit (ein Hack) ist eine Funktion/gespeicherte Prozedur zu haben, die eine ungültige Aktion ausführt. Zum Beispiel nach dem Pseudo-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; 

Wo auf dem Tisch tbl_throw_error, gibt es eine eindeutige Einschränkung auf der Spalte ERR_MSG ist. Ein Nebeneffekt davon (zumindest bei MySQL) ist, dass der Wert von err_msg als Beschreibung der Ausnahme verwendet wird, wenn sie wieder in das Ausnahmeobjekt auf Anwendungsebene gelangt.

Ich weiß nicht, ob Sie etwas ähnliches mit SQL Server tun können, aber einen Versuch wert.

+5

Interessante Idee, aber INSERT ist nicht in einer Funktion erlaubt, auch nicht. – EMP

13

Der übliche Trick besteht darin, eine Division durch 0 zu erzwingen. Dies führt zu einem Fehler und unterbricht die aktuelle Anweisung, die die Funktion bewertet. Wenn der Entwickler oder die unterstützende Person von diesem Verhalten Kenntnis hat, ist das Untersuchen und Beheben des Problems ziemlich einfach, da der Fehler "Division durch 0" als ein Symptom eines anderen, nicht verwandten Problems verstanden wird.

So schlecht, wie es aus irgendeinem Gesichtspunkt aussieht, leider das Design von SQL-Funktionen im Moment keine bessere Wahl ermöglicht. Die Verwendung von RAISERROR sollte in Funktionen absolut erlaubt sein.

3

Ich denke, der sauberste Weg ist einfach zu akzeptieren, dass die Funktion NULL zurückgeben kann, wenn ungültige Argumente übergeben werden. Solange dies klar dokumentiert ist, sollte dies in Ordnung sein?

-- ============================================= 
-- 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 
7

Im Anschluss an Vladimir Korolev Antwort, das Idiom bedingt einen Fehler werfen ist

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

Die beste Antwort ist im Allgemeinen am besten, funktioniert aber nicht für Inline-Tabellenwerte.

MikeTeeVee gab eine Lösung dafür in seinem Kommentar auf die Top-Antwort, aber es erfordert die Verwendung einer Aggregatfunktion wie MAX, die für meine Umstände nicht gut funktioniert.

ich messed um mit einer alternativen Lösung für den Fall, dass Sie eine Inline-Tabellenwert UDF müssen, die so etwas wie select * anstelle eines Aggregats zurückgibt. Beispielcode, der diesen speziellen Fall löst, ist unten. Wie schon jemand darauf hingewiesen hat ... "JEEZ wotta hack" :) Ich begrüße jede bessere Lösung für diesen Fall!

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 
+0

für jeden, der liest, habe ich nicht auf mögliche Leistungseffekte geschaut ... ich wäre nicht überrascht, wenn die Hack Union + Case-Anweisung Dinge verlangsamt ... – davec

2

ich nicht unter davec Antwort in Bezug auf Tabellenwertfunktion äußern kann, aber in meiner bescheidenen Meinung nach ist dies die einfachere Lösung:

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

Einige Leute über fragten Fehler in Tabellenwertfunktionen erhöhen , da Sie nicht "RETURN [ungültige Besetzung]" Art der Dinge verwenden können. Das Zuweisen der ungültigen Besetzung zu einer Variablen funktioniert genauso gut.

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

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

RETURN 

END 

Daraus ergibt sich:

Msg 245, Level 16, State 1, Line 14 Conversion failed when converting the varchar value 'booooom!' to data type int.

Verwandte Themen