Getting output of one Stored Procedure called in another using TSQL - tsql

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.

Related

Just Exec SP works but Insert into table Exec SP goes in infinite loop

Question 1)
We are planning to turn off XP CMDSHELL in SQL. Is there any alternative to the method I am using below:
Question 2)
Below SP
1.Creates new SQL Job every time
2.Runs the CMD command passed to it from SP param
3.Finally SP return result
USE [Test]
GO
/****** Object: StoredProcedure [dbo].[CMDSHELL_ALTERNATIVE] Script Date: 5/8/2020 5:44:39 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[CMDSHELL_ALTERNATIVE]
#Command varchar(200),
#InvokedBySP varchar(100) = 'AdhocCalling'
AS
BEGIN
DECLARE #OutputFileName varchar(max)
DECLARE #OutputFilePath varchar(max)
DECLARE #JobOutcome int = 5
DECLARE #JobMessage varchar(max)
DECLARE #TodayDate DATE = GETDATE()
DECLARE #JobExecutionTime DATETIME
DECLARE #RandomeNumber BIGINT = CAST(RAND() * 1000000 AS BIGINT)
DECLARE #JobName varchar(100) = 'CMDSHELL_JOB_'+ CAST(#TodayDate AS VARCHAR)+'_'+CAST(#RandomeNumber AS VARCHAR)
SET #OutputFilePath ='C:\Users\OutputFiles\'
SET #OutputFileName = #JobName+'.txt'
DECLARE #FullOutputFilePathName varchar(max) = #OutputFilePath+#OutputFileName
DECLARE #jobId BINARY(16)
EXEC msdb.dbo.sp_add_job #job_name=#JobName,
#enabled=1,
#notify_level_eventlog=0,
#notify_level_email=2,
#notify_level_page=2,
#delete_level=0,
#category_name=N'[Uncategorized (Local)]',
#owner_login_name=N'sa', #job_id = #jobId OUTPUT
EXEC msdb.dbo.sp_add_jobserver #job_name=#JobName, #server_name = N'(local)'
EXEC msdb.dbo.sp_add_jobstep #job_name=#JobName, #step_name=N'step-1',
#step_id=1,
#cmdexec_success_code=0,
#on_success_action=1,
#on_fail_action=2,
#retry_attempts=0,
#retry_interval=0,
#os_run_priority=0, #subsystem=N'CmdExec',
#command=N'',
#database_name=N'master',
#output_file_name=N'',
#flags=0
EXEC msdb.dbo.sp_update_job #job_name=#JobName,
#enabled=1,
#start_step_id=1,
#notify_level_eventlog=0,
#notify_level_email=2,
#notify_level_page=2,
#delete_level=0,
#description=N'',
#category_name=N'[Uncategorized (Local)]',
#owner_login_name=N'sa',
#notify_email_operator_name=N'',
#notify_page_operator_name=N''
EXEC msdb.dbo.sp_update_jobstep
#job_name=#JobName,
#step_id=1 ,
#command=#Command,
#output_file_name=#FullOutputFilePathName
EXEC msdb.dbo.sp_start_job #JobName
SELECT distinct #JobOutcome = SJH.run_status
FROM msdb..sysjobhistory SJH, msdb..sysjobs SJ
WHERE SJH.job_id = SJ.job_id and SJ.Name = #JobName
WHILE (#JobOutcome != 3 AND #JobOutcome > 1)
BEGIN
print 'In a delay loop'
WAITFOR DELAY '00:00:02';
SELECT distinct #JobOutcome = SJH.run_status
FROM msdb..sysjobhistory SJH, msdb..sysjobs SJ
WHERE SJH.job_id = SJ.job_id and SJ.Name = #JobName
END
SET #JobExecutionTime = GETDATE()
SELECT #JobMessage = message FROM msdb..sysjobhistory SJH, msdb..sysjobs SJ
WHERE SJH.job_id = SJ.job_id and SJ.Name = #JobName
AND step_id = 1
IF #JobOutcome = 1
BEGIN
print 'Job successfull'
Declare #BulkInsertCommand varchar(max)
CREATE TABLE #temp (results NVARCHAR(755))
SET #BulkInsertCommand =
'BULK INSERT #temp
FROM ''' + #FullOutputFilePathName +'''
WITH
(
ROWTERMINATOR = ''\n'',
DataFileType=''widechar''
)'
EXEC (#BulkInsertCommand)
Select results from #temp
execute Test.dbo.CLR_CMDSHELL_LOGGING #JobName,#InvokedBySP,#Command,#JobExecutionTime,'Job Successfull',#JobMessage
END
ELSE
BEGIN
print 'Dynamic Job failed'
execute Test.dbo.CLR_CMDSHELL_LOGGING #JobName,#InvokedBySP,#Command,#JobExecutionTime,'Job Failed',#JobMessage
END
IF EXISTS (SELECT job_id FROM msdb.dbo.sysjobs_view WHERE name = #JobName)
EXEC msdb.dbo.sp_delete_job #job_name=#JobName
END
Now I run above SP using below code -
DECLARE #cmd VARCHAR(100)
SET #cmd = 'DIR /B C:\Users\Test'
--DECLARE #dirTable TABLE (oPut VARCHAR(max))
--insert #dirTable
EXEC [dbo].CMDSHELL_ALTERNATIVE #cmd
--select * from #dirTable
Result without Insert
enter image description here
Problem
Code above without insert works fine, returns result quickly. But when Insert statement is uncommented, SP runs forever.
enter image description hereenter code here
I can't figure out exactly where the blocking is. Any alternative/fix in above?

Method sp_OAGetProperty does not return value when executed

I have a certain address for a client and I need to get the latitude and longitude coordinates from Google using API Key, however either sp_OAGetProperty is returning blank instead of an XML data, what could be the reason for blank results it is returning?
This is for a geo analytics purposes, I have reconfigured the server to accept the execution of Ole Automation Procedures but still the same.
I'm using code.
use Development_Weekly
go
DECLARE #Response varchar(8000)
DECLARE #XML xml
DECLARE #Obj int
DECLARE #Result int
DECLARE #HTTPStatus int
DECLARE #Erreur varchar(MAX)
declare #MessageErreur varchar(max)
declare #MessageErreurAvecNumero varchar(max)
declare #URL varchar(600) =(select top 1 [BranchAddressURL] from [Development_Weekly].[dbo].[vwBranchLocation])
EXEC #Result = sp_OACreate 'MSXML2.ServerXMLHttp', #Obj OUT
if #Erreur <> 0 begin set #MessageErreur = 'sp_OACreate MSXML2.ServerXMLHttp.3.0 failed' end
--BEGIN TRY
EXEC #Result = sp_OAMethod #Obj, 'open', NULL, 'GET', #URL, false
if #Erreur <> 0 begin set #MessageErreur = 'sp_OAMethod MSXML2.ServerXMLHttp.3.0 failed' end
EXEC #Result = sp_OAMethod #Obj, 'setRequestHeader', NULL, 'Content-Type', 'application/x-www-form-urlencoded'
if #Erreur <> 0 begin set #MessageErreur = 'sp_OAMethod MSXML2.ServerXMLHttp.3.0 failed' end
EXEC #Result = sp_OAMethod #Obj, send, NULL, ''
if #Erreur <> 0 begin set #MessageErreur = 'sp_OAMethod MSXML2.ServerXMLHttp.3.0 failed' end
EXEC #Result = sp_OAGetProperty #Obj, 'responseXML.xml', #Response OUT
if #Erreur <> 0 begin set #MessageErreur = 'sp_OAGetProperty MSXML2.ServerXMLHttp.3.0 failed' end
EXEC #Result = sp_OADestroy #Obj
if #Erreur <> 0 begin set #MessageErreur = 'sp_OADestroy MSXML2.ServerXMLHttp.3.0 failed' end
select #Result,#Response,#URL
I am not sure what exactly the reason is without a real url. But I always insert the response to a table variable instead of using #Response OUT to avoid truncated result. Here is what I usually do:
DECLARE #TABLEVAR TABLE (responseXml VARCHAR(MAX))
--skip some codes here
INSERT INTO #TABLEVAR
EXEC #Result = sp_OAGetProperty #Obj, 'responseXML.xml' --, #Response OUT
EXEC sp_OADestroy #Obj
SELECT #Response = responseXml FROM #TABLEVAR

execute TSQL command with an output variable

I want to pass an SQL command to a variable and execute aggregate function for that. but when I run it,
this error is shown, and nothing appear for #cnt value ,
Must declare the variable '#cnt'
What's my mistake ?
DECLARE #ret varchar(300);
set #ret = '';
declare #cnt int;
set #ret = 'select #cnt = count(*) from TBL1'
EXEC (#ret)
print #cnt
You could use sp_executesql to access a variable inside of your dynamic SQL String:
DECLARE #SQLString nvarchar(500);
DECLARE #ParmDefinition nvarchar(500);
DECLARE #cnt varchar(30);
SET #SQLString = N'SELECT #cntOUT = count(1) from tbl1';
SET #ParmDefinition = N'#cntOUT varchar(30) OUTPUT';
EXECUTE sp_executesql #SQLString, #ParmDefinition, #cntOUT=#cnt OUTPUT;
SELECT #cnt;
The exec statement signals the end of a batch, so the print statement doesn't know about #cnt.
Try this instead:
DECLARE #ret varchar(300)
set #ret = ''
set #ret = 'declare #cnt int
select #cnt = count(*) from [Load].RINData
print #cnt'
EXEC (#ret)

What is the effect of having XACT_ABORT on/off in parent/child stored procedures respectively?

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

Cannot use function calls as parameters to RAISERROR()

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