SQL Server Transactions how can I commit my transaction - sql

I have SQL Server 2005 stored procedure. Someone one is calling my stored procedure within a transaction. In my stored proc I'm logging some information (insert into a table). When the higher level transaction rolls back it removes my insert.
Is there anyway I can commit my insert and prevent the higher level rollback from removing my insert?
Thanks

Even if you start a new transaction, it will be nested within the outer transaction. SQL Server guarantees that a rollback will result in an unmodified database state. So there is no way you can insert a row inside an aborted transaction.
Here's a way around it, it's a bit of a trick. Create a linked server with rpc out = true and remote proc transaction promotion = false. The linked server can point to the same server as your procedure is running on. Then, you can use execte (<query>) at <server> to execute something in a new transaction.
if OBJECT_ID('logs') is not null drop table logs
create table logs (id int primary key identity, msg varchar(max))
if OBJECT_ID('TestSp') is not null drop procedure TestSp
go
create procedure TestSp as
execute ('insert into dbo.logs (msg) values (''test message'')') at LINKEDSERVER
go
begin transaction
exec TestSp
rollback transaction
select top 10 * from logs
This will end with a row in the log table, even though the transaction was rolled back.
Here's example code to create such a linked server:
IF EXISTS (SELECT srv.name FROM sys.servers srv WHERE srv.server_id != 0 AND
srv.name = N'LINKEDSERVER')
EXEC master.dbo.sp_dropserver #server=N'LINKEDSERVER',
#droplogins='droplogins'
EXEC master.dbo.sp_addlinkedserver #server = N'LINKEDSERVER',
#srvproduct=N'LOCALHOST', #provider=N'SQLNCLI', #datasrc=N'LOCALHOST',
#catalog=N'DatabaseName'
EXEC master.dbo.sp_serveroption #server=N'LINKEDSERVER', #optname=N'rpc out',
#optvalue=N'true'
EXEC master.dbo.sp_addlinkedsrvlogin #rmtsrvname=N'LINKEDSERVER',
#useself=N'True', #locallogin=NULL,#rmtuser=NULL, #rmtpassword=NULL
EXEC master.dbo.sp_serveroption #server=N'LINKEDSERVER',
#optname=N'remote proc transaction promotion', #optvalue=N'false'

In Oracle you would use autonomous transactions for that, however, SQL Server does not support them.
It is possible to declare a table variable and return it from your stored procedure.
The table variables survive the ROLLBACK, however, the upper level code should be modified to read the variable and store its data permanently.

Depending on permissions, you could call out using xp_cmdshell to OSQL thereby creating an entirely separate connection. You might be able to do something similar with the CLR, although I've never tried it. However, I strongly advise against doing something like this.
Your best bet is to establish what the conventions are for your code and the calling code - what kind of a contract is supported between the two. You could make it a rule that your code is never called within another transaction (probably not a good idea) or you could give requirements on what the calling code is responsible for when an error occurs.

Anything inside of a transaction will be part of that transaction. If you don't want it to be part of that transaction then do not put it inside.

Related

Stored procedure with multiple 'INSERT INTO Table_Variable EXECUTE stored_procedure' statements [duplicate]

I have three stored procedures Sp1, Sp2 and Sp3.
The first one (Sp1) will execute the second one (Sp2) and save returned data into #tempTB1 and the second one will execute the third one (Sp3) and save data into #tempTB2.
If I execute the Sp2 it will work and it will return me all my data from the Sp3, but the problem is in the Sp1, when I execute it it will display this error:
INSERT EXEC statement cannot be nested
I tried to change the place of execute Sp2 and it display me another error:
Cannot use the ROLLBACK statement
within an INSERT-EXEC statement.
This is a common issue when attempting to 'bubble' up data from a chain of stored procedures. A restriction in SQL Server is you can only have one INSERT-EXEC active at a time. I recommend looking at How to Share Data Between Stored Procedures which is a very thorough article on patterns to work around this type of problem.
For example a work around could be to turn Sp3 into a Table-valued function.
This is the only "simple" way to do this in SQL Server without some giant convoluted created function or executed sql string call, both of which are terrible solutions:
create a temp table
openrowset your stored procedure data into it
EXAMPLE:
INSERT INTO #YOUR_TEMP_TABLE
SELECT * FROM OPENROWSET ('SQLOLEDB','Server=(local);TRUSTED_CONNECTION=YES;','set fmtonly off EXEC [ServerName].dbo.[StoredProcedureName] 1,2,3')
Note: You MUST use 'set fmtonly off', AND you CANNOT add dynamic sql to this either inside the openrowset call, either for the string containing your stored procedure parameters or for the table name. Thats why you have to use a temp table rather than table variables, which would have been better, as it out performs temp table in most cases.
OK, encouraged by jimhark here is an example of the old single hash table approach: -
CREATE PROCEDURE SP3 as
BEGIN
SELECT 1, 'Data1'
UNION ALL
SELECT 2, 'Data2'
END
go
CREATE PROCEDURE SP2 as
BEGIN
if exists (select * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
INSERT INTO #tmp1
EXEC SP3
else
EXEC SP3
END
go
CREATE PROCEDURE SP1 as
BEGIN
EXEC SP2
END
GO
/*
--I want some data back from SP3
-- Just run the SP1
EXEC SP1
*/
/*
--I want some data back from SP3 into a table to do something useful
--Try run this - get an error - can't nest Execs
if exists (select * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
DROP TABLE #tmp1
CREATE TABLE #tmp1 (ID INT, Data VARCHAR(20))
INSERT INTO #tmp1
EXEC SP1
*/
/*
--I want some data back from SP3 into a table to do something useful
--However, if we run this single hash temp table it is in scope anyway so
--no need for the exec insert
if exists (select * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
DROP TABLE #tmp1
CREATE TABLE #tmp1 (ID INT, Data VARCHAR(20))
EXEC SP1
SELECT * FROM #tmp1
*/
My work around for this problem has always been to use the principle that single hash temp tables are in scope to any called procs. So, I have an option switch in the proc parameters (default set to off). If this is switched on, the called proc will insert the results into the temp table created in the calling proc. I think in the past I have taken it a step further and put some code in the called proc to check if the single hash table exists in scope, if it does then insert the code, otherwise return the result set. Seems to work well - best way of passing large data sets between procs.
This trick works for me.
You don't have this problem on remote server, because on remote server, the last insert command waits for the result of previous command to execute. It's not the case on same server.
Profit that situation for a workaround.
If you have the right permission to create a Linked Server, do it.
Create the same server as linked server.
in SSMS, log into your server
go to "Server Object
Right Click on "Linked Servers", then "New Linked Server"
on the dialog, give any name of your linked server : eg: THISSERVER
server type is "Other data source"
Provider : Microsoft OLE DB Provider for SQL server
Data source: your IP, it can be also just a dot (.), because it's localhost
Go to the tab "Security" and choose the 3rd one "Be made using the login's current security context"
You can edit the server options (3rd tab) if you want
Press OK, your linked server is created
now your Sql command in the SP1 is
insert into #myTempTable
exec THISSERVER.MY_DATABASE_NAME.MY_SCHEMA.SP2
Believe me, it works even you have dynamic insert in SP2
I found a work around is to convert one of the prods into a table valued function. I realize that is not always possible, and introduces its own limitations. However, I have been able to always find at least one of the procedures a good candidate for this. I like this solution, because it doesn't introduce any "hacks" to the solution.
I encountered this issue when trying to import the results of a Stored Proc into a temp table, and that Stored Proc inserted into a temp table as part of its own operation. The issue being that SQL Server does not allow the same process to write to two different temp tables at the same time.
The accepted OPENROWSET answer works fine, but I needed to avoid using any Dynamic SQL or an external OLE provider in my process, so I went a different route.
One easy workaround I found was to change the temporary table in my stored procedure to a table variable. It works exactly the same as it did with a temp table, but no longer conflicts with my other temp table insert.
Just to head off the comment I know that a few of you are about to write, warning me off Table Variables as performance killers... All I can say to you is that in 2020 it pays dividends not to be afraid of Table Variables. If this was 2008 and my Database was hosted on a server with 16GB RAM and running off 5400RPM HDDs, I might agree with you. But it's 2020 and I have an SSD array as my primary storage and hundreds of gigs of RAM. I could load my entire company's database to a table variable and still have plenty of RAM to spare.
Table Variables are back on the menu!
I recommend to read this entire article. Below is the most relevant section of that article that addresses your question:
Rollback and Error Handling is Difficult
In my articles on Error and Transaction Handling in SQL Server, I suggest that you should always have an error handler like
BEGIN CATCH
IF ##trancount > 0 ROLLBACK TRANSACTION
EXEC error_handler_sp
RETURN 55555
END CATCH
The idea is that even if you do not start a transaction in the procedure, you should always include a ROLLBACK, because if you were not able to fulfil your contract, the transaction is not valid.
Unfortunately, this does not work well with INSERT-EXEC. If the called procedure executes a ROLLBACK statement, this happens:
Msg 3915, Level 16, State 0, Procedure SalesByStore, Line 9 Cannot use the ROLLBACK statement within an INSERT-EXEC statement.
The execution of the stored procedure is aborted. If there is no CATCH handler anywhere, the entire batch is aborted, and the transaction is rolled back. If the INSERT-EXEC is inside TRY-CATCH, that CATCH handler will fire, but the transaction is doomed, that is, you must roll it back. The net effect is that the rollback is achieved as requested, but the original error message that triggered the rollback is lost. That may seem like a small thing, but it makes troubleshooting much more difficult, because when you see this error, all you know is that something went wrong, but you don't know what.
I had the same issue and concern over duplicate code in two or more sprocs. I ended up adding an additional attribute for "mode". This allowed common code to exist inside one sproc and the mode directed flow and result set of the sproc.
what about just store the output to the static table ? Like
-- SubProcedure: subProcedureName
---------------------------------
-- Save the value
DELETE lastValue_subProcedureName
INSERT INTO lastValue_subProcedureName (Value)
SELECT #Value
-- Return the value
SELECT #Value
-- Procedure
--------------------------------------------
-- get last value of subProcedureName
SELECT Value FROM lastValue_subProcedureName
its not ideal, but its so simple and you don't need to rewrite everything.
UPDATE:
the previous solution does not work well with parallel queries (async and multiuser accessing) therefore now Iam using temp tables
-- A local temporary table created in a stored procedure is dropped automatically when the stored procedure is finished.
-- The table can be referenced by any nested stored procedures executed by the stored procedure that created the table.
-- The table cannot be referenced by the process that called the stored procedure that created the table.
IF OBJECT_ID('tempdb..#lastValue_spGetData') IS NULL
CREATE TABLE #lastValue_spGetData (Value INT)
-- trigger stored procedure with special silent parameter
EXEC dbo.spGetData 1 --silent mode parameter
nested spGetData stored procedure content
-- Save the output if temporary table exists.
IF OBJECT_ID('tempdb..#lastValue_spGetData') IS NOT NULL
BEGIN
DELETE #lastValue_spGetData
INSERT INTO #lastValue_spGetData(Value)
SELECT Col1 FROM dbo.Table1
END
-- stored procedure return
IF #silentMode = 0
SELECT Col1 FROM dbo.Table1
Declare an output cursor variable to the inner sp :
#c CURSOR VARYING OUTPUT
Then declare a cursor c to the select you want to return.
Then open the cursor.
Then set the reference:
DECLARE c CURSOR LOCAL FAST_FORWARD READ_ONLY FOR
SELECT ...
OPEN c
SET #c = c
DO NOT close or reallocate.
Now call the inner sp from the outer one supplying a cursor parameter like:
exec sp_abc a,b,c,, #cOUT OUTPUT
Once the inner sp executes, your #cOUT is ready to fetch. Loop and then close and deallocate.
If you are able to use other associated technologies such as C#, I suggest using the built in SQL command with Transaction parameter.
var sqlCommand = new SqlCommand(commandText, null, transaction);
I've created a simple Console App that demonstrates this ability which can be found here:
https://github.com/hecked12/SQL-Transaction-Using-C-Sharp
In short, C# allows you to overcome this limitation where you can inspect the output of each stored procedure and use that output however you like, for example you can feed it to another stored procedure. If the output is ok, you can commit the transaction, otherwise, you can revert the changes using rollback.
On SQL Server 2008 R2, I had a mismatch in table columns that caused the Rollback error. It went away when I fixed my sqlcmd table variable populated by the insert-exec statement to match that returned by the stored proc. It was missing org_code. In a windows cmd file, it loads result of stored procedure and selects it.
set SQLTXT= declare #resets as table (org_id nvarchar(9), org_code char(4), ^
tin(char9), old_strt_dt char(10), strt_dt char(10)); ^
insert #resets exec rsp_reset; ^
select * from #resets;
sqlcmd -U user -P pass -d database -S server -Q "%SQLTXT%" -o "OrgReport.txt"

I've generated an sql file full of inserts but can't find any documentation of executing this script from a stored procedure

I'm creating a stored procedure that will delete all the data in my database and then insert the data from my sql file. The reason I am using the delete and insert instead of a restore is because a restore requires that no one is connected to the database where as deleting and inserting allows people to still be connected.
Stored Procedure:
CREATE PROCEDURE DropAndRestore
-- Add the parameters for the stored procedure here
#filepath nvarchar(200)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
Exec sp_MSFOREACHTABLE 'delete from ?
RESTORE DATABASE [landofbeds] -- These lines are what needs to be replaced
FROM DISK = #FilePath --
END
GO
The reason I am using the delete and insert instead of a restore is
because a restore requires that no one is connected to the database
where as deleting and inserting allows people to still be connected
If all you need is minimum downtime you can restore your database in db_copy. Then drop your db and rename db_copy to db.
Yes you should disconnect all the users to be able to drop your db, but it will take minimum time, while if you delete your data the table will still be unavailable for the whole duration of the delete, and as delete is always fully logged your users will wait.
To launch your script you can use xp_cmdshell that calls sqlcmd with -i but it's not a good idea. You have no control on your script execution and if something goes wrong you will have even more downtime for your users.
Does your tables have FK defined?
Exec sp_MSFOREACHTABLE 'delete from ?
will try to delete everything in order it decides and you may end up with errors when you try to delete rows that are referenced in other tables.
To execute your sql file from Stored procedure .. you can use xp_cmdshell. See steps below
First Create a Batch File (C:\testApps\test.bat) and execute your sql file from there..
e.g.
osql -S TestSQlServer -E -I C:\testApps\test.sql > C:\testApps\tlog.txt
Then add this line to your Calling Stored procedure
exec xp_cmdshell 'C:\testApps\test.bat'
Execute your procedure
**Please note you will need to enable xp_cmdshell
You can use bulk insert like this:
BULK INSERT landofbeds.dbo.SalesOrderDetail
FROM '\\computer\share\folder\neworders.txt'

Confusion with the GO statement, uncommitted transactions and alter procedure

I would like to get to the bottom of this because it's confusing me. Can anyone explain when I should use the GO statement in my scripts?
As I understand it the GO statement is not part of the T-SQL language, instead it is used to send a batch of statements to SQL server for processing.
When I run the following script in Query Analyser it appears to run fine. Then I close the window and it displays a warning:
"There are uncommitted transactions. Do you wish to commit these transactions before closing the window?"
BEGIN TRANSACTION;
GO
ALTER PROCEDURE [dbo].[pvd_sp_job_xxx]
#jobNum varchar(255)
AS
BEGIN
SET NOCOUNT ON;
UPDATE tbl_ho_job SET delete='Y' WHERE job = #job;
END
COMMIT TRANSACTION;
GO
However if I add a GO at the end of the ALTER statement it is OK (as below). How come?
BEGIN TRANSACTION;
GO
ALTER PROCEDURE [dbo].[pvd_sp_xxx]
#jobNum varchar(255)
AS
BEGIN
SET NOCOUNT ON;
UPDATE tbl_ho_job SET delete='Y' WHERE job = #job;
END
GO
COMMIT TRANSACTION;
GO
I thought about removing all of the GO's but then it complains that the alter procedure statement must be the first statement inside a query batch? Is this just a requirement that I must adhere to?
It seems odd because if I BEGIN TRANSACTION and GO....that statement is sent to the server for processing and I begin a transaction.
Next comes the ALTER procedure, a COMMIT TRANSACTION and a GO (thus sending those statements to the server for processing with a commit to complete the transaction started earlier), how come it complains when I close the window still? Surely I have satisfied that the alter procedure statement is the first in the batch. How come it complains about are uncommitted transactions.
Any help will be most appreciated!
In your first script, COMMIT is part of the stored procedure...
The BEGIN and END in the stored proc do not define the scope (start+finish of the stored proc body): the batch does, which is the next GO (or end of script)
So, changing spacing and adding comments
BEGIN TRANSACTION;
GO
--start of batch. This comment is part of the stored proc too
ALTER PROCEDURE [dbo].[pvd_sp_job_xxx]
#jobNum varchar(255)
AS
BEGIN --not needed
SET NOCOUNT ON;
UPDATE tbl_ho_job SET delete='Y' WHERE job = #job;
END --not needed
--still in the stored proc
COMMIT TRANSACTION;
GO--end of batch and stored procedure
To check, run
SELECT OBJECT_DEFINITION(OBJECT_ID('dbo.pvd_sp_job_xxx'))
Although this is a old post, the question is still in my mind after I compiled one of my procedure successfully without any begin transaction,commit transaction or GO. And the procedure can be called and produce the expected result as well.
I am working with SQL Server 2012. Does it make some change
I know this is for an answer. But words are too small to notice in comment section.

T-SQL 2005: combine multiple create/alter procedure calls in one transaction

I want to build a T-SQL change script that rolls out database changes from dev to test to production.
I've split the script into three parts:
DDL statements
changes for stored procedures (create and alter procedure)
data creation and modification
I want all of the changes in those three scripts to be made in a transaction. Either all changes in the script are processed or - upon an error - all changes are rolled back.
I managed to do this for the steps 1 and 3 by using the try/catch and begin transaction statements.
My problem is now to do the same thing for the stored procedures.
A call to "begin transaction" directly before a "create stored procedure" statement results in a syntax error telling me that "alter/create procedure statement must be the first statement inside a query batch".
So I wonder how I could combine multiple create/alter procedure statements in one transaction.
Any help is highly appreciated ;-)
Thanks
You can use dynamic SQL to create your stored procedures.
EXEC ('CREATE PROC dbo.foo AS ....`)
This will avoid the error "alter/create procedure statement must be the first statement inside a query batch"
Try this:
begin transaction
go
create procedure foo as begin select 1 end
go
commit transaction
try putting the steps in a job
BEGIN TRANSACTION
BEGIN TRY
-- Do your stuff here
COMMIT TRANSACTION
PRINT 'Successfull.'
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() as ErrorNumber,
ERROR_MESSAGE() as ErrorMessage;
ROLLBACK TRANSACTION
END CATCH

How does SQL Server treat statements inside stored procedures with respect to transactions?

Say I have a stored procedure consisting of several separate SELECT, INSERT, UPDATE and DELETE statements. There is no explicit BEGIN TRANS / COMMIT TRANS / ROLLBACK TRANS logic.
How will SQL Server handle this stored procedure transaction-wise? Will there be an implicit connection for each statement? Or will there be one transaction for the stored procedure?
Also, how could I have found this out on my own using T-SQL and / or SQL Server Management Studio?
Thanks!
There will only be one connection, it is what is used to run the procedure, no matter how many SQL commands within the stored procedure.
since you have no explicit BEGIN TRANSACTION in the stored procedure, each statement will run on its own with no ability to rollback any changes if there is any error.
However, if you before you call the stored procedure you issue a BEGIN TRANSACTION, then all statements are grouped within a transaction and can either be COMMITted or ROLLBACKed following stored procedure execution.
From within the stored procedure, you can determine if you are running within a transaction by checking the value of the system variable ##TRANCOUNT (Transact-SQL). A zero means there is no transaction, anything else shows how many nested level of transactions you are in. Depending on your sql server version you could use XACT_STATE (Transact-SQL) too.
If you do the following:
BEGIN TRANSACTION
EXEC my_stored_procedure_with_5_statements_inside #Parma1
COMMIT
everything within the procedure is covered by the transaction, all 6 statements (the EXEC is a statement covered by the transaction, 1+5=6). If you do this:
BEGIN TRANSACTION
EXEC my_stored_procedure_with_5_statements_inside #Parma1
EXEC my_stored_procedure_with_5_statements_inside #Parma1
COMMIT
everything within the two procedure calls are covered by the transaction, all 12 statements (the 2 EXECs are both statement covered by the transaction, 1+5+1+5=12).
You can find out on your own by creating a small stored procedure that does something simple, say insert a record into a test table. Then Begin Tran; run sp_test; rollback; Is the new record there? If so, then the SP ignores the outside transaction. If not, then the SP is just another statement executed inside the transaction (which I am pretty sure is the case).
You must understand that a transaction is a state of the session. The session can be in an explicit transaction state because there is at least one BEGIN TRANSACTION that have been executed in the session wherever the command "BEGIN TRANSACTION" has been throwed (before entering in a routine or inside the routine code). Otherwise, the state of the session is in an implicit transaction state. You can have multiple BEGIN TRANSACTION, but only the first one change the behavior of the session... The others only increase the ##TRANCOUNT global sesion variable.
Implicit transaction state means that all SQL orders (DDL, DML and DCL comands) wil have an invisble integrated transaction scope.