I am using a try-catch block in T-SQL, and I want to only catch a specific error number. In other cases, I am using RAISERROR() as a poor-man's re-throw to return error information to the application.
When I try the following, I get an "Incorrect syntax near 'error_message'" error:
raiserror
(
error_message()
,1
,1
)
The following, however, works fine:
declare #err varchar(100)
set #err = error_message()
raiserror
(
#err
,1
,1
)
I thought it might be a typecasting quirk, so I tried this, but that also yielded a syntax error:
raiserror
(
cast(error_message() as varchar(100))
,1
,1
)
What's going on here? Why do I have to store the result of ERROR_MESSAGE() in a variable before using it as a parameter to RAISERROR(), instead of calling the function directly?
Below post answers your Question: https://stackoverflow.com/a/3415125/639960
In a nutshell (quoted from above post):
RAISERROR follows the same rules as any other stored procedure call.
Parameters passed in must be a constant or a variable. You cannot pass
a function directly as a parameter.
See Executing Stored Procedures for documentation on this.
Try switching error type by uncommenting set #Fail= GETDATE() and see its not very realiable
set nocount on
begin
declare #ErrorMessage varchar(100)
declare #ErrorState varchar(100)
declare #ErrorSeverity varchar(100)
declare #ErrorNumber varchar(100)
declare #Fail int
begin try
--set #Fail= GETDATE()
set #Fail = 1/0
end try
begin catch
print 'Why can''t it catch all type of errors like implicit conversion'
SELECT #ErrorMessage = ERROR_MESSAGE(), #ErrorSeverity = ERROR_SEVERITY(), #ErrorState = ERROR_STATE();
RAISERROR (#ErrorMessage -- Message text.
,#ErrorState -- State.
,#ErrorSeverity -- Severity.
);
end catch
end
Related
I have two stored procedures spParent and spParentChild. spParent calls spParentChild. Following the SPs:
CREATE PROCEDURE [dbo].[spParent]
#isApproved varchar(max) = 'TEST',
#ApplicationDBName varchar(100) = 'secDB',
#ApplicationInsertSPName varchar(100) = 'spParentChild',
#resultP varchar(max) output
AS
BEGIN
BEGIN TRY
SET NOCOUNT ON;
IF #resultP <> ''
BEGIN
RAISERROR('An error occurred in the parent SP.', 18, 0)
END
DECLare #resultCC varchar(max)
DECLARE #SP_Call Varchar(MAX)
SET #SP_Call = 'exec spParentChild ''1'', #resultCC output'
EXEC #SP_Call
select #resultCC
END TRY
BEGIN CATCH
Declare #ErrMessage Varchar(max)
Declare #ErrState Varchar(max)
Declare #ErrLine Varchar(max)
select #ErrMessage=ErrorMessage, #ErrLine=ErrorLine, #ErrState=ErrorState from fnGetError()
IF #ErrMessage is not Null
BEGIN
SELECT #resultP = #ErrMessage
SELECT #resultCC
END
ELSE
BEGIN
SELECT #resultP = 'An unknown error occurred.'
raiserror(#resultP,18,0)
END
END CATCH
END
CREATE PROCEDURE [dbo].[spParentChild]
#isApproved varchar(max) = 'TEST',
#resultC varchar(max) output
AS
BEGIN
BEGIN TRY
SET NOCOUNT ON;
RAISERROR('An error occurred in the child SP.', 18, 0)
END TRY
BEGIN CATCH
Declare #ErrMessage Varchar(max)
Declare #ErrState Varchar(max)
Declare #ErrLine Varchar(max)
select #ErrMessage=ErrorMessage, #ErrLine=ErrorLine, #ErrState=ErrorState from fnGetError()
IF #ErrMessage is not Null
BEGIN
SET #resultC = #ErrMessage
END
ELSE
BEGIN
SELECT #resultC = 'An unknown error occurred.'
END
END CATCH
END
As you can see spParent calls spParentChild. What I am needing is to use TSQL for the call, but it does not work. If I use the following all works fine, but I need TSQL:
EXEC spParentChild 1, #resultCC output
Can anyone help me in seeing what I am doing wrong or if this is even possible at all?
Thank you in advance.
Godfrey
To those who have a similar problem or need I am posting this solution I put together. It may sound as if this is crazy, but please believe when I tell you that I do require this and the actual SPs that need this are too long to post.
The spParentChild has not changed and remains as above. spParent is changed as shown below. The result is that spParent calls spParentChild. In his case if an error occurs in spParentChild the message is passed up to spParent in the output parameter.
I shall attempt to answer any questions should you have any. Following is spParent:
ALTER PROCEDURE [dbo].[spParent]
#isApproved varchar(max) = 'TEST',
#ApplicationDBName varchar(100) = 'SecDB',
#ApplicationInsertSPName varchar(100) = 'spParentChild',
#resultP varchar(max) output
AS
BEGIN
BEGIN TRY
SET NOCOUNT ON;
IF #resultP <> ''
BEGIN
RAISERROR('An error occurred in the parent SP.', 18, 0)
END
DECLARE #params NVarChar(max)
SET #params = '#resultCC VarChar(100) OUTPUT' -- OUTPUT Parameter for the called child SP
DECLARE #SP_Call NVarchar(MAX) -- sp_executesql requires NVarChar
SET #SP_Call = '[' + #ApplicationDBName + '].[dbo].[' + #ApplicationInsertSPName + '] ' + ' ''1'', #resultCC OUTPUT' -- ResultCC is the output parameter for the sp_executesql
EXEC sp_executesql #SP_Call, #params, #resultCC = #resultP OUTPUT -- Assign the value to the output parameter of this SP
-- EXEC spParentChild 1, #resultCC output -- THE ABOVE 5 LINES OF CODE ARE EQUAL TO THIS IS A HARDCODED CALL
if #resultP <> ''
BEGIN
SELECT #resultP [Returned Value]
END
END TRY
BEGIN CATCH
Declare #ErrMessage Varchar(max)
Declare #ErrState Varchar(max)
Declare #ErrLine Varchar(max)
select #ErrMessage=ErrorMessage, #ErrLine=ErrorLine, #ErrState=ErrorState from fnGetError()
IF #ErrMessage is not Null
BEGIN
SELECT #resultP = #ErrMessage
SELECT #ErrMessage
END
ELSE
BEGIN
SELECT #resultP = 'An unknown error occurred.'
SELECT 'bbb'
raiserror(#resultP,18,0)
END
END CATCH
END
I hope this assists someone in such need.
I would like to create a more specific error message for Postgres CHECK IN violations. So for example a violation of the following CHECK constraint on a column:
management_zone varchar(15) NOT NULL CHECK (management_zone IN ('Marine', 'Terrestrial') ),
should return a custom error message such as ie.: "Hint: Check spelling. Only allowed inputs are: 'Marine', 'Terrestrial'.
The best solution I have seen so far solves it by using the error message as the name of the check constraint, ie
ADD CONSTRAINT c_managementzone_#Check_spelling._Only_allowed_inputs_are:_'Marine',_'Terrestrial' CHECK (management_zone IN ('Marine', 'Terrestrial') ),
and then applying a function that fixes the error message after the # sign by replacing the underscores with space to make it more readable. The function is then called using a try and catch statement.
This code however is in t-sql which I am unfamiliar with, and I also don't know enough PL/pgSQL to be able to convert and replicate it. I therefore wonder if anyone can suggest how something similar can be done in postgres, ex. using a function and a trigger?
The t-sql code and explanation is available from here and I copy paste it below:
https://social.technet.microsoft.com/wiki/contents/articles/29187.t-sql-error-handling-for-check-constraints.aspx
CREATE FUNCTION dbo.ufnGetClearErrorMessage2()
RETURNS NVARCHAR(4000)
AS
BEGIN
DECLARE #Msg NVARCHAR(4000) = ERROR_MESSAGE() ;
DECLARE #ErrNum INT = ERROR_NUMBER() ;
DECLARE #ClearMessage NVARCHAR(4000) ;
IF #ErrNum = 547
BEGIN
/*--how to find #ClearMessage:
SELECT #msg ,
CHARINDEX('#', #msg) ,
RIGHT(#msg, LEN(#msg) - CHARINDEX('#', #msg)) ,
CHARINDEX('"', RIGHT(#msg, LEN(#msg) - CHARINDEX('#', #msg))) ,
LEFT(RIGHT(#msg, LEN(#msg) - CHARINDEX('#', #msg)), CHARINDEX('"', RIGHT(#msg, LEN(#msg) - CHARINDEX('#', #msg))) - 1 ) ,
REPLACE(LEFT(RIGHT(#msg, LEN(#msg) - CHARINDEX('#', #msg)), CHARINDEX('"', RIGHT(#msg, LEN(#msg) - CHARINDEX('#', #msg))) - 1 ), '_', SPACE(1)) + '.'
*/
SELECT #ClearMessage = #Msg + CHAR(13) +
REPLACE(LEFT(RIGHT(#msg, LEN(#msg) - CHARINDEX('#', #msg)), CHARINDEX('"', RIGHT(#msg, LEN(#msg) - CHARINDEX('#', #msg))) - 1 ), '_', SPACE(1)) + '.'
END
ELSE
SET #ClearMessage = #Msg ;
RETURN #ClearMessage ;
END
Try and catch:
BEGIN TRY
INSERT dbo.Book
( BookId, WritingDate, publishDate )
VALUES ( 1, GETDATE(), GETDATE() + 1 )
END TRY
BEGIN CATCH
DECLARE #Msg NVARCHAR(4000) = dbo.ufnGetClearErrorMessage2();
THROW 60001, #Msg, 1;
END CATCH
Any advice much apreciated.
If you can live with a slightly different check constraint, you can do the following:
Create a function that checks the values:
create function check_zone(p_input text)
returns boolean
as
$$
declare
l_allowed text[] := array['Marine', 'Terrestrial'];
begin
if p_input = any(l_allowed) then
return true;
end if;
raise 'The only allowed values are: %', array_to_string(l_allowed, ', ');
end;
$$
language plpgsql
immutable;
And then use that function instead of an IN condition:
create table data
(
management_zone text not null,
CONSTRAINT check_zone CHECK (check_zone(management_zone))
);
The following INSERT
insert into data values ('foo');
will result in:
ERROR: The only allowed values are: Marine, Terrestrial
I'm trying to improve the error handling of a current system to produce more meaningful error messages. I have a "root" stored procedure that makes several calls to other nested stored procedures.
In the root sp, XACT_ABORT is set to ON but in the nested procedures, XACT_ABORT is set to OFF. I want to capture the specific errors from the lower level procedures rather than getting the root procedure's error.
I often see the error, uncommittable transaction is detected at the end of the batch, the transaction is being rolled back.
Is there any effect to having these "mixed" environments with the XACT_ABORTs?
Also, if you have any suggestions for advanced error handling, that would be much appreciated. I think I would like to use sp_executesql so I can pass parameters to get error output without having to modify all of the stored procedures and use RAISERROR to invoke the parent procedure's CATCH block.
As per Andomar's answer here and MSDN:
The setting of SET XACT_ABORT is set at execute or run time and not at
parse time
i.e. XACT_ABORT will not be 'copied' from the creation session to each procedure, so any PROC which doesn't explicitly set this option internally will inherit the setting from the ambient session at run time, which can be disastrous.
FWIW, as a general rule, we always ensure that XACT_ABORT is ON globally and do a lint check to ensure none of our PROCs have overridden this setting.
Note that XACT_ABORT isn't a silver bullet, however - e.g. errors that have been raised by your PROC with RAISERROR won't terminate the batch. However, it seems that this is improved with the THROW keyword in SQL 2012
As you've suggested, and as per Remus Rusanu's observation, structured exception handling (TRY / CATCH) is a much more clean and robust mechanism for handling of exceptions.
A way to keep XACT_ABORT on and get errors if any or commit if all is fine when calling SP that may call other SP: two sp and three tests as example
create PROCEDURE [dbo].[myTestProcCalled]
(
#testin int=0
)
as
begin
declare #InnerTrans int
set XACT_ABORT on;
set #InnerTrans = ##trancount;
PRINT '02_01_Trancount='+cast (#InnerTrans as varchar(2));
begin try
if (#InnerTrans = 0)
begin
PRINT '02_02_beginning trans';
begin transaction
end
declare #t2 int
set #t2=0
PRINT '02_03_doing division'
set #t2=10/#testin
PRINT '02_04_doing AfterStuff'
if (#InnerTrans = 0 and XACT_STATE()=1)
begin
PRINT '02_05_Committing'
commit transaction
end
PRINT '02_05B_selecting calledValue=' +cast(#t2 as varchar(20))
select #t2 as insidevalue
end try
begin catch
PRINT '02_06_Catching Errors from called'
declare #ErrorMessage nvarchar(4000);
declare #ErrorNumber int;
declare #ErrorSeverity int;
declare #ErrorState int;
select #ErrorMessage = error_message(), #ErrorNumber = error_number(), #ErrorSeverity = error_severity(), #ErrorState = error_state();
if (#InnerTrans = 0 and XACT_STATE()=-1)
begin
PRINT '02_07_Rolbacking'
rollback transaction
end
PRINT '02_08_Rising Error'
raiserror(#ErrorMessage, #ErrorSeverity, #ErrorState);
--use throw if in 2012 or above
-- else might add a "return" statement
end catch
end
go
create PROCEDURE [dbo].[myTestPCalling]
(
#test int=0
,#testinside int=0
)
as
begin
declare #InnerTrans int
set XACT_ABORT on;
set #InnerTrans = ##trancount;
PRINT '01_01_Trancount='+cast (#InnerTrans as varchar(2));
begin try
if (#InnerTrans = 0)
begin
PRINT '01_02_beginning trans';
begin transaction
end
declare #t2 int
set #t2=0
PRINT '01_03_doing division'
set #t2=10/#test
PRINT '01_04_calling inside sp'
execute [dbo].[myTestProcCalled]
#testin = #testinside
--
PRINT '01_05_doing AfterStuff'
if (#InnerTrans = 0 and XACT_STATE()=1)
begin
PRINT '01_06_Committing'
commit transaction
PRINT '01_06B_selecting callerValue=' +cast(#t2 as varchar(20))
select #t2 as outsidevalue
end
end try
begin catch
PRINT '01_07_Catching Errors from Caller'
declare #ErrorMessage nvarchar(4000);
declare #ErrorNumber int;
declare #ErrorSeverity int;
declare #ErrorState int;
select #ErrorMessage = error_message(), #ErrorNumber = error_number(), #ErrorSeverity = error_severity(), #ErrorState = error_state();
if (#InnerTrans = 0 and XACT_STATE()=-1)
begin
PRINT '01_08_Rolbacking'
rollback transaction
end
PRINT '01_09_Rising Error'
raiserror(#ErrorMessage, #ErrorSeverity, #ErrorState);
--use throw if in 2012 or above
-- else might add a "return" statement
end catch
end
----test 1 :result OK----
USE [PRO-CGWEB]
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[myTestPCalling]
#test =2
,#testinside = 2
SELECT 'Return Value' = #return_value
GO
----test2 :error in caller ----
USE [PRO-CGWEB]
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[myTestPCalling]
#test =0
,#testinside = 2
SELECT 'Return Value' = #return_value
GO
----test3 :error in calling ----
USE [PRO-CGWEB]
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[myTestPCalling]
#test =2
,#testinside = 0
SELECT 'Return Value' = #return_value
GO
The title really is the question for this one: Is there an equivalent in T-SQL to C#'s "throw;" to re-throw exceptions?
In C# one can do this:
try
{
DoSomethingThatMightThrowAnException();
}
catch (Exception ex)
{
// Do something with the exception
throw; // Re-throw it as-is.
}
Is there something in T-SQL's BEGIN CATCH functionality that does the same?
You can use RAISERROR. From the MSDN documentation on RAISERROR:
BEGIN TRY
-- RAISERROR with severity 11-19 will cause execution to
-- jump to the CATCH block
RAISERROR ('Error raised in TRY block.', -- Message text.
16, -- Severity.
1 -- State.
);
END TRY
BEGIN CATCH
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT #ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
-- Use RAISERROR inside the CATCH block to return
-- error information about the original error that
-- caused execution to jump to the CATCH block.
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
END CATCH;
EDIT:
This is not really the same thing as c#'s throw or throw ex. As #henrikstaunpoulsen points out you don't get the original error number in the new error (RAISERROR is restricted in which numbers it can use). You would have to use some sort of convention and parse the information (if available) out of the message.
MSDN has an article Using TRY...CATCH in Transact-SQL and I used some of the code to create the test below:
use test;
GO
IF OBJECT_ID (N'usp_RethrowError',N'P') IS NOT NULL
DROP PROCEDURE usp_RethrowError;
GO
CREATE PROCEDURE usp_RethrowError AS
IF ERROR_NUMBER() IS NULL
RETURN;
DECLARE
#ErrorMessage NVARCHAR(4000),
#ErrorNumber INT,
#ErrorSeverity INT,
#ErrorState INT,
#ErrorLine INT,
#ErrorProcedure NVARCHAR(200);
SELECT
#ErrorNumber = ERROR_NUMBER(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE(),
#ErrorLine = ERROR_LINE(),
#ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
SELECT #ErrorMessage =
N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' +
'Message: '+ ERROR_MESSAGE();
RAISERROR
(
#ErrorMessage,
#ErrorSeverity,
#ErrorState,
#ErrorNumber, -- parameter: original error number.
#ErrorSeverity, -- parameter: original error severity.
#ErrorState, -- parameter: original error state.
#ErrorProcedure, -- parameter: original error procedure name.
#ErrorLine -- parameter: original error line number.
);
GO
PRINT 'No Catch'
DROP TABLE XXXX
PRINT 'Single Catch'
BEGIN TRY
DROP TABLE XXXX
END TRY
BEGIN CATCH
EXEC usp_RethrowError;
END CATCH;
PRINT 'Double Catch'
BEGIN TRY
BEGIN TRY
DROP TABLE XXXX
END TRY
BEGIN CATCH
EXEC usp_RethrowError;
END CATCH;
END TRY
BEGIN CATCH
EXEC usp_RethrowError;
END CATCH;
Which produces the following output:
No Catch
Msg 3701, Level 11, State 5, Line 3
Cannot drop the table 'XXXX', because it does not exist or you do not have permission.
Single Catch
Msg 50000, Level 11, State 5, Procedure usp_RethrowError, Line 25
Error 3701, Level 11, State 5, Procedure -, Line 7, Message: Cannot drop the table 'XXXX', because it does not exist or you do not have permission.
Double Catch
Msg 50000, Level 11, State 5, Procedure usp_RethrowError, Line 25
Error 50000, Level 11, State 5, Procedure usp_RethrowError, Line 25, Message: Error 3701, Level 11, State 5, Procedure -, Line 16, Message: Cannot drop the table 'XXXX', because it does not exist or you do not have permission.
In SQL 2012 they added the new THROW keyword, that can also be used to re-throw an exception
USE tempdb;
GO
CREATE TABLE dbo.TestRethrow
( ID INT PRIMARY KEY
);
BEGIN TRY
INSERT dbo.TestRethrow(ID) VALUES(1);
-- Force error 2627, Violation of PRIMARY KEY constraint to be raised.
INSERT dbo.TestRethrow(ID) VALUES(1);
END TRY
BEGIN CATCH
PRINT 'In catch block.';
THROW;
END CATCH;
http://msdn.microsoft.com/en-us/library/ee677615.aspx
Here is what I have used to rethrow an exception after rolling back the transaction. This gives the line number information of the error too.
BEGIN TRY
BEGIN TRANSACTION -- Start the transaction
-- Do your work here
-- Commit the transaction
COMMIT TRANSACTION
END TRY
BEGIN CATCH
-- There was an error, rollback the transaction
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
-- Raise an error with the details of the exception
DECLARE #ErrorMessage nvarchar(2048)
DECLARE #ErrorProcedure nvarchar(128)
DECLARE #ErrorState int
DECLARE #ErrorLine int
DECLARE #ErrorSeverity int
SET #ErrorProcedure = ERROR_PROCEDURE()
SET #ErrorLine = ERROR_LINE()
SET #ErrorSeverity = ERROR_SEVERITY()
SET #ErrorState = ERROR_STATE()
SET #ErrorMessage = ''
IF #ErrorProcedure IS NOT NULL
SET #ErrorMessage = #ErrorMessage + #ErrorProcedure + ' ';
IF #ErrorLine IS NOT NULL
SET #ErrorMessage = #ErrorMessage + '[Line ' + CAST(#ErrorLine as nvarchar) + '] ';
SET #ErrorMessage = #ErrorMessage + ERROR_MESSAGE()
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState)
END CATCH
In order to prevent the repetition of procedure information/error/line numbers in multiple catch scenarios, I use a similar procedure, with the slight modification as follows:
IF #Error_Procedure <> OBJECT_NAME(##PROCID)
BEGIN
RAISERROR('[Procedure: %s]: Nest Level: %d; Line: %d; Error Number: %d; Message: %s',#Error_Severity,#Error_State,#Error_Procedure, #NestLevel, #Error_Line, #Error_Number, #Error_Message)
END
ELSE
BEGIN
RAISERROR(#Error_Message,#Error_Severity,#Error_State)
END
So if we have already caught and re-raised the error with this SP, we don't repeatedly add the additional information, so at the outer scope, we see only the error as originally re-raised.
In the examples posted above, the double-catch output would be the same as the single-catch output. I also include the nest level in the error message to aid with debugging.
you can raise exceptions using RAISEERROR
http://msdn.microsoft.com/en-us/library/ms178592.aspx
I generally use the following:
DECLARE #Outcome as bit
DECLARE #Error as int
BEGIN TRANSACTION
-- *** YOUR TSQL TRY CODE HERE ****
-- Capture the TSQL outcome.
SET #Error = ##ERROR
-- Set the Outcome to be returned to the .NET code to successful
SET #Outcome = 1
IF #Error <> 0
BEGIN
-- An Error was generate so we invoke ROLLBACK
ROLLBACK
-- We set the Outcome to be returned to .Net to unsuccessful
SET #Outcome = 0
end
ELSE
BEGIN
-- The transaction was successful, invoke COMMIT
COMMIT
END
-- Returning a boolean value to the .NET code
Select #Outcome as Outcome
Basically I want to use PRINT statement inside a user defined function to aide my debugging.
However I'm getting the following error;
Invalid use of side-effecting or time-dependent operator in 'PRINT'
within a function.
Can this not be done?
Anyway to aid my user defined function debugging?
Tip:
generate error.
declare #Day int, #Config_Node varchar(50)
set #Config_Node = 'value to trace'
set #Day = #Config_Node
You will get this message:
Conversion failed when converting the varchar value 'value to trace'
to data type int.
No, sorry. User-defined functions in SQL Server are really limited, because of a requirement that they be deterministic. No way round it, as far as I know.
Have you tried debugging the SQL code with Visual Studio?
I got around this by temporarily rewriting my function to something like this:
IF OBJECT_ID ('[dbo].[fx_dosomething]', 'TF') IS NOT NULL
drop function [dbo].[fx_dosomething];
GO
create FUNCTION dbo.fx_dosomething ( #x numeric )
returns #t table (debug varchar(100), x2 numeric)
as
begin
declare #debug varchar(100)
set #debug = 'printme';
declare #x2 numeric
set #x2 = 0.123456;
insert into #t values (#debug, #x2)
return
end
go
select * from fx_dosomething(0.1)
I have tended in the past to work on my functions in two stages. The first stage would be to treat them as fairly normal SQL queries and make sure that I am getting the right results out of it. After I am confident that it is performing as desired, then I would convert it into a UDF.
Use extended procedure xp_cmdshell to run a shell command. I used it to print output to a file:
exec xp_cmdshell 'echo "mytextoutput" >> c:\debuginfo.txt'
This creates the file debuginfo.txt if it does not exist. Then it adds the text "mytextoutput" (without quotation marks) to the file. Any call to the function will write an additional line.
You may need to enable this db-server property first (default = disabled), which I realize may not be to the liking of dba's for production environments though.
No, you can not.
You can call a function from a stored procedure and debug a stored procedure (this will step into the function)
On my opinion, whenever I want to print or debug a function. I will copy the content of it to run as a normal SQL script. For example
My function:
create or alter function func_do_something_with_string(#input nvarchar(max)) returns nvarchar(max)
as begin
-- some function logic content
declare #result nvarchar(max)
set #result = substring(#input , 1, 10)
-- or do something else
return #result
end
Then I just copy and run this out of the function to debug
declare #input nvarchar(max) = 'Some string'
-- some function logic content
declare #result nvarchar(max)
set #result = substring(#input , 1, 10)
-- this line is added to check while debugging
print #result
-- or do something else
-- print the final result
print #result
You can try returning the variable you wish to inspect.
E.g. I have this function:
--Contencates seperate date and time strings and converts to a datetime. Date should be in format 25.03.2012. Time as 9:18:25.
ALTER FUNCTION [dbo].[ufn_GetDateTime] (#date nvarchar(11), #time nvarchar(11))
RETURNS datetime
AS
BEGIN
--select dbo.ufn_GetDateTime('25.03.2012.', '9:18:25')
declare #datetime datetime
declare #day_part nvarchar(3)
declare #month_part nvarchar(3)
declare #year_part nvarchar(5)
declare #point_ix int
set #point_ix = charindex('.', #date)
set #day_part = substring(#date, 0, #point_ix)
set #date = substring(#date, #point_ix, len(#date) - #point_ix)
set #point_ix = charindex('.', #date)
set #month_part = substring(#date, 0, #point_ix)
set #date = substring(#date, #point_ix, len(#date) - #point_ix)
set #point_ix = charindex('.', #date)
set #year_part = substring(#date, 0, #point_ix)
set #datetime = #month_part + #day_part + #year_part + ' ' + #time
return #datetime
END
When I run it.. I get:
Msg 241, Level 16, State 1, Line 1
Conversion failed when converting date and/or time from character string.
Arghh!!
So, what do I do?
ALTER FUNCTION [dbo].[ufn_GetDateTime] (#date nvarchar(11), #time nvarchar(11))
RETURNS nvarchar(22)
AS
BEGIN
--select dbo.ufn_GetDateTime('25.03.2012.', '9:18:25')
declare #day_part nvarchar(3)
declare #point_ix int
set #point_ix = charindex('.', #date)
set #day_part = substring(#date, 0, #point_ix)
return #day_part
END
And I get '25'. So, I am off by one and so I change to..
set #day_part = substring(#date, 0, #point_ix + 1)
Voila! Now it works :)