Can I rollback Dynamic SQL in SQL Server / TSQL - sql

Can I run a dynamic sql in a transaction and roll back using EXEC:
exec('SELECT * FROM TableA; SELECT * FROM TableB;');
Put this in a Transaction and use the ##error after the exec statement to do rollbacks.
eg. Code
BEGIN TRANSACTION
exec('SELECT * FROM TableA; SELECT * FROM TableB;');
IF ##ERROR != 0
BEGIN
ROLLBACK TRANSACTION
RETURN
END
ELSE
COMMIT TRANSACTION
If there are n dynamic sql statements and the error occurs in n/2 will the first 1 to ((n/2) - 1) statements be rolled back
Questions about the first answer
##Error won't pick up the error most likely
Which means that it might not pick up the error, which means a transaction might commit? Which defeats the purpose
TRY/CATCH in SQL Server 2005+
Yes I am using SQL Server 2005 but haven't used the Try Catch before
Would doing the below do the trick
BEGIN TRANSACTION
BEGIN TRY
exec('SELECT * FROM TableA; SELECT * FROM TableB;');
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH
OR I looked at some more examples on the net
BEGIN TRY --Start the Try Block..
BEGIN TRANSACTION -- Start the transaction..
exec('SELECT * FROM TableA; SELECT * FROM TableB;');
COMMIT TRAN -- Transaction Success!
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRAN --RollBack in case of Error
RAISERROR(ERROR_MESSAGE(), ERROR_SEVERITY(), 1)
END CATCH

Yes. The TXNs belong to the current session/connection and dynamic SQL uses the same context.
However, ##ERROR won't pick up the error most likely: the status has to be checked immediately after the offending statement. I'd use TRY/CATCH, assuming SQL Server 2005+
Edit: The TRY/CATCH should work OK.

Don't take our word for it that try catch will work, test it yourself. Since this is dynamic sql the easiest thing to do is to make the first statement correct (and of course it mneeds to bean update,insert or delete or there is no need for atransaction) and then make a deliberate syntax error in the second statment. Then test that the update insert or delete in the first statment went through.
I also want to point out that dynamic sql as rule is a poor practice. Does this really need to be dynamic?

Related

Is rollback required in SQL Server when transaction failed in commit tran statement

I am working in some existing application using SQL Server 2014 in the backend. I find that the pattern to commit the transaction is like
USE AdventureWorks;
GO
BEGIN TRANSACTION;
GO
DELETE FROM HumanResources.JobCandidate WHERE JobCandidateID = 10;
DELETE FROM HumanResources.JobCandidate WHERE JobCandidateID = 11;
DELETE FROM HumanResources.JobCandidate WHERE JobCandidateID = 12;
GO
COMMIT TRANSACTION;
GO
I am wondering if the query failed in commit transaction statement, do i need to have the rollback statement there?
according to this question Can a COMMIT statement (in SQL) ever fail? How?, the commit tran can fail, but do I have to roll that back since the transaction hasn't been commit successfully. Would SQL server roll that back automatically when the connection is closed?
Please point me to the documentation in MSDN or wherever you got the information.
I believe it will, after the connection is closed. You should not count on this, there are many factors to consider including connection pooling. I suggest you look into SET XACT_ABORT ON and / or using a try catch block.
The way that I use transactions:
Begin Try
Begin Tran
-- do some work here...
Commit Tran
End Try
Begin Catch
If ( ##TranCount > 0 )
Rollback Tran
End Catch
doing your transaction commit/rollbacks in a try/catch is probably a best practice.
if, however you want your code to automatically rollback all of the statements in the transaction you need to add the "set xact_abort on" statement somewhere before the begin trans statement. xact_abort automatically rolls back all of the statements in a transaction if any of them fail. to understand the effect of xact_abort, execute the following code. set xact_abort on and off and observe the contents of the table. the first statement of the batch in the sample will always fail because of a primary key violation.
use tempdb
go
if exists (select * from sys.tables where name='t') drop table t
go
create table t (id int not null primary key)
go
insert t values(1)
go
set xact_abort on
begin transaction
insert t values(1)
insert t values(2)
commit transaction
go
select * from t
You can use try...catch like the below.
BEGIN TRY
DELETE
FROM HumanResources.JobCandidate
WHERE JobCandidateID = 10;
DELETE
FROM HumanResources.JobCandidate
WHERE JobCandidateID = 11;
DELETE
FROM HumanResources.JobCandidate
WHERE JobCandidateID = 12;
COMMIT;
END TRY
BEGIN CATCH
ROLLBACK
SELECT Db_name()
,CONVERT(NVARCHAR(15), Error_number())
,CONVERT(NVARCHAR(10), Error_line())
,Error_message()
END CATCH

Error with uncommitted transaction

I have the following query that I ran in SQL Server Management Studio:
BEGIN TRANSACTION [Tran1]
BEGIN TRY
UPDATE SomeTable
SET value = 0
WHERE username = 'test'
OR [PR_RequestDate] < DATEADD(day, -2, GETDATE())
INSERT INTO SomeTable (username, value)
VALUES('test', 'test')
COMMIT TRANSACTION [Tran1]
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION [Tran1]
END CATCH
GO
After executing it every other query that I try to run on the same table runs forever, and when I try to exist SQL Server Management Studio, I get a warning that there are uncommitted transactions.
What exactly is wrong with my query and how should I fix it?
Typos will be caught immediately,further as per Ivan suggestionmyou are not showing us entire info.You are getting uncommitted transactions error due to error in update or insert and the entire query trying to rollback..You can do the follow to get total message and trancount,,
set xact_abort on--to rollback entire batch,rollback in catch is redundant
begin try
begin tran
commit
end try
begin catch
select ##trancount--gives me number of open transactions
select error_message()
rollback tran
end catch

T-SQL install / upgrade script as a transaction

I'm trying to write a single T-SQL script which will upgrade a system which is currently in deployment. The script will contain a mixture of:
New tables
New columns on existing tables
New functions
New stored procedures
Changes to stored procedures
New views
etc.
As it's a reasonably large upgrade I want the script to rollback if a single part of it fails. I have an outline of my attempted code below:
DECLARE #upgrade NVARCHAR(32);
SELECT #upgrade = 'my upgrade';
BEGIN TRANSACTION #upgrade
BEGIN
PRINT 'Starting';
BEGIN TRY
CREATE TABLE x ( --blah...
);
ALTER TABLE y --blah...
);
CREATE PROCEDURE z AS BEGIN ( --blah...
END
GO --> this is causing trouble!
CREATE FUNCTION a ( --blah...
END TRY
BEGIN CATCH
PRINT 'Error with transaction. Code: ' + ##ERROR + '; Message: ' + ERROR_MESSAGE();
ROLLBACK TRANSACTION #upgrade;
PRINT 'Rollback complete';
RETURN;
END TRY
END
PRINT 'Upgrade successful';
COMMIT TRANSACTION #upgrade;
GO
Note - I know some of the syntax is not perfect - I'm having to re-key the code
It seems as though I can't put Stored Procedures into a transaction block. Is there a reason for this? Is it because of the use of the word GO? If so, how can I put SPs into a transaction block? What are the limitations as to what can go into a transaction block? Or, what would be a better alternative to what I'm trying to achieve?
Thanks
As Thomas Haratyk said in his answer, your issue was the "go". However, you can have as many batches in a transaction as you want. It's the try/catch that doesn't like this. Here's a simple proof-of-concept:
begin tran
go
select 1
go
select 2
go
rollback
begin try
select 1
go
select 2
go
end try
begin catch
select 1
end catch
Remove the GO and create your procedure by using dynamic sql or it will fail.
EXEC ('create procedure z
as
begin
print "hello world"
end')
GO is not a SQL keyword, it is a batch separator. So it cannot be included into a transaction.
Please refer to those topics for further information :
sql error:'CREATE/ALTER PROCEDURE' must be the first statement in a query batch?
Using "GO" within a transaction
http://msdn.microsoft.com/en-us/library/ms188037.aspx

TSQL error checking code not working

I'm trying to add an alert to my log for a running insert of a view into a table when there is an error executing the query in the view. When I run the view alone, I get an invalid input into SUBSTRING (the exact wording of the error I can't remember). When I run it as part of my view -> table stored procedure, the error is ignored, then I have to go digging for the offending line and make an exception in the view's code to omit that line from the results (I know, it sounds kludge-y, but I'm doing data reduction on huge web-log files from a specialized webapp), but I digress.
I've tried two different methods for trying to catch the error and neither are triggered in such a way to insert the row indicating an error in my execution result table (refresh_results). I think I may be missing some fundamental - perhaps the errors are being encapsulated in come way. If I can't detect the error, the only way to notice an error is if someone notices the number of entries into the table is low for a given period of time.
SELECT #TransactionName = 'tname';
BEGIN TRANSACTION #TransactionName;
BEGIN TRY
print 'tname ***In Try***';
if exists (select name from sysobjects where name='tablename')
begin
drop table tablename;
end
select * into tablename
from opendatasource('SQLNCLI', 'Data Source=DATABASE;UID=####;password=####').dbo.viewname;
COMMIT TRANSACTION #TransactionName;
END TRY
BEGIN CATCH
print 'tablename ***ERROR - check for SUBSTRING***';
begin transaction
set #result_table = 'tablename ***ERROR - check for SUBSTRING***'
select #result_time = getdate(),
#result_rows = count(logtime)
from tablename
insert INTO [dbo].[refresh_results] (result_time, result_table, result_rows)
values (#result_time, #result_table, #result_rows);
commit transaction
ROLLBACK TRANSACTION #TransactionName;
END CATCH
or
if exists (select name from sysobjects where name='tablename')
begin
drop table tablename;
end
select * into tablename
from opendatasource('SQLNCLI', 'Data Source=DATABASE;UID=####;password=####').dbo.viewname;
print '##error'
print ##error
if ##error <> 0
Begin
print 'tablename ***ERROR - check for SUBSTRING***';
set #result_table = 'tablename ***ERROR - check for SUBSTRING***'
select #result_time = getdate(),
#result_rows = count(logtime)
from tablename
insert INTO [dbo].[refresh_results] (result_time, result_table, result_rows)
values (#result_time, #result_table, #result_rows);
End
Your nested transactions aren't doing what you think. You are rolling back the error you thought you stored. Roll back the initial transaction and then, if you feel the need, start a new transaction for logging the error.
See here.
You have two seperate problems
In your first example you are running transactions that do the following:
BEGIN TRAN
...error...
BEGIN TRAN
...log error...
COMMIT TRAN
ROLLBACK TRAN
The inner transaction is rolled back with the outer transaction. Maybe try:
BEGIN TRAN
...error...
ROLLBACK TRAN
BEGIN TRAN
...log error...
ROLLBACK TRAN
The second example you are using ##ERROR. As I understand it as soon as you run something ##ERROR is replaced. That something I think includes the print statement.
If you change it to something like:
DECLARE #Error INT
select * into tablename
from opendatasource('SQLNCLI', 'Data Source=DATA3;UID=;password=').dbo.viewname;
SET #Error = ##ERROR
print '##error'
print #Error
if #Error <> 0
...log the error
The advantage of the TRY CATCH is that if you have an error it will catch it. The ##ERROR method works 100% but it only works on the last line run. so if you have an error with DROP TABLE tablename ##ERROR won't get it (unless you add another check)
Ok, so I had to use a helper procedure to add a log entry. I think what was going on is that the rollback was also rolling back the log entry.
This is what I had to do:
DECLARE #myError tinyint;
BEGIN TRY
BEGIN TRANSACTION;
if exists (select name from sys.sysobjects where name='table_name')
begin
drop table table_name
end
select * into table_name
from opendatasource('SQLNCLI', 'Data Source=###;UID=###;password=###').view_Table
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
set #myError = 1
ROLLBACK TRANSACTION;
END CATCH
if #myError <> 0
begin
exec dbo.table error
end
ELSE
EXEC exec dbo.table normal row

While linked server is closed, exec the stored procedure will get an error

I have a stored procedure for select data between many different sql server, and all the sql server have set the link server at the sql server where the stored procedure built in.
Here is my procedure:
Create Proc dbo.spGetData
#code as char(4)
AS
if (#code='aaaa')
Select date From [ServerA].Stock.dbo.Syspara
else if (#code='bbbb')
Select date From [ServerB].Stock.dbo.Syspara
else if (#code='cccc')
Select date From [ServerC].Stock.dbo.Syspara
else if (#code='dddd')
Select date From [ServerD].Stock.dbo.Syspara
GO
If the [ServerB] isn't alive or is closed, when I call:
exec dbo.spGetData 'dddd'
There will be an error, but if the all 4 server are alive, the query will return without error.
How can I do to avoid the problem?
Add TRY..CATCH error handling:
The following example shows how an object name resolution error generated by a SELECT statement is not caught by the TRY…CATCH construct, but is caught by the CATCH block when the same SELECT statement is executed inside a stored procedure.
BEGIN TRY
-- Table does not exist; object name resolution
-- error not caught.
SELECT * FROM NonexistentTable;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() as ErrorNumber,
ERROR_MESSAGE() as ErrorMessage;
END CATCH
The error is not caught and control passes out of the TRY…CATCH construct to the next higher level.
Running the SELECT statement inside a stored procedure will cause the error to occur at a level lower than the TRY block. The error will be handled by the TRY…CATCH construct.
-- Verify that the stored procedure does not exist.
IF OBJECT_ID ( N'usp_ExampleProc', N'P' ) IS NOT NULL
DROP PROCEDURE usp_ExampleProc;
GO
-- Create a stored procedure that will cause an
-- object resolution error.
CREATE PROCEDURE usp_ExampleProc
AS
SELECT * FROM NonexistentTable;
GO
BEGIN TRY
EXECUTE usp_ExampleProc
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() as ErrorNumber,
ERROR_MESSAGE() as ErrorMessage;
END CATCH;
If, like me, you still have to use SQL Server 2000, then you can't use try catch blocks.
I don't think it will help with the time out, but if you break your if statements out into individual statements and check ##ERROR after each one, that will get get you better control.