Using multiple relating statements inside a stored procedure? - sql

I am using TSQL. I found this stored procedure template in the internet.
I want to first create a table A and then create from table a the table C. Would this work?
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE <Procedure_Name, sysname, ProcedureName>
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRAN
SELECT Name into table_A From table1
COMMIT TRAN
BEGIN TRAN
SELECT Name into table_C From table_A
COMMIT TRAN
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRAN
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT #ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState );
END CATCH
END
GO

FIRST - there is no need to write BEGIN TRANSACTION and COMMIT arround all SQL Statement that manipulate data or structures inside the database. SQL Server run in autocommit mode, which mean that every SQL statement (INSERT, DELETE, UPDATE, CREATE, ALTER...) has it own transacion scope. If you want to have a transaction involving many statement use only one transaction...
SECOND - The syntax of RAISERROR does not accept parameters for then 2nd and 3rd argument as shown in the doc (just press F1 on the command when in SSMS) :
RAISERROR ( { msg_id | msg_str | #local_variable }
{ ,severity ,state }
[ ,argument [ ,...n ] ] )
[ WITH option [ ,...n
As you see, only the message can be argued as a local variable (it is why you get the error...)
Severity must be 16 which is the reserved class of developper error raising.
1 is the default state, but this is no more used...
THIRD - because the transaction is not systematically began whe entering in the CATCH part, you need to test if the transaction is alive or not with the XACT_STATE() function
FOURTH - it is preferable to use THROW instead of RAISERROR as the doc says...
FITH - Instead of writing separates command DECLARE and a SELECT for assignement, you can declare and set simultaneously
SIXTH - terminate all your TSQL sentences with a semicolon
Well now you have the code :
CREATE PROCEDURE <Procedure_Name, sysname, ProcedureName>
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION;
SELECT Name into table_A From table1;
SELECT Name into table_C From table_A;
COMMIT TRAN;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
ROLLBACK TRAN;
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
THROW 55555, #ErrorMessage, 1;
END CATCH
END
GO

It will definitely work. Stored Procedure allows you to have multiple statement (create table, insert, update, index) and many more.

The best way to handle exceptions in SQL is, oddly enough, to not handle them at all.
If you catch the exception, and there are multiple messages, you lose that info. You also prevent the exception from bubbling up properly to the client. It only looks like a strange SELECT.
Furthermore, you can't catch all exceptions anyway. There are a number of fatal exceptions which are un-catchable.
So the best thing to do is to just use XACT_ABORT ON to ensure that any transactions are always rolled back and not left hanging. Note that not every query needs a transaction, only if you want to do multiple things atomically.
You should also use NOCOUNT ON for performance reasons
CREATE OR ALTER Procedure_Name
AS
SET XACT_ABORT, NOCOUNT ON;
SELECT 'Something';
GO
CREATE OR ALTER Procedure_Name
AS
SET XACT_ABORT, NOCOUNT ON;
BEGIN TRAN;
UPDATE SomeTable....;
INSERT SomeTable....;
COMMIT;
GO

Related

Create SQL Server procedure in a transaction

I need to create two procedures in a SQL Server transaction. If failure, I need to rollback the create(s) and any other executed queries in this transaction. I know the create statement must be the first statement in query batch, but I need to know how handle the transaction with multiple batches.
BEGIN TRANSACTION
CREATE PROCEDURE [dbo].[SP_SP-1]
#id BIGINT
AS
BEGIN
SET NOCOUNT ON;
-- SQL statements
END
GO
CREATE PROCEDURE [dbo].[SP_SP-2]
#id BIGINT
AS
BEGIN
SET NOCOUNT ON;
-- SP-2 statements
END
GO
UPDATE Table
SET Value = '1.0.0.5'
COMMIT TRANSACTION / ROLLBACK TRANSACTION
Below is one method to execute multiple batches in a transaction. This uses a temp table to indicate if any batch erred and perform a final COMMIT or ROLLLBACK accordingly.
Another method is to encapsulate statements that must be in single-statement batch (CREATE PROCEDURE, CREATE VIEW, etc.) but that can get rather ugly when quotes within the literal text must be escaped.
CREATE TABLE #errors (error varchar(5));
GO
BEGIN TRANSACTION
GO
CREATE PROCEDURE [dbo].[USP_SP-1]
#id bigint
AS
BEGIN
SET NOCOUNT ON;
-- SP Statments
END;
GO
IF ##ERROR <> 0 INSERT INTO #errors VALUES('error');
GO
CREATE PROCEDURE [dbo].[USP_SP-2]
#id BIGINT
AS
BEGIN
SET NOCOUNT ON;
-- SP-2 Statments
END;
GO
IF ##ERROR <> 0 INSERT INTO #errors VALUES('error');
GO
UPDATE Table SET Value='1.0.0.5'
GO
IF ##ERROR <> 0 INSERT INTO #errors VALUES('error');
GO
IF EXISTS(SELECT 1 FROM #errors)
BEGIN
IF ##TRANCOUNT > 0 ROLLBACK;
END
ELSE
BEGIN
IF ##TRANCOUNT > 0 COMMIT;
END;
GO
IF OBJECT_ID(N'tempdb..#errors', 'U') IS NOT NULL
DROP TABLE #errors;
GO
I suggest you to study more about this subject in Handling Transactions in Nested SQL Server Stored Procedures.
From the beginning, your syntax is wrong. You cannot begin a transaction and then create a procedure, you need to do just the opposite:
CREATE PROCEDURE [dbo].[SP_SP-1]
#id bigint
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION
SET NOCOUNT ON;
-- SP-2 Statments
Update Table set Value='1.0.0.5'
END TRY
BEGIN CATCH
--handle error and perform rollback
ROLLBACK
SELECT ERROR_NUMBER() AS ErrorNumber
SELECT ERROR_MESSAGE() AS ErrorMessage
END CATCH
END
It is best practice to use TRY and CATCH when attempting to perform update inside transaction scope.
Please read more and investigate using the link I provided to get a bigger picture.
To use Transaction, you need to know what is the meaning of transaction. It's meaning of 'Unit of work either in commit state or rollback state'.
So when you use transaction, you must know that where you declare and where you close. So you must start and end transaction in the parent procedure only than it will work as a unit of work i.e. whatever no of query execute of DML statement, it uses the same transaction.
I do not understand why your update statement outside of procedure and transaction portion too.
It should be (See my comments, you can use TRY Catch same as c sharp) :
Create PROCEDURE [dbo].[SP_SP-1]
#id bigint
AS
BEGIN
Begin Transaction
SET NOCOUNT ON;
-- SP Statments
Exec SP_SP-2 #id --here you can pass the parameter to another procedure, but do not use transaction in another procedure, other wise it will create another transaction
If ##Error > 0 than
Rollback
Else
Commit
End
END
GO
--Do not use transaction in another procedure, otherwise, it will create another transaction which has own rollback and commit and do not participate in the parent transaction
Create PROCEDURE [dbo].[SP_SP-2]
#id BIGINT
AS
BEGIN
SET NOCOUNT ON;
-- SP-2 Statments
END
GO
i find this solution to execute the procedure as string execution , it`s a workaround to execute what i want
Begin Try
Begin Transaction
EXEC ('
Create PROCEDURE [dbo].[SP_1]
#id bigint
AS
BEGIN
SET NOCOUNT ON;
SP-1
END
GO
Create PROCEDURE [dbo].[SP_Inc_Discovery_RunDoc]
#id bigint
AS
BEGIN
SET NOCOUNT ON;
Sp-2
END')
Update Table set Value='1.0.0.5'
Commit
End Try
Begin Catch
Rollback
Declare #Msg nvarchar(max)
Select #Msg=Error_Message();
RaisError('Error Occured: %s', 20, 101,#Msg) With Log;
End Catch

Unable to Use Transactions with Go statement in SQL Server 2008

BEGIN TRY
BEGIN TRANSACTION
SET ANSI_NULLS ON
Go
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION dbo.RerurnStaticValue
(
#value nvarchar(10)
)
RETURNS varchar(max)
AS
BEGIN
DECLARE
#ReturnValue nvarchar(10)
SET #ReturnValue = #value
RETURN #ReturnValue
END
COMMIT TRAN -- Transaction Success!
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRAN --RollBack in case of Error
select ERROR_MESSAGE()
END CATCH
I am preparing a very long script and try to implement the transaction in the script so in case if there will be any error in my script it will not effect my database. But I am getting the error Create function must be the only statement in the batch when implementing transactions.
Please help.
I think the documentation on functions is pretty clear:
User-defined functions cannot be used to perform actions that modify
the database state.
COMMIT/ROLLBACK definitely fall into this category.
Use a stored procedure instead.

Batchwise Script Execution

I have long script which contains the Create tables, create schemas,insert data,update tables etc.I have to do this by only on script in batch wise.I ran it before but it created every time some error due to this some object will present inside the database. So In need some mechanism which can handle the batch execution if something goes wrong the whole script should be rolled back.
Appreciated Help and Time.
--343
Try this:
DECLARE #outer_tran int;
SELECT #outer_tran = ##TRANCOUNT;
-- find out whether we are inside the outer transaction
-- if yes - creating save point if no starting own transaction
IF #outer_tran > 0 SAVE TRAN save_point ELSE BEGIN TRAN;
BEGIN TRY
-- YOUR CODE HERE
-- if no errors and we have started own transaction - commit it
IF #outer_tran = 0 COMMIT;
END TRY
BEGIN CATCH
-- if error occurred - rollback whole transaction if it is own
-- or rollback to save point if we are inside the external transaction
IF #outer_tran > 0 ROLLBACK TRAN save_point ELSE ROLLBACK;
--and rethrow original exception to see what happens
DECLARE
#ErrorMessage nvarchar(max),
#ErrorSeverity int,
#ErrorState int;
SELECT
#ErrorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5)),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState);
END CATCH
While I might not have caught all the nuances of your question, I believe XACT_ABORT will deliver the functionality you seek. Simply add a
SET XACT_ABORT ON;
to the beginning of your script.
With the 2005 release of SQL Server, you have access to try/catch blocks in TSQL as well.

Transaction handling in Trigger (TRY/CATCH....XACT_ABORT ON)

I have the process scenario on SQL Server 2008R2:
• A usp to gather data and then a transfer data between two SQL Servers
This process is to be done with transaction at all levels of the process (usp, SSIS, and trigger)
In the data flow transferring the data to DB7.dbo.Dest, this table has an AFTER INSERT trigger which inserts the data that just came through into the final table DB7.dbo.FinalDestination:
CREATE TRIGGER [dbo].[Insert_OnStaging] ON [dbo].[Dest]
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON; --Rollsback complete transaction if there are any errors
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO [DB7].[dbo].[FinalDestination] WITH (TABLOCK)
(Column1
,Column2
)
SELECT I.Column1, I.Column2
FROM INSERTED I
INNER JOIN [DB7].[dbo].[Dest] PR
ON I.IDcol = PR.IDcol
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 AND XACT_STATE() <> 0
ROLLBACK TRANSACTION;
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
DECLARE #ErrorLine INT;
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE(),
#ErrorLine = ERROR_LINE()
;
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState, -- State.
#ErrorLine --Error Line
);
END CATCH;
END
At every level, I have tried to be defensive about the data due to the sensitivity of the data properly and completely reaching the final table.
In regards to the SSIS, from what I've read and tested it seems to work fine.
My biggest concern is the trigger which I scripted above. From my reading and understanding, setting XACT_ABORT ON will roll back the transaction inside the TRY block if any errors (in other words, there is an uncommitable transaction). In this case I went ahead and still added the rollback transaction portion in the CATCH block as a piece of mind since it would never reach (from my understanding). At the same time, I added the WITH (TABLOCK) option in order to lock the table while performing the INSERT.
In the case of the trigger, is the TRY...CATCH even necessary with the XACT_ABORT being ON? Is the COMMIT TRANSACTION necessary inside the TRY block? As I have also seen it committed after the CATCH block based on the ##TRANCOUNT
BEGIN TRY
BEGIN TRANSACTION
[Tsql here]
END TRY
BEGIN CATCH
[Error Handling]
END CATCH
IF ##TRANCOUNT > 0
COMMIT TRANSACTION
END
Answers and critique are welcomed and thank you in advance. Please excuse any typos as I tried to generalize the names...
You need TRY..CATCH even though you're using XACT_ABORT. XACT_ABORT aborts the tran but continues running the batch/procedure! This is very, very nasty behavior. It means that DML/DDL can still run after an error has occurred but outside of a transaction so that you can never roll it back.
SQL Server does not have any mechanism to avoid that except for TRY..CATCH. I'm not sure what XACT_ABORT is ever good for. In your example neither does it help not does it hurt.
And yes, you can move the COMIT outside the TRY if you want to. Just make sure to properly balance it with the BEGIN TRAN.

Can you detect INSERT-EXEC scenario's?

Is it possible to DETECT whether the current stored procedure is being called by an INSERT-EXEC statement?
Yes, I understand we may want to no longer use INSERT-EXEC statements...that is NOT the question I am asking.
The REASON I am using INSERT-EXEC is because i am hoping to promote re-use of stored procedures rather than re-writing the same SQL all the time.
Here's why I care:
Under the INSERT-EXEC scenario the original error message will get lost once a ROLLBACK is requested. As such, any records created will now be orphaned.
Example:
ALTER PROCEDURE [dbo].[spa_DoSomething]
(
#SomeKey INT,
#CreatedBy NVARCHAR(50)
)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION
-- SQL runs and throws an error of some kind.
COMMIT TRAN
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRAN
-- If this procedure is called using an INSERT-EXEC
-- then the original error will be lost at this point because
-- "Cannot use the ROLLBACK statement within an INSERT-EXEC statement."
-- will come-up instead of the original error.
SET #ErrorMessage = ERROR_MESSAGE();
SET #ErrorSeverity = ERROR_SEVERITY();
SET #ErrorState = ERROR_STATE();
RAISERROR(#ErrorMessage, #ErrorSeverity, #ErrorState)
END CATCH
RETURN ##Error
END
I've come up with a bit of a kludge (ok, it's a big kludge), based on the fact that you can't have nested INSERT--EXECUTE... statements. Basically, if your "problem" procedure is the target of an INSERT--EXECUTE, and itself contains an INSERT--EXECUTE, then an error will be raised. To make this work, you'd have to have a (quite probably pointless) INSERT--EXECUTE call in the procedure, and wrap it in a TRY--CATCH block with appropriate handling. Awkward and obtuse, but if nothing else comes up it might be worth a try.
Use the following to test it out. This will create three procedures:
IF objectproperty(object_id('dbo.Foo1'), 'isProcedure') = 1
DROP PROCEDURE dbo.Foo1
IF objectproperty(object_id('dbo.Foo2'), 'isProcedure') = 1
DROP PROCEDURE dbo.Foo2
IF objectproperty(object_id('dbo.Foo3'), 'isProcedure') = 1
DROP PROCEDURE dbo.Foo3
GO
-- Returns a simple data set
CREATE PROCEDURE Foo1
AS
SET NOCOUNT on
SELECT name
from sys.databases
GO
-- Calls Foo1, loads data into a local temp table, then returns those contents
CREATE PROCEDURE Foo2
AS
SET NOCOUNT on
CREATE TABLE #Temp (DBName sysname not null)
BEGIN TRY
INSERT #Temp (DBName)
EXECUTE Foo1
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 8164
PRINT 'Nested INSERT EXECUTE'
ELSE
PRINT 'Unanticipated err: ' + cast(ERROR_NUMBER() as varchar(10))
END CATCH
SELECT *
from #Temp
GO
-- Calls Foo2, loads data into a local temp table, then returns those contents
CREATE PROCEDURE Foo3
AS
SET NOCOUNT on
CREATE TABLE #Temp2 (DBName sysname not null)
INSERT #Temp2 (DBName)
EXECUTE Foo2
SELECT *
from #Temp2
GO
EXECUTE Foo1 will return the "base" data set.
EXECUTE Foo2 will call Foo1, load the data into a temp table, and then return the contents of that table.
EXECUTE Foo3 attempts to do the same thing as Foo2, but it calls Foo2. This results in a nested INSERT--EXECUTE error, which is detected and handled by Foo2's TRY--CATCH.
Maybe ##NESTLEVEL can help:
http://msdn.microsoft.com/en-us/library/ms187371.aspx