2009-09-28 8 views
120

Sto scrivendo una funzione definita dall'utente in SQL Server 2008. So che le funzioni non possono generare errori nel solito modo - se si tenta di includere l'istruzione RAISERROR SQL restituisce:Come segnalare un errore da una funzione definita dall'utente di SQL Server

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

Ma il fatto è, la funzione richiede un po 'di ingresso, che può essere valido e, se lo è, non v'è alcun valore significativo la funzione può restituire. Cosa faccio allora?

Potrei, ovviamente, restituire NULL, ma sarebbe difficile per qualsiasi sviluppatore che utilizza la funzione per risolvere questo problema. Potrei anche causare una divisione per zero o qualcosa del genere - questo genererebbe un messaggio di errore, ma uno fuorviante. C'è un modo in cui posso avere il mio messaggio di errore segnalato in qualche modo?

risposta

182

È possibile utilizzare CAST per gettare errore significativo:

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

Poi SQL Server mostrare alcune informazioni di aiuto:

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

SI !!! Funziona! È geniale! – EMP

+86

Ottima risposta, ma JEEZ è stato hackerato. > :( – JohnL4

+3

Per una funzione di valore inline-table dove il RETURN è una selezione semplice, questo da solo non funziona perché non viene restituito nulla - nemmeno null, e nel mio caso ho voluto lanciare un errore quando non è stato trovato nulla Non volevo suddividere la funzione inline in una multi-statment per ragioni evidenti di prestazioni, ma ho usato la tua soluzione più ISNULL e MAX.La statica RETURN ora appare così: SELECT ISNULL (MAX (E.EntityID) , CAST ('La ricerca (' + @LookupVariable + ') non esiste.' Come Int)) [EntityID] FROM Entity come E DOVE E.Lookup = @ LookupVariable – MikeTeeVee

5

RAISEERROR o @@ERROR non sono consentiti nelle UDF. Riesci a trasformare l'UDF in una procedura stridula?

Dall'articolo di Erland Sommarskog 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

Un modo (un hack) è quello di avere una stored procedure funzione/che esegue un'azione non valida. Ad esempio, la seguente 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; 

Qualora sul tbl_throw_error tavolo, c'è un vincolo univoco sulla err_msg colonna. Un effetto collaterale di questo (almeno su MySQL) è che il valore di err_msg viene utilizzato come descrizione dell'eccezione quando viene ripristinato nell'oggetto eccezione a livello di applicazione.

Non so se è possibile eseguire operazioni simili con SQL Server, ma vale la pena provare.

+5

Idea interessante, ma INSERT non è consentito in una funzione, sia. – EMP

13

Il solito trucco consiste nel forzare una divisione per 0. Ciò genera un errore e interrompe l'istruzione corrente che sta valutando la funzione. Se lo sviluppatore o la persona di supporto sono a conoscenza di questo comportamento, investigare e risolvere il problema è abbastanza facile poiché l'errore di divisione per 0 è inteso come sintomo di un problema diverso e non correlato.

Per quanto possa sembrare negativo da un qualsiasi punto di vista, sfortunatamente il design delle funzioni SQL al momento non offre una scelta migliore. Usare RAISERROR deve essere assolutamente consentito nelle funzioni.

3

Penso che il modo più semplice sia accettare semplicemente che la funzione possa restituire NULL se vengono passati argomenti non validi. Finché questo è chiaramente documentato, allora dovrebbe andar bene?

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

A seguito di risposta di Vladimir Korolev, l'idioma di condizionale un errore è

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

La risposta superiore è generalmente la migliore, ma non funziona per le funzioni con valori inline.

MikeTeeVee ha fornito una soluzione per questo nel suo commento sulla risposta principale, ma ha richiesto l'uso di una funzione di aggregazione come MAX, che non ha funzionato bene per la mia circostanza.

Ho sbagliato in giro con una soluzione alternativa per il caso in cui è necessario un valore UDF tabella inline che restituisce qualcosa come select * al posto di un aggregato. Il codice di esempio che risolve questo caso particolare è di seguito. Come qualcuno ha già fatto notare ... "JEEZ wotta hack" :) Accolgo con favore qualsiasi soluzione migliore per questo caso!

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

per chiunque stia leggendo, non ho guardato i potenziali effetti delle prestazioni ... non sarei sorpreso se l'istruzione hack union + case rallenti le cose ... – davec

2

Non posso commentare sotto la risposta di davec per quanto riguarda valori di tabella funzione, ma a mio modesto parere questa è la soluzione più semplice:

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

un paio di persone sono state chiedendo circa sollevare errori nelle funzioni con valori di tabella , dal momento che non è possibile utilizzare "RETURN [invalid cast]" in una sorta di "cose". L'assegnazione del cast non valido a una variabile funziona altrettanto bene.

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

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

RETURN 

END 

Questo si traduce in:

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

Problemi correlati