J'écris une fonction définie par l'utilisateur dans SQL Server 2008. Je sais que les fonctions ne peuvent pas générer d'erreurs de la manière habituelle. Si vous essayez d'inclure l'instruction RAISERROR, SQL renvoie:
Msg 443, Level 16, State 14, Procedure ..., Line ...
Invalid use of a side-effecting operator 'RAISERROR' within a function.
Mais le fait est que la fonction prend une entrée, qui peut être invalide et, si c'est le cas, il n'y a pas de valeur significative que la fonction peut renvoyer. Qu'est-ce que je fais alors?
Bien sûr, je pourrais renvoyer NULL, mais il serait difficile pour tout développeur utilisant cette fonction de résoudre ce problème. Je pourrais aussi provoquer une division par zéro ou quelque chose comme ça - cela générerait un message d'erreur, mais trompeur. Existe-t-il un moyen de signaler mon propre message d'erreur?
Vous pouvez utiliser CAST pour générer une erreur significative:
create function dbo.throwError()
returns nvarchar(max)
as
begin
return cast('Error happened here.' as int);
end
Ensuite, SQL Server affichera des informations d'aide:
Msg 245, Level 16, State 1, Line 1
Conversion failed when converting the varchar value 'Error happened here.' to data type int.
L'astuce habituelle consiste à forcer une division par 0. Cela provoquera une erreur et interrompra l'instruction en cours qui évalue la fonction. Si le développeur ou le support technique est informé de ce problème, il est relativement facile d'analyser et de résoudre le problème, car l'erreur de division par 0 est comprise comme le symptôme d'un problème différent, non lié.
Aussi mauvais que cela puisse paraître, le design des fonctions SQL ne permet malheureusement pas de meilleur choix. L'utilisation de RAISERROR doit absolument être autorisée dans les fonctions.
Suite à la réponse de Vladimir Korolev, l’idiome de jeter conditionnellement une erreur est
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)
Je pense que le moyen le plus propre est simplement d’accepter que la fonction puisse renvoyer NULL si des arguments non valides sont passés. Aussi longtemps que cela est clairement documenté, cela devrait aller.
-- =============================================
-- 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
RAISEERROR
ou @@ERROR
ne sont pas autorisés dans les FDU. Pouvez-vous transformer le fichier UDF en procédure hiérarchisée?
Extrait de l'article d'Erland Sommarskog Traitement des erreurs dans SQL Server - un arrière-plan :
Les fonctions définies par l'utilisateur sont généralement appelées dans le cadre d'une instruction SET, SELECT, INSERT, UPDATE ou DELETE. Ce que j’ai trouvé, c’est que si une erreur apparaît dans une fonction table multi-instruction ou dans une fonction scalaire, l’exécution de la fonction est immédiatement interrompue, de même que l’instruction à laquelle appartient la fonction. L'exécution continue sur la ligne suivante, sauf si l'erreur a annulé le lot. Dans les deux cas, @@ error vaut 0. Il n'y a donc aucun moyen de détecter une erreur dans une fonction de T-SQL.
Le problème n'apparaît pas avec les fonctions table inline, puisqu’une fonction table inline est fondamentalement une macro que le processeur de requête colle dans la requête.
Vous pouvez également exécuter des fonctions scalaires avec l'instruction EXEC. Dans ce cas, l'exécution continue si une erreur survient (sauf s'il s'agit d'une erreur d'annulation de lot). @@ error est défini et vous pouvez vérifier la valeur de @@ error dans la fonction. Cependant, il peut être problématique de communiquer l'erreur à l'appelant.
La meilleure réponse est généralement préférable, mais ne fonctionne pas pour les fonctions à valeur de table en ligne.
MikeTeeVee a donné une solution à cela dans son commentaire sur la première réponse, mais cela nécessitait l'utilisation d'une fonction d'agrégat telle que MAX, qui ne fonctionnait pas bien dans mon cas.
J'ai déconné avec une autre solution pour le cas où vous avez besoin d'une table inline de valeur udf qui retourne quelque chose comme , sélectionnez * d'un agrégat. Vous trouverez ci-dessous un exemple de code permettant de résoudre ce cas particulier. Comme quelqu'un l'a déjà fait remarquer ... "JEEZ wotta hack" :) Je me réjouis de toute solution meilleure pour ce cas!
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
Quelques personnes se demandaient comment générer des erreurs dans les fonctions Table-Valued, car vous ne pouvez pas utiliser le type "RETURN [conversion invalide]". L'affectation de la distribution non valide à une variable fonctionne également.
CREATE FUNCTION fn()
RETURNS @T TABLE (Col CHAR)
AS
BEGIN
DECLARE @i INT = CAST('booooom!' AS INT)
RETURN
END
Cela se traduit par:
Msg 245, Niveau 16, Etat 1, Ligne 14 La conversion a échoué lors de la conversion de la valeur varchar 'booooom!' au type de données int.
Je ne peux pas commenter sous la réponse de davec concernant la fonction table value, mais à mon humble avis, cette solution est plus simple:
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