Commit transaction outside the current transaction (like autonomous transaction in Oracle) - sql

I need to write into a log table from a stored procedure.
Now this log info has to survive a rollback offcourse.
I know this question has been asked before, but my situation is different and I cannot find an answer to my problem in these questions.
When there is no error in the stored procedure things are simple, the entry in the logtable will just be there.
When there is an error than things are complicated.
Inside the procedure I can do rollback in the catch and then insert the data into the log table, I know that and I am already doing that.
But the problem is when the stored procedure is called like this :
begin transaction
exec myStoredProcedure
rollback transaction
select * from myLogTable
I know this code makes not much sense, I kept it mimimal to demonstrate my problem.
If the caller of the stored procedure does the commit/rollback then it does not matters what I do in the stored procedure. My logentry will always be rolled back.
I also cannot use the temporary table trick, which is to return the data I want to log and let the caller use that data to insert it into the logtable after it has done the rollback, because the caller is an external application that I do not have the source from.
The logging is done in a seperate procedure that only has one line of code, the insert into the logtable.
What I need is a way to commit the insert in this procedure, outside the current transaction so it survives any rollback.
Is there a way to do this ?
The Solution:
I used lad2025 answer and thus far it is working without problems or performance issues.
But this procedure will only be called about 1000 times each day which is not that much so I guess I don't have to expect any problems either.

It is quite interesting topic so let's check how MS approaches it.
First documentation: Migrating-Oracle-to-SQL-Server-2014-and-Azure-SQL-DB.pdf
Page 152.
Simulating Oracle Autonomous Transactions
This section describes how SSMA for Oracle V6.0 handles autonomous transactions
(PRAGMA AUTONOMOUS_TRANSACTION). These autonomous transactions do not
have direct equivalents in Microsoft SQL Server 2014.
When you define a PL/SQL block (anonymous block, procedure, function, packaged
procedure, packaged function, database trigger) as an autonomous transaction, you
isolate the DML in that block from the caller's transaction context. The block becomes
an independent transaction started by another transaction, referred to as the main
transaction.
To mark a PL/SQL block as an autonomous transaction, you simply include the
following statement in your declaration section:
PRAGMA AUTONOMOUS_TRANSACTION;
SQL Server 2014 does not support autonomous transactions. The only way to isolate a
Transact-SQL block from a transaction context is to open a new connection.
Use the xp_ora2ms_exec2 extended procedure and its extended version
xp_ora2ms_exec2_ex, bundled with the SSMA 6.0 Extension Pack, to open new
transactions. The procedure's purpose is to invoke any stored procedure in a new
connection and help invoke a stored procedure within a function body. The
xp_ora2ms_exec2 procedure has the following syntax:
xp_ora2ms_exec2
<active_spid> int,
<login_time> datetime,
<ms_db_name> varchar,
<ms_schema_name> varchar,
<ms_procedure_name> varchar,
<bind_to_transaction_flag> varchar,
[optional_parameters_for_procedure];
Then you need to install on your server stored procedures and other scripts:
SSMA for Oracle Extension Pack (only SSMA for Oracle Extension Pack.7.5.0.msi).
Your stored procedure will become:
CREATE TABLE myLogTable(i INT IDENTITY(1,1),
d DATETIME DEFAULT GETDATE(),
t NVARCHAR(1000));
GO
CREATE OR ALTER PROCEDURE my_logging
#t NVARCHAR(MAX)
AS
BEGIN
INSERT INTO myLogTable(t) VALUES (#t);
END;
GO
CREATE OR ALTER PROCEDURE myStoredProcedure
AS
BEGIN
-- some work
SELECT 1;
INSERT INTO myLogTable(t)
VALUES ('Standard logging that will perish after rollback');
DECLARE #login_time DATETIME = GETDATE();
DECLARE #custom_text_to_log NVARCHAR(100);
SET #custom_text_to_log=N'some custom loging that should survive rollback';
DECLARE #database_name SYSNAME = DB_NAME();
EXEC master.dbo.xp_ora2ms_exec2_ex
##spid,
#login_time,
#database_name,
'dbo',
'my_logging',
'N',
#custom_text_to_log;
END;
And final call:
begin transaction
exec myStoredProcedure
rollback transaction
select * from myLogTable;
OUTPUT:
i d t
2 2017-08-21 some custom loging that should survive rollback

So you really search for some sort of Autonomous transaction (like in Oracle).
One ugly way to simulate it is to use loopback linked server.
Warning: This is PoC (I would think twice before I would use it in PROD) and do a lot of testing.
DECLARE #servername SYSNAME;
SET #servername = CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'));
EXECUTE sys.sp_addlinkedserver
#server = N'loopback',
#srvproduct = N'',
#provider = N'SQLNCLI',
#datasrc = #servername;
EXECUTE sys.sp_serveroption
#server = N'loopback',
#optname = 'RPC OUT',
#optvalue = 'ON';
EXECUTE sys.sp_serveroption
#server = N'loopback',
#optname = 'remote proc transaction promotion',
#optvalue = 'OFF';
And code:
DROP TABLE IF EXISTS myLogTable;
CREATE TABLE myLogTable(i INT IDENTITY(1,1),
d DATETIME DEFAULT GETDATE(),
t NVARCHAR(1000));
GO
CREATE OR ALTER PROCEDURE my_logging
#t NVARCHAR(MAX)
AS
BEGIN
INSERT INTO myLogTable(t) VALUES (#t);
END;
GO
CREATE OR ALTER PROCEDURE myStoredProcedure
AS
BEGIN
-- some work
SELECT 1;
INSERT INTO myLogTable(t)
VALUES ('Standard logging that will perish after rollback');
EXEC loopback.T1.dbo.my_logging
#t = N'some custom loging that should survive rollback';
END;
Final call:
begin transaction
exec myStoredProcedure
rollback transaction
select * from myLogTable
Output:
i d t
2 2017-08-17 some custom loging that should survive rollback

Related

Properly understanding the error Cannot use the ROLLBACK statement within an INSERT-EXEC statement - Msg 50000, Level 16, State 1

I understand there is a regularly quoted answer that is meant to address this question, but I believe there is not enough explanation on that thread to really answer the question.
Why earlier answers are inadequate
The first (and accepted) answer simply says this is a common problem and talks about having only one active insert-exec at a time (which is only the first half of the question asked there and doesn't address the ROLLBACK error). The given workaround is to use a table-valued function - which does not help my scenario where my stored procedure needs to update data before returning a result set.
The second answer talks about using openrowset but notes you cannot dynamically specify argument values for the stored procedure - which does not help my scenario because different users need to call my procedure with different parameters.
The third answer provides something called "the old single hash table approach" but does not explain whether it is addressing part 1 or 2 of the question, nor how it works, nor why.
No answer explains why the database is giving this error in the first place.
My use case / requirements
To give specifics for my scenario (although simplified and generic), I have procedures something like below.
In a nutshell though - the first procedure will return a result set, but before it does so, it updates a status column. Effectively these records represent records that need to be synchronised somewhere, so when you call this procedure the procedure will flag the records as being "in progress" for sync.
The second stored procedure calls that first one. Of course the second stored procedure wants to take those records and perform inserts and updates on some tables - to keep those tables in sync with whatever data was returned from the first procedure. After performing all the updates, the second procedure then calls a third procedure - within a cursor (ie. row by row on all the rows in the result set that was received from the first procedure) - for the purpose of setting the status on the source data to "in sync". That is, one by one it goes back and says "update the sync status on record id 1, to 'in sync'" ... and then record 2, and then record 3, etc.
The issue I'm having is that calling the second procedure results in the error
Msg 50000, Level 16, State 1, Procedure getValuesOuterCall, Line 484 [Batch Start Line 24]
Cannot use the ROLLBACK statement within an INSERT-EXEC statement.
but calling the first procedure directly causes no error.
Procedure 1
-- Purpose here is to return a result set,
-- but for every record in the set we want to set a status flag
-- to another value as well.
alter procedure getValues #username, #password, #target
as
begin
set xact_abort on;
begin try
begin transaction;
declare #tableVariable table (
...
);
update someOtherTable
set something = somethingElse
output
someColumns
into #tableVariable
from someTable
join someOtherTable
join etc
where someCol = #username
and etc
;
select
someCols
from #tableVariable
;
commit;
end try
begin catch
if ##trancount > 0 rollback;
declare #msg nvarchar(2048) = error_message() + ' Error line: ' + CAST(ERROR_LINE() AS nvarchar(100));
raiserror (#msg, 16, 1);
return 55555
end catch
end
Procedure 2
-- Purpose here is to obtain the result set from earlier procedure
-- and then do a bunch of data updates based on the result set.
-- Lastly, for each row in the set, call another procedure which will
-- update that status flag to another value.
alter procedure getValuesOuterCall #username, #password, #target
as
begin
set xact_abort on;
begin try
begin transaction;
declare #anotherTableVariable
insert into #anotherTableVariable
exec getValues #username = 'blah', #password = #somePass, #target = ''
;
with CTE as (
select someCols
from #anotherTableVariable
join someOtherTables, etc;
)
merge anUnrelatedTable as target
using CTE as source
on target.someCol = source.someCol
when matched then update
target.yetAnotherCol = source.yetAnotherCol,
etc
when not matched then
insert (someCols, andMoreCols, etc)
values ((select someSubquery), source.aColumn, source.etc)
;
declare #myLocalVariable int;
declare #mySecondLocalVariable int;
declare lcur_myCursor cursor for
select keyColumn
from #anotherTableVariable
;
open lcur_muCursor;
fetch lcur_myCursor into #myLocalVariable;
while ##fetch_status = 0
begin
select #mySecondLocalVariable = someCol
from someTable
where someOtherCol = #myLocalVariable;
exec thirdStoredProcForSettingStatusValues #id = #mySecondLocalVariable, etc
end
deallocate lcur_myCursor;
commit;
end try
begin catch
if ##trancount > 0 rollback;
declare #msg nvarchar(2048) = error_message() + ' Error line: ' + CAST(ERROR_LINE() AS nvarchar(100));
raiserror (#msg, 16, 1);
return 55555
end catch
end
The parts I don't understand
Firstly, I have no explicit 'rollback' (well, except in the catch block) - so I have to presume that an implicit rollback is causing the issue - but it is difficult to understand where the root of this problem is; I am not even entirely sure which stored procedure is causing the issue.
Secondly, I believe the statements to set xact_abort and begin transaction are required - because in procedure 1 I am updating data before returning the result set. In procedure 2 I am updating data before I call a third procedure to update further data.
Thirdly, I don't think procedure 1 can be converted to a table-valued function because the procedure performs a data update (which would not be allowed in a function?)
Things I have tried
I removed the table variable from procedure 2 and actually created a permanent table to store the results coming back from procedure 1. Before calling procedure 1, procedure 2 would truncate the table. I still got the rollback error.
I replaced the table variable in procedure 1 with a temporary table (ie. single #). I read the articles about how such a table persists for the lifetime of the connection, so within procedure 1 I had drop table if exists... and then create table #.... I still got the rollback error.
Lastly
I still don't understand exactly what is the problem - what is Microsoft struggling to accomplish here? Or what is the scenario that SQL Server cannot accommodate for a requirement that appears to be fairly straightforward: One procedure returns a result set. The calling procedure wants to perform actions based on what's in that result set. If the result set is scoped to the first procedure, then why can't SQL Server just create a temporary copy of the result set within the scope of the second procedure so that it can be acted upon?
Or have I missed it completely and the issue has something to do with the final call to a third procedure, or maybe to do with using try ... catch - for example, perhaps the logic is totally fine but for some reason it is hitting the catch block and the rollback there is the problem (ie. so if I fix the underlying reason leading us to the catch block, all will resolve)?

How do I limit a SQL stored procedure to be run by one person

I have a stored procedure that basically inserts from one table to another.
While this procedure is running, I don't want anyone else to be able to start it, however I don't want serialization, the other person to run the procedure after I am done with it.
What I want is for the other person trying to start it to get an error, while I am running the procedure.
I've tried with using sp_getapplock, however I can't manage to completely stop the person from running the procedure.
I also tried finding the procedure with sys.dm_exec_requests and blocking the procedure, while this does work, i think it's not optimal because on some servers I don't have the permissions to run sys.dm_exec_sql_text(sql_handle).
What is the best way for me to do this?
Cunning stunts:
ALTER PROC MyProc
AS
BEGIN TRY
IF OBJECT_ID('tembdb..##lock_proc_MyProc') IS NOT NULL
RAISERROR('Not now.', 16, 0)
ELSE
EXEC('CREATE TABLE ##lock_proc_MyProc (dummy int)')
...
EXEC('DROP TABLE ##lock_proc_MyProc')
END TRY
BEGIN CATCH
...
EXEC('DROP TABLE ##lock_proc_MyProc')
...
END CATCH
GO
Which can be extended by storing spid and watching for zombie ##.
Also you can just raise isolation level/granularity like TABLOCK, UPDLOCK:
ALTER PROC MyProc
AS
BEGIN TRAN
DECLARE #dummy INT
SELECT #dummy=1
FROM t (TABLOCKX, HOLDLOCK)
WHERE 1=0
...
COMMIT TRAN
this will have different effect - will wait, not fall.

tSQLt fails when attempting to FakeTable a synonym table

I am using tSQLt (through Red Gate's SQL Test version 1.0.0.455). tSQLt is installed on database A. I am trying to do a tSQLt.FakeTable on a table in database B on the same SQL server instance through a synonym on database A.
Code:
ALTER PROCEDURE [ErrorType109NonTankHasSizeOrVolume].[test AliasTest]
AS
BEGIN
Exec tSQLt.FakeTable 'dbo.Bygning';
Insert Into dbo.Bygning (ObjStatus) Values (1);
EXEC tSQLt.AssertEquals 1, 1
END;
Where dbo.Bygning is a synonym in database A referring to a table in database B and ObjStatus is a column in dbo.Bygning
Error message:
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
[ErrorType109NonTankHasSizeOrVolume].[test AliasTest] failed: An invalid parameter or option was specified for procedure 'sp_addextendedproperty'.{sp_addextendedproperty,37}
Is there any way to tSQLt.FakeTable synonym tables?
Clarification:
The error message comes when running the test.
tSQLt does not support faking on synonyms at the moment. However, I think it may be easy to add support for this. I quickly prototyped the following fix and hope it may solve your issue. Can you please try it and confirm? If it works out for you, I'll make sure it gets into the next release.
ALTER PROCEDURE tSQLt.Private_MarkFakeTable
#SchemaName NVARCHAR(MAX),
#TableName NVARCHAR(MAX),
#NewNameOfOriginalTable NVARCHAR(4000)
AS
BEGIN
DECLARE #UnquotedSchemaName NVARCHAR(MAX);SET #UnquotedSchemaName = OBJECT_SCHEMA_NAME(OBJECT_ID(#SchemaName+'.'+#TableName));
DECLARE #UnquotedTableName NVARCHAR(MAX);SET #UnquotedTableName = OBJECT_NAME(OBJECT_ID(#SchemaName+'.'+#TableName));
DECLARE #Level1Type NVARCHAR(MAX);
SELECT #Level1Type =
CASE type
WHEN 'SN' THEN 'SYNONYM'
ELSE 'TABLE'
END
FROM sys.objects
WHERE object_id = OBJECT_ID(#SchemaName+'.'+#TableName);
EXEC sys.sp_addextendedproperty
#name = N'tSQLt.FakeTable_OrgTableName',
#value = #NewNameOfOriginalTable,
#level0type = N'SCHEMA', #level0name = #UnquotedSchemaName,
#level1type = N'TABLE', #level1name = #UnquotedTableName;
END;
GO
I ran into this today and devised the following solution.
In the Assemble section:
CREATE TABLE #mock
(
id_item VARCHAR(15),
descr_1 VARCHAR(50)
)
INSERT INTO #mock
( id_item, descr_1 )
VALUES ('123456-01', 'Great description here'),
('123456-02', 'Blue, gnarly, cloud')
EXEC sp_rename 'syn_name', 'syn_name_orig'
CREATE SYNONYM syn_name FOR #mock
Then clean up at the end of the act section:
DROP SYNONYM syn_name
EXEC sp_rename 'syn_name_orig', 'syn_name'
It's working for me. Might have side-effects for concurrent processes trying to use that synonym, but I only run my tests in a development environment, so I am not worried about it.
The workaround I've used is to change the production code to use a view, which then uses the synonym. I can then mock the view using tSQLt.FakeTable.
I have also faced similar issue.
I have 2 databases i.e. config and main db
I have a table 'tableA' in config db. and its synonym is created in main db with same name i.e. dbo.tableA. But while mocking that synonym it gave me same error. So here is my solution which worked for me.
First, I have created synonym of a tSQLt.FakeTable sp from config db into my main db
for e.g.
CREATE SYNONYM tSQLt.tSqltFakeTable_config FOR [Config_db].tSQLt.faketable
Now in test case, I used new synonym tSQLt.tSqltFakeTable_config as below to fake a synonym of 'tableA'
CREATE PROC [testclass].[test faking synonym which points to another db]
AS
BEGIN
--arrange
exec [tSQLt].tSqltFakeTable_config 'dbo.tableA';
--act
--assert
END
But I felt there would be only 1 issue that if you uninstall tSQLt from config db. You have to keep track of all such new synonyms and remove them first then you can uninstall tSQLt from Config db.
Suggestions are welcome.

How to Suppress the SELECT Output of a Stored Procedure called from another Stored Procedure in SQL Server?

I'm not talking about doing a "SET NOCOUNT OFF". But I have a stored procedure which I use to insert some data into some tables. This procedure creates a xml response string, well let me give you an example:
CREATE PROCEDURE [dbo].[insertSomeData] (#myParam int) AS
DECLARE #reply varchar(2048)
... Do a bunch of inserts/updates...
SET #reply = '<xml><big /><outputs /></xml>'
SELECT #reply
GO
So I put together a script which uses this SP a bunch of times, and the xml "output" is getting to be too much (it's crashed my box once already).
Is there a way to suppress or redirect the output generated from this stored procedure? I don't think that modifying this stored procedure is an option.
thanks.
I guess i should clarify. This SP above is being called by a T-SQL Update script that i wrote, to be run through enterprise studio manager, etc.
And it's not the most elegant SQL i've ever written either (some psuedo-sql):
WHILE unprocessedRecordsLeft
BEGIN
SELECT top 1 record from updateTable where Processed = 0
EXEC insertSomeData #param = record_From_UpdateTable
END
So lets say the UpdateTable has some 50k records in it. That SP gets called 50k times, writing 50k xml strings to the output window. It didn't bring the sql server to a stop, just my client app (sql server management studio).
The answer you're looking for is found in a similar SO question by Josh Burke:
-- Assume this table matches the output of your procedure
DECLARE #tmpNewValue TABLE ([Id] int, [Name] varchar(50))
INSERT INTO #tmpNewValue
EXEC [ProcedureB]
-- SELECT [Id], [Name] FROM #tmpNewValue
I think I found a solution.
So what i can do now in my SQL script is something like this (sql-psuedo code):
create table #tmp(xmlReply varchar(2048))
while not_done
begin
select top 1 record from updateTable where processed = 0
insert into #tmp exec insertSomeData #param=record
end
drop table #tmp
Now if there was a even more efficient way to do this. Does SQL Server have something similar to /dev/null? A null table or something?
Answering the question, "How do I suppress stored procedure output?" really depends on what you are trying to accomplish. So I want to contribute what I encountered:
I needed to supress the stored procedure (USP) output because I just wanted the row count (##ROWCOUNT) from the output. What I did, and this may not work for everyone, is since my query was already going to be dynamic sql I added a parameter called #silentExecution to the USP in question. This is a bit parameter which I defaulted to zero (0).
Next if #silentExecution was set to one (1) I would insert the table contents into a temporary table, which is what would supress the output and then execute ##ROWCOUNT with no problem.
USP Example:
CREATE PROCEDURE usp_SilentExecutionProc
#silentExecution bit = 0
AS
BEGIN
SET NOCOUNT ON;
DECLARE #strSQL VARCHAR(MAX);
SET #strSQL = '';
SET #strSQL = 'SELECT TOP 10 * ';
IF #silentExecution = 1
SET #strSQL = #strSQL + 'INTO #tmpDevNull ';
SET #strSQL = #strSQL +
'FROM dbo.SomeTable ';
EXEC(#strSQL);
END
GO
Then you can execute the whole thing like so:
EXEC dbo.usp_SilentExecutionProc #silentExecution = 1;
SELECT ##ROWCOUNT;
The purpose behind doing it like this is if you need the USP to be able to return a result set in other uses or cases, but still utilize it for just the rows.
Just wanted to share my solution.
I have recently come across with a similar issue while writing a migration script and since the issue was resolved in a different way, I want to record it.
I have nearly killed my SSMS Client by running a simple while loop for 3000 times and calling a procedure.
DECLARE #counter INT
SET #counter = 10
WHILE #counter > 0
BEGIN
-- call a procedure which returns some resultset
SELECT #counter-- (simulating the effect of stored proc returning some resultset)
SET #counter = #counter - 1
END
The script result was executed using SSMS and default option on query window is set to show “Results to Grid”[Ctrl+d shortcut].
Easy Solution:
Try setting the results to file to avoid the grid to be built and painted on the SSMS client. [CTRL+SHIFT+F keyboard shortcut to set the query results to file].
This issue is related to : stackoverflow query
Man, this is seriously a case of a computer doing what you told it to do instead of what you wanted it to do.
If you don't want it to return results, then don't ask it to return results. Refactor that stored procedure into two:
CREATE PROCEDURE [dbo].[insertSomeData] (#myParam int) AS
BEGIN
DECLARE #reply varchar(2048)
--... Do a bunch of inserts/updates...
EXEC SelectOutput
END
GO
CREATE PROCEDURE SelectOutput AS
BEGIN
SET #reply = '<xml><big /><outputs /></xml>'
SELECT #reply
END
From which client are you calling the stored procedure? Say it was from C#, and you're calling it like:
var com = myConnection.CreateCommand();
com.CommandText = "exec insertSomeData 1";
var read = com.ExecuteReader();
This will not yet retrieve the result from the server; you have to call Read() for that:
read.Read();
var myBigString = read[0].ToString();
So if you don't call Read, the XML won't leave the Sql Server. You can even call the procedure with ExecuteNonQuery:
var com = myConnection.CreateCommand();
com.CommandText = "exec insertSomeData 1";
com.ExecuteNonQuery();
Here the client won't even ask for the result of the select.
You could create a SQL CLR stored procedure that execs this. Should be pretty easy.
I don't know if SQL Server has an option to suppress output (I don't think it does), but the SQL Query Analyzer has an option (under results tab) to "Discard Results".
Are you running this through isql?
You said your server is crashing. What is crashing the application that consumes the output of this SQL or SQL Server itself (assuming SQL Server).
If you are using .Net Framework application to call the stored procedure then take a look at SQLCommand.ExecuteNonQuery. This just executes stored procedure with no results returned. If problem is at SQL Server level then you are going to have to do something different (i.e. change the stored procedure).
You can include in the SP a parameter to indicate if you want it to do the select or not, but of course, you need to have access and reprogram the SP.
CREATE PROCEDURE [dbo].[insertSomeData] (#myParam int, #doSelect bit=1) AS
DECLARE #reply varchar(2048)
... Do a bunch of inserts/updates...
SET #reply = '<xml><big /><outputs /></xml>'
if #doSelect = 1
SELECT #reply
GO
ever tried SET NOCOUNT ON; as an option?

Find out the calling stored procedure in SQL Server

Is it possible to find out who called a stored procedure?
For example, say I get an error in proc3. From within that proc I want to know if it was called by proc1 or proc2.
I would use an extra input parameter, to specify the source, if this is important for your logic.
This will also make it easier to port your database to another platform, since you don't depend on some obscure platform dependent function.
There is no nice automatic way to do this (alas). So it really depends on how much you are prepared to (re)write your procs in order to be able to do this.
If you have a logging mechanism, you might be able to read the log and work out who called you.
For example, if you implement logging by inserting to a table, for example:
CREATE TABLE Log
(timestamp dattime,
spid int,
procname varchar(255),
message varchar(255) )
... text of proc ...
INSERT INTO Log
SELECT get_date(), ##spid, #currentproc, 'doing something'
-- you have to define #currentproc in each proc
-- get name of caller
SELECT #caller = procname
FROM Log
WHERE spid = ##spid
AND timestamp = (SELECT max(timestamp)
FROM Log
WHERE timestamp < get_date()
AND procname != #currentproc )
This wouldn't work for recursive calls, but perhaps someone can fix that?
Do you need to know in proc3 at runtime which caused the error, or do you just need to know while debugging?
You can use SQL Server profiler if you only need to do it during debugging/monitoring.
Otherwise in 2005 I don't believe you have the ability to stack trace.
To work around it you could add and extra parameter to proc3, #CallingProc or something like that.
OR you could add try catch blocks to proc1 and proc2.
BEGIN TRY
EXEC Proc3
END TRY
BEGIN CATCH
SELECT 'Error Caught'
SELECT
ERROR_PROCEDURE()
END CATCH
Good reference here : http://searchsqlserver.techtarget.com/tip/1,289483,sid87_gci1189087,00.html
and of course always SQL Server Books Online
SQL Server 2008 does have the ability to debug through procedures however.
You could have proc1 and proc2 pass their names into proc3 as a parameter.
For example:
CREATE PROCEDURE proc3
#Caller nvarchar(128) -- Name of calling proc.
AS
BEGIN
-- Produce error message that includes caller's name.
RAISERROR ('Caller was %s.', 16,10, #Caller);
END
GO
CREATE PROCEDURE proc1
AS
BEGIN
-- Get the name of this proc.
DECLARE #ProcName nvarchar(128);
SET #ProcName = OBJECT_NAME(##PROCID);
-- Pass it to proc3.
EXEC proc3 #ProcName
END
GO
CREATE PROCEDURE proc2
AS
BEGIN
-- Get the name of this proc.
DECLARE #ProcName nvarchar(128);
SET #ProcName = OBJECT_NAME(##PROCID);
-- Pass it to proc3.
EXEC proc3 #ProcName
END
GO