Sql script with Transaction/Rollback with "Go" statement - sql-server

I create desktop application. with user have select .sql file and run it with Transaction/Rollback.
in that case i read all context from sql file. and put between following code to replace sql file script with *****$Replace$***** text.
How can i handle transaction/Rollback if sql file contain "GO" batch. its throwing error.
any solution to Handel thhis type of scenario to any sql file convert to Transaction/Rollback .
Note: without changes in sql file script.
sql file script:
INSERT INTO [dbo].[User] ([Name] ,[ContectNo]) VALUES ('m3','33')
GO BEGIN TRANSACTION
INSERT INTO [dbo].[User] ([Name] ,[ContectNo]) VALUES ('m3',null)
GO BEGIN TRANSACTION
INSERT INTO [dbo].[User] ([Name] ,[ContectNo]) VALUES ('m3','99')
GO
-----------------------------------------------------
Transaction block: replace sql file query from *****$Replace$*****
-----------------------------------------------------
SET XACT_ABORT ON;
GO
BEGIN TRY
BEGIN TRANSACTION
*****$Replace$*****
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##ERROR > 0
BEGIN
ROLLBACK TRANSACTION
DECLARE #ErrorMessage NVARCHAR(max) = ERROR_MESSAGE()
DECLARE #ErrorSeverity INT = ERROR_SEVERITY()
DECLARE #ErrorState INT = ERROR_STATE()
DECLARE #ErrorLine INT = ERROR_LINE()
RAISERROR (#ErrorMessage,
#ErrorSeverity,
#ErrorState
);
END
END CATCH
SET XACT_ABORT OFF

You are getting an error, because GO is not a command that should be sent to the SQL Server engine. It is something recognized by tools like SSMS, sqlcmd and osql. It signals the end of the batch of T-SQL statements and serves as a signal that the commands so far (the current batch) should be send to the SQL Server engine.
You should not try to execute GO statements as T-SQL. You should either try to parse the file for GO lines, split it into separate batches and execute them one by one, or to make sure you are passing valid T-SQL contents in these files.
You can see more information about GO statement in SQL Server Utilities Statements - GO article.

Related

Executing a stored procedure fails when placed in transaction

I have a query that I want to execute via a linked server. The query looks like this:
USE db1;
SET xact_abort ON;
DECLARE #statement NVARCHAR(max);
SET #statement = 'EXECUTE (''INSERT INTO T1(V1, V2) VALUES (1, 2)'') AT LS1';
BEGIN try
BEGIN TRANSACTION
EXEC Sp_executesql #statement
COMMIT TRANSACTION
END try
BEGIN catch
IF ( Xact_state() ) = -1
BEGIN
PRINT Error_message()
ROLLBACK TRANSACTION
END
IF ( Xact_state() ) = 1
BEGIN
PRINT 'COMMIT OPEN TRANSACTION'
COMMIT TRANSACTION
END
INSERT INTO tblerrmsg (errornumber, errorseverity, errorstate, errorline, errormessage) EXECUTE Usp_geterrorinfo;
END catch
This fails with an entry in my TblErrMsg table.
ErrorNumber = 8501, ErrorSeverity = 16, ErrorState = 3, ErrorLine = 1, ErrorMessage = MSDTC on server 'XXX' is unavailable.
So I researched for the specific error message and checked if the Distributed Transaction Coordinator Service was running on the server, but this was already the case. Even a restart of the service did not bring any change. Next I tried to remove the transaction and execute the following procedure:
USE db1;
DECLARE #statement NVARCHAR(max);
SET #statement = 'EXECUTE (''INSERT INTO T1(V1, V2) VALUES (1, 2)'') AT LS1';
BEGIN try
EXEC Sp_executesql #statement
END try
BEGIN catch
PRINT Error_message()
END catch
And this time it worked. There were no errors and the INSERT also worked. So I'm wondering what really the problem is. Apparently there seems to be no problem with the execution of the procedure nor with the Linked Server connection.
Has anyone ever had a similar problem, or has an explanation for this behavior?
It's possible you're missing the key word DISTRIBUTED when specifying the explicit transaction.
Instead of
BEGIN TRANSACTION
Try
BEGIN DISTRIBUTED TRANSACTION
According to the Docs this is the way to specify "... the start of a Transact-SQL distributed transaction managed by Microsoft Distributed Transaction Coordinator (MS DTC)"
Without the explicit transaction the default behavior afaik is the transaction across linked server is not guaranteed to be atomic and cannot be rolled back. This why it works when no explicit transaction is specified.
When BEGIN TRANSACTION is specified without 'DISTRIBUTED' the statement is asking something that's not possible when the procedure contains a linked server reference.

Error Handling in VBS third party program like SQL

I have to develop a WinCC Visual Basic Script management application. In this application I read an XML archive, and after that I put the information in a SQL database through a SQL INSERT query.
My problem is that I don't know how to do the error handling to view the SQL errors in VBScript MsgBox for example.
Activating the error handling with On Error Resume Next and after the evaluation of these errors with If Err.Number <> 0 Then ... the errors produced in SQL Server don't appear in VBScript.
If you want to get SQL Server error, You can use stored procedure with transaction to insert data into table:
create procedure dbo.InsertTable (
#param1 nvarchar(80)
,#param2 nvarchar(80)
,#error_text nvarchar(400) output)
as
begin
begin tran
begin try
insert into YourTable (column1, column2)
values (#param1, #param2)
end try
begin catch
set #error_text = error_message()
rollback
return
end catch
commit
end
Now You will get eventually error from the output parameter #error_text
declare #error_text nvarchar(400)
exec dbo.InsertTable 'Value1','Value2', #error_text output
select #error_text

save point within a transaction and store error message in sql server table

I have stored procedure which is for inserting data into a table. This procedure is called from asp.net application which handles the transaction start, commit and rollback functionality. Inside the stored procedure there is no transaction.
In this scenario my application is working fine and it is hosted in the live. Now, inside the store procedure, I have to add a new functionality to insert another table by linked server to another database and if error appears then I have to store it in the database.
We want to implement this insertion in such a way so that the previous sp will be working fine.
Noted that if error from the insertion of linked server comes then entire process is rolled back and also save point is not working. We can do this by dot net code but this is for more 40 modules, so I have to do this by sp . So, how can we implement this.
MSDN SAVE TRANSACTION
CREATE PROCEDURE [dbo].[SaveCustomer]
#Firstname nvarchar(50),
#Lastname nvarchar(50)
AS
DECLARE #ReturnCode int = 1 -- 1 - success
,#NewID int
---------------------------------------------- These variables are for TRY/CATCH RAISEERROR and BEGIN TRAN/SAVE POINT use
,#tranCounter int -- #TranCounter > 0 means an active transaction was started before the procedure was called.
,#errorMessage nvarchar(4000) -- echo error information to the caller. Message text.
,#errorSeverity int -- Severity.
,#errorState int; -- State
SET #tranCounter = ##TRANCOUNT;
IF #tranCounter > 0
SAVE TRANSACTION SaveCustomer_Tran;
ELSE
BEGIN TRANSACTION;
BEGIN TRY
INSERT dbo.Customer (
Firstname
,Lastname)
VALUES (
#Firstname
,#Lastname)
SET #NewID = scope_identity()
IF #tranCounter = 0
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF #tranCounter = 0
ROLLBACK TRANSACTION;
ELSE
IF XACT_STATE() <> -1
ROLLBACK TRANSACTION SaveCustomer_Tran;
SELECT #errorMessage = 'Error in [SaveCustomer]: ' + ERROR_MESSAGE(), #errorSeverity = ERROR_SEVERITY(), #errorState = ERROR_STATE();
RAISERROR (#errorMessage, #errorSeverity, #errorState);
SET #ReturnCode = -1
END CATCH
SELECT #ReturnCode as [ReturnCode], #NewID as [NewID]
GO
There are two accepted ways to escape the current transaction rollback tarpit.
One way is to use a looopback connection. Either via linked server, or from SQLCLR, connect back to the current server and write the INSERT, making sure DTC enrollment is not allowed. As the loopback connection is a different transaction, you can safely rollback in the original transaction and preserve the INSERT. This approach has the drawback that is very easy to deadlock yourself.
The other, less known, way is to use sp_trace_generateevent to fire off a user-configurable event. This may not seem much, but you can create event notifications for these events and then impelemnt handling that does the INSERT in processing the event. Eg. see Sql progress logging in transaction. This approach has the drawback of being difficult.

Return if remote stored procedure fails

I am in the process of creating a stored procedure. This stored procedure runs local as well as external stored procedures. For simplicity, I'll call the local server [LOCAL] and the remote server [REMOTE].
Here's a simple topology:
The procedure
USE [LOCAL]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[monthlyRollUp]
AS
SET NOCOUNT, XACT_ABORT ON
BEGIN TRY
EXEC [REOMTE].[DB].[table].[sp]
--This transaction should only begin if the remote procedure does not fail
BEGIN TRAN
EXEC [LOCAL].[DB].[table].[sp1]
COMMIT
BEGIN TRAN
EXEC [LOCAL].[DB].[table].[sp2]
COMMIT
BEGIN TRAN
EXEC [LOCAL].[DB].[table].[sp3]
COMMIT
BEGIN TRAN
EXEC [LOCAL].[DB].[table].[sp4]
COMMIT
END TRY
BEGIN CATCH
-- Insert error into log table
INSERT INTO [dbo].[log_table] (stamp, errorNumber,
errorSeverity, errorState, errorProcedure, errorLine, errorMessage)
SELECT GETDATE(), ERROR_NUMBER(), ERROR_SEVERITY(), ERROR_STATE(), ERROR_PROCEDURE(),
ERROR_LINE(), ERROR_MESSAGE()
END CATCH
GO
When using a transaction on the remote procedure, it throws this error:
OLE DB provider ... returned message "The partner transaction manager has disabled its support for remote/network transactions.".
I get that I'm unable to run a transaction locally for a remote procedure.
How can I ensure that the this procedure will exit and rollback if any part of the procedure fails?
Notes
With regards to combining the simple procedures, some of them are used individually.
IMO easiest way is to
Add Return value to remote proc.
Wrap remote proc into transaction and try catch (inside remote proc). If error happened return false.
On local stored proc if false, simply do not continue.
I also fail to understand the reason behind multiple BEGIN TRANS / COMMIT in the local proc. I mean if this is month end rollup, shuldn't this be one big transaction rather than a bunch of small? Otherwise your trans 1 and 2 may commit successfully, but 3 will fail and that's that.
Names are made up ofc:
CREATE PROC [remote].db.REMOTE_PROC (
#return_value int output
)
AS
BEGIN
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANS
... do stuff ...
set #return_value = 1;
COMMIT;
END TRY
BEGIN CATCH
set #return_value = 0;
END CATCH
END
and the local proc
CREATE PROC [local].db.[monthlyRollUp] AS
BEGIN
SET XACT_ABORT ON;
declare #ret int;
EXECUTE [remote].dbo.REMOTE_PROC #return_value = #ret OUTPUT;
IF #ret = 0
PRINT 'ERROR :('
RETURN
END IF
BEGIN TRANS
-- one big transaction here
EXEC [LOCAL].[DB].[table].[sp1];
EXEC [LOCAL].[DB].[table].[sp2];
EXEC [LOCAL].[DB].[table].[sp3];
EXEC [LOCAL].[DB].[table].[sp4];
COMMIT;
END;
afair [remote].dbo.REMOTE_PROC runs its own transaction space, and returns 1 if successful. Local proc, checks the return value and decides whether to proceed or not.
sp1 sp2 sp3 and sp4 are all running in one single transactions, as having multiple transactions for each of them does not really make much sense to me.
You can try to execute both stored procedure into seperate TRY CATCH block and check for corresponding ERROR_NUMBER in CATCH block. If ERROR_NUMBER is same as error you are getting you can simply return or raiseerror as per your requirement.
Is it causing a fatal error. Please check what error severity is in the exception.
I might be a little unclear on what you want. If you need the entire monthlyRollUp SP to rollback on a failure of either the remote or local procedures, then you will need a distributed transaction coordinator. This will allow the servers to communicate the information about the transaction and coordinate the commits. I.e., both servers have to indicate that all necessary locks were gained and then coordinate commits on both servers so that the operation is automic. Here is one example of setting up a DTC:
http://social.msdn.microsoft.com/forums/en-US/adodotnetdataproviders/thread/7172223f-acbe-4472-8cdf-feec80fd2e64/
If you don't want the remote procedures to participate/affect the transaction, you can try setting:
SET REMOTE_PROC_TRANSACTIONS OFF;
http://msdn.microsoft.com/en-us/library/ms178549%28SQL.90%29.aspx
I haven't used that setting before though so I'm not sure if it will accomplish what you need.
If you can't or don't want to use DTC, and don't want to use CLR, then then you need to call the remote sp last, as you won't be able to rollback the remote sp call.
SET NOCOUNT, XACT_ABORT ON
SET REMOTE_PROC_TRANSACTIONS OFF;
BEGIN TRY
DECLARE #ret INT
BEGIN TRAN
--Perform these in a transaction, so they all rollback together
EXEC [LOCAL].[DB].[table].[sp1]
EXEC [LOCAL].[DB].[table].[sp2]
EXEC [LOCAL].[DB].[table].[sp3]
EXEC [LOCAL].[DB].[table].[sp4]
--We call remote sp last so that if it fails we rollback the above transactions
--We'll have to assume that remote sp takes care of itself on error.
EXEC [REMOTE].[DB].[table].[sp]
COMMIT
END TRY
BEGIN CATCH
--We rollback
ROLLBACK
-- Insert error into log table
INSERT INTO [dbo].[log_table] (stamp, errorNumber,
errorSeverity, errorState, errorProcedure, errorLine, errorMessage)
SELECT GETDATE(), ERROR_NUMBER(), ERROR_SEVERITY(), ERROR_STATE(),ERROR_PROCEDURE(),
ERROR_LINE(), ERROR_MESSAGE()
END CATCH
If the local sp's depend on results from the remote stored procedure, then you can use a CLR sp (will need EXTERNAL_ACCESS permissions) and manage the transactions explicitly (basically, a roll your own DTC, but no two-phase commit. You're effectively delaying the remote commit.)
//C# fragment to roll your own "DTC" This is not true two-phase commit, but
//may be sufficient to meet your needs. The edge case is that if you get an error
//while trying to commit the remote transaction, you cannot roll back the local tran.
using(SqlConnection cnRemote = new SqlConnection("<cnstring to remote>"))
{
try {
cnRemote.Open();
//Start remote transaction and call remote stored proc
SqlTransaction trnRemote = cnRemote.BeginTransaction("RemoteTran");
SqlCommand cmdRemote = cnRemote.CreateCommand();
cmdRemote.Connection = cnRemote;
cmdRemote.Transaction = trnRemote;
cmdRemote.CommandType = CommandType.StoredProcedure;
cmdRemote.CommandText = '[dbo].[sp1]';
cmdRemote.ExecuteNonQuery();
using(SqlConnection cnLocal = new SqlConnection("context connection=true"))
{
cnLocal.Open();
SqlTransaction trnLocal = cnLocal.BeginTransaction("LocalTran");
SqlCommand cmdLocal = cnLocal.CreateCommand();
cmdLocal.Connection = cnLocal;
cmdLocal.Transaction = trnLocal;
cmdLocal.CommandType = CommandType.StoredProcedure;
cmdLocal.CommandText = '[dbo].[sp1]';
cmdLocal.ExecuteNonQuery();
cmdLocal.CommandText = '[dbo].[sp2]';
cmdLocal.ExecuteNonQuery();
cmdLocal.CommandText = '[dbo].[sp3]';
cmdLocal.ExecuteNonQuery();
cmdLocal.CommandText = '[dbo].[sp4]';
cmdLocal.ExecuteNonQuery();
//Commit local transaction
trnLocal.Commit();
}
//Commit remote transction
trnRemote.Commit();
} // try
catch (Exception ex)
{
//Cleanup stuff goes here. rollback remote tran if needed, log error, etc.
}
}

Maintain transaction on Linked server inside a stored procedure which uses OpenRowSet command to read data from Excel file

I have a Helper DB which is on 32bit sql server and i have added a linked server of 64bit,
to perform Excel Import operation as Jet.oledb driver is not supported on 64-bit sql server machine.
Everything is working fine, but i have to maintain transactions for insert ,update,delete that happens on linked server database,
I have configured DTC service on client machine and server machine,
Added Block on top of sp for transaction on stored procedure start like,
BEGIN TRY SET XACT_ABORT ON BEGIN TRANSACTION
BEGIN TRY
SET XACT_ABORT ON
BEGIN TRANSACTION
--Code
--Code
END TRY
BEGIN CATCH
IF ##TRANCOUNT<>0 ROLLBACK TRANSACTION
--// raise error to log in website.
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
But it throws an err saying,
The requested operation could not be performed because OLE DB provider "Microsoft.Jet.OLEDB.4.0" for linked server "(null)" does not support the required transaction interface.
It is throwing error on Openrowset function that i have used to read data from Excel sheet
i.e,
SELECT * FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0',
'Excel 5.0;HDR=Yes; IMEX=1;Database=C:\test.xls','SELECT EmployeeID FROM [Sheet$]
where EmployeeID is not null')
I m really stuck and getting Crazy as am not able to find any solution,
Thanks in Advance for Help,
I had a similar problem, importing from CSV. Solved it by naming my transaction (e.g. BEGIN TRAN mytran) and checking ##ROWCOUNT instead of ##TRANCOUNT.
I understand it's a long time since OP asked, but maybe it could help somebody googling for this issue. :)
What if you open the Excel file in a cursor, but before the Begin Trans. ?

Resources