10

I have stored procedure in SQL Server 2012 say spXample and a scaler-valued function say fXample. I call a function fXample from spXample. Can I throw an exception in function and catch it in stored procedure's Catch block and rethrow to the calling C# code?

Update:

The function I wrote like:

CREATE FUNCTION dbo.fXample(@i INT)
RETURNS TINYINT
AS
BEGIN
  RETURN (SELECT CASE WHEN @i < 10 
    THEN THROW 51000,'Xample Exception',1;
    ELSE (SELECT @i) 
    END);
END
GO

I am getting error

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

How do I write alternative code to achieve above functionality?

bluish
  • 26,356
  • 27
  • 122
  • 180
MaxRecursion
  • 4,773
  • 12
  • 42
  • 76
  • Throw an exception in the function based on what type of condition? And what type of function, scalar, TVF, multi-statement TVF? – Aaron Bertrand Apr 05 '13 at 14:41
  • Try RAISERROR: http://msdn.microsoft.com/en-us/library/ms178592.aspx. If you give it a lower severity, it can be caught by a CATCH. From there, you can call it with a "critical" severity (I think 11+; there are examples on the page) and it will stop the SP's execution and kick it back to your application. – valverij Apr 05 '13 at 14:42
  • Aaron its scaler-valued, exception would be validation based. – MaxRecursion Apr 05 '13 at 14:45
  • 3
    Haven't got 2012 here to test with but I don't think you can. What happens if you try? On previous versions trying to use `RAISERROR` would fail with `Invalid use of a side-effecting operator 'RAISERROR' within a function.` – Martin Smith Apr 05 '13 at 15:19
  • 1
    @AaronBertrand see http://stackoverflow.com/questions/1485034/how-to-report-an-error-from-a-sql-server-user-defined-function how to handle this (+1 for pointing how to post such information) – Alex Apr 05 '13 at 19:33

4 Answers4

14

You can do this by forcing an error condition when your validation fails, provided that isn't a possible error that might occur naturally. When you know a certain error can only occur when validation has failed, you can handle that in a custom way by checking for that error_number in your catch block. Example in tempdb:

USE tempdb;
GO

CREATE FUNCTION dbo.fXample(@i INT)
RETURNS TINYINT
AS
BEGIN
  RETURN (SELECT CASE WHEN @i < 10 -- change this to your "validation failed" condition
    THEN 1/0         -- something that will generate an error
    ELSE (SELECT @i) -- (you'd have your actual retrieval code here)
    END);
END
GO

CREATE PROCEDURE dbo.spXample
  @i INT
AS
BEGIN
  SET NOCOUNT ON;
  BEGIN TRY
    SELECT dbo.fXample(@i);
  END TRY
  BEGIN CATCH
    IF ERROR_NUMBER() = 8134 -- divide by zero
    BEGIN
      THROW 50001, 'Your custom error message.', 1;
      -- you can throw any number > 50000 here
    END
    ELSE -- something else went wrong
    BEGIN
      THROW; -- throw original error
    END
  END CATCH
END
GO

Now try it out:

EXEC dbo.spXample @i = 10;  -- works fine
EXEC dbo.spXample @i = 6;   -- fails validation
EXEC dbo.spXample @i = 256; -- passes validation but overflows return

Results:

----
10

Msg 50001, Level 16, State 1, Procedure spXample, Line 12
Your custom error message.

Msg 220, Level 16, State 2, Procedure spXample, Line 7
Arithmetic overflow error for data type tinyint, value = 256.

Aaron Bertrand
  • 272,866
  • 37
  • 466
  • 490
  • 4
    Thanks, but what ridiculous hoops we have to jump though to do something which is so simple! Just shows that SQL is a language of the past which simply isn't moving on. :( – NickG Aug 09 '13 at 12:53
  • 2
    I'd just like to add THROW is from Sql Server 2012 – Luis Filipe Mar 06 '15 at 12:09
  • 2
    @NickG, You could use a stored procedure instead. Functions are actually an elegant way to say that they don't have side effects. Of course, they still do since you can throw a built in error - so it is not entirely true that they don't have side effects. It's an elegant way of having pure functions in a language. SQL is a language well ahead of its time and is pretty impressive considering how old it is. It is basically a DSL for set based data. Pretty amazing! – Jon49 Mar 22 '17 at 16:15
2
/*  *** EXAMPLES ***
    SELECT dbo.fnDoSomething(500) -- RETURNS TRUE
    
    SELECT dbo.fnDoSomething(5000) -- THROWS ERROR
        Msg 245, Level 16, State 1, Line 4
        Conversion failed when converting the varchar value 
        'Function Error - [dbo].[fnDoSomething] - Value is greater than 1000' to data type bit.
*/
CREATE FUNCTION dbo.fnDoSomething
(
    @SomeValue      INT
)
RETURNS BIT
AS
BEGIN
    IF @SomeValue > 1000
    BEGIN
        DECLARE @FunctionName AS SYSNAME = QUOTENAME(OBJECT_SCHEMA_NAME(@@PROCID)) + '.' + QUOTENAME(OBJECT_NAME(@@PROCID))
        DECLARE @Error AS VARCHAR(200) = 'Function Error - '+ @FunctionName + ' - Value is greater than 1000'
        RETURN  CONVERT(BIT,@Error)
    END

    RETURN 1
END
GO
Chris Rodriguez
  • 888
  • 7
  • 16
2

Although this has been solved but I feel I should still share this. As I think it will be a simpler solution:

BEGIN TRY
    THROW 60000, 'Error', 1;
END TRY
BEGIN CATCH
    THROW
END CATCH
Shashank Chaturvedi
  • 2,756
  • 19
  • 29
  • 1
    Msg 443, Level 16, State 14, Procedure test, Line 3 [Batch Start Line 57] Invalid use of a side-effecting operator '**BEGIN TRY**' within a function. Msg 443, Level 16, State 14, Procedure test, Line 7 [Batch Start Line 57] Invalid use of a side-effecting operator '**THROW**' within a function. Msg 443, Level 16, State 14, Procedure test, Line 5 [Batch Start Line 57] Invalid use of a side-effecting operator '**END TRY**' within a function. Msg 443, Level 16, State 14, Procedure test, Line 6 [Batch Start Line 57] Invalid use of a side-effecting operator '**BEGIN CATCH**' within a fun.... – Reversed Engineer Jun 30 '21 at 11:39
1

You can also raise an error in the stored procedure if a certain condition is met but the error severity has to be higher than 10 to throw an SQL exception

CREATE PROC test
AS
RAISERROR('the error message', 11, 1);
RETURN

you can access this message later in your catch block as such

try {
    testProc.ExecuteNonQuery();
}
catch(SqlException ex)
{
    System.Diagnostics.Debug.WriteLine(ex.Message);
}

Output : "the error message"

  • From [Microsoft's documentation on RAISEERROR](https://learn.microsoft.com/en-us/sql/t-sql/language-elements/raiserror-transact-sql): "The RAISERROR statement does not honor SET XACT_ABORT. New applications should use THROW instead of RAISERROR." This question is also about throwing an exception from within a function, not a stored procedure. – Michael Brandon Morris Apr 26 '22 at 16:00