Ordering of SET OPTIONS and BEGIN END - sql-server

It's a best practice to put SET OPTIONS at the top of a stored proc. It's also a best practice to have BEGIN and END wrap the entire stored proc body. Should the SET OPTIONS be wrapped in BEGIN/END, or does it not matter?
I.e.:
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN
...
END
or
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
...
END

Related

How to put CREATE TRIGGER into TRY-CATCH block with TRANSACTION?

I'm not advanced programmer in SQL and maybe my question is silly, but I haven't found an answer in google. We have some SQL construction for implementing packages of changes:
...
BEGIN TRY
BEGIN TRANSACTION;
<User Code Is Here>
...
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
...
END CATCH;
...
How can I put the chain of CREATE TRIGGER blocks instead of <User Code Is Here> without errors:
-- Table1
CREATE TRIGGER trTable1_Dates ON dbo.Table1
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
...
SET NOCOUNT OFF;
END
GO
...
-- TableN
CREATE TRIGGER trTableN_Dates ON dbo.TableN
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
...
SET NOCOUNT OFF;
END
GO
The purpose is to create all triggers or nothing and print message in CATCH block of code if fails.
Edited
The errors are:
On first trigger's BEGIN: SQL80001: Incorrect syntax near 'BEGIN'. Expecting EXTERNAL.
After first trigger, on GO: SQL80001: Incorrect syntax near 'GO'.
END TRY: SQL80001: Incorrect syntax near 'TRY'. Expecting CONVERSATION.
END CATCH: SQL80001: Incorrect syntax near 'CATCH'. Expecting CONVERSATION.
You need to run each create trigger statement in a separate scope/batch (because it has to be the first statement in the batch). So you'll have to escape any quotes in the trigger definitions too:
BEGIN TRY
BEGIN TRANSACTION;
exec sp_executesql N'CREATE TRIGGER trTable1_Dates ON dbo.Table1
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
//An empty string in here has to be '''' to escape the quotes
SET NOCOUNT OFF;
END'
exec sp_executesql N'CREATE TRIGGER trTableN_Dates ON dbo.TableN
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
...
SET NOCOUNT OFF;
END'
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
...
END CATCH;
Transactions are orthogonal to batches and nested scopes, so the transaction covers all activity that occurs inside each EXEC too.

SET XACT_ABORT OFF if transaction terminated by SET XACT_ABORT ON

I read that many tend to SET XACT_ABORT ON at the beginning of the procedure.
CREATE PROC myProc
AS
Begin
BEGIN TRAN
SET XACT_ABORT ON
[..code1 that might throw an error..]
[..code2..]
SET XACT_ABORT OFF [?]
COMMIT TRAN
END
SET XACT_ABORT OFF [?]
Because run-time errors will terminate the procedure, SET XACT_ABORT will be left as ON. I have some questions :
Where do you set it on ? Before the CREATE PROC definition, after BEGIN or after BEGIN TRAN ? SET is at connection level so I suppose all three would not make a difference ?
When do you turn it OFF if it is left ON when errors occur ?
Setting XACT_ABORT before the CREATE PROC definition doesn't have much sense. Unlike ANSI_NULLS or QUOTED_IDENTIFIER, this option is not stored as a property of the stored procedure. I would say that you should set XACT_ABORT immediately after CREATE PROC myProc AS or after the first BEGIN, where you would also place the SET NOCOUNT ON.
I would not bother setting XACT_ABORT off. If any code portion needs to have it off (for example, inside a TRY-CATCH block that ignores some errors), it should set it off inside that specific TRY block, and set it back on at the end of END CATCH.
For more info, see: http://www.sommarskog.se/error_handling/Part1.html

Are these code snippets equivalent ('set xact_abort on' vs 'try catch rollback')?

I used to use this code snippet within my stored procedure in SQL Server:
create procedure proc_name
--declare variables
as
set nocount on
begin transaction
begin try
--do something
commit transaction
end try begin catch
rollback transaction
;throw
end catch
go
but today I got to know 'set xact_abort on' statement.
Is the following code equivalent to previous one? Are there any differences between them?
create procedure proc_name
--declare variables
as
set nocount on
set xact_abort on
begin transaction
--do something
commit transaction
go
Quoting from MS docs
A TRY…CATCH construct catches all execution errors that have a severity higher than 10 that do not close the database connection.
So, try catch does not catch all possible errors. You can use xact_abort on in addition to try catch.
try/catch give you more flexibility, i.e., you are not limited to just a rollback when something is not happy.

TSQL mutual exclusive access in a stored procedure

Several web servers access a SQL Server to get a numeric code, when this code doesn't exist, it has to be autogenerated by the SQL Server.
I need to ensure that even if two concurrent calls come in and the code doesn't exist, only one code is created and both calls return the same code. So I have to do something like this:
begin lock
if code exists
return code
else
generate code
return code
end lock
I've been reading a little about isolation levels and table locking, but I have a terrible mess with all that. First I thought that a SERIALIZABLE isolation level is what I need, but apparently it's not.
So, what would you do to accomplish a "lock" in TSQL?
Thanks a lot.
UPDATE:
I got this error when I try to set the serializable level using this as example:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE get_code
AS
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
GO
BEGIN TRANSACTION
select code from codes where granted is null;
END
GO
Msg 1018, Level 15, State 1, Procedure
get_code, Line 4 Incorrect syntax near
'SERIALIZABLE'. If this is intended as
a part of a table hint, A WITH keyword
and parenthesis are now required. See
SQL Server Books Online for proper
syntax. Msg 102, Level 15, State 1,
Line 5 Incorrect syntax near 'END'.
What does it means?
SERIALIZABLE is an isolation level for locking, not a semaphore.
It won't work in this case all you'll do is persist a read lock to the end of the TXN that doesn't prevent another process into the code reading.
You need to use sp_getapplock in Transaction mode. You can configure it to wait, bomb immediately etc: up to you
This is based on my template from Nested stored procedures containing TRY CATCH ROLLBACK pattern?
ALTER PROCEDURE get_code
AS
SET XACT_ABORT, NOCOUNT ON
DECLARE #starttrancount int, #result int;
BEGIN TRY
SELECT #starttrancount = ##TRANCOUNT
IF #starttrancount = 0 BEGIN TRANSACTION
EXEC #result = sp_getapplock 'get_code', 'Exclusive', 'Transaction', 0
IF #result < 0
RAISERROR('INFO: One at a time please`!', 16, 1);
[...Perform work...]
IF #starttrancount = 0
COMMIT TRANSACTION
ELSE
EXEC sp_releaseapplock 'get_code';
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 AND #starttrancount = 0
ROLLBACK TRANSACTION
RAISERROR [rethrow caught error using #ErrorNumber, #ErrorMessage, etc]
END CATCH
GO
This is how I did it. Given a table MetaInfo with columns MetaKey varchar(max) and MeatValueLong bigInt.
Note, in my case there goal was to exclusively get a increasing value without duplicates. I used a rowlock to create the isolation on this single operation. (Yes I know I could have used inserting and an auto-increment key, but there was an addition requirement that the caller can pass in a minimum value.)
CREATE PROCEDURE [dbo].[uspGetNextID]
(
#inID bigInt
)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION
-- This section can be removed if you want to pass in an id.
SET #inID = 0
UPDATE MetaInfo WITH (ROWLOCK)
SET MetaValueLong = CASE
WHEN ISNULL(MetaValueLong,0) > #inID THEN MetaValueLong+1
ELSE #inID+1
END
WHERE MetaKey = 'Internal-ID-Last'
SELECT MetaValueLong
FROM MetaInfo
WHERE MetaKey = 'Internal-ID-Last'
COMMIT TRANSACTION
END
yes, SET ISOLATION LEVEL SERIALIZABLE is exactly what you need. It does not permit dirty writes and dirty reads. All db-objets that are inside serializable transactions are locked so other connections will be able read/write only when first one does commit or rollback.

Basic template for Transactions in sqlserver

If I simply wrap my query with:
BEGIN TRANSACTION
COMMIT TRANSACTION
If anything fails inside of that, will it automatically rollback?
From looking at other code, they seem to check for an error, if there is an error then they do a GOTO statement which then calls ROLLBACK TRANSACTION
But that seems like allot of work, to have to check for IF( ##ERROR <> 0) after every insert/update.
I typically do something like this inside my stored procedures. It keeps things nice and safe and passes along any errors that I encounter.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- Code goes here
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
DECLARE
#ERROR_SEVERITY INT,
#ERROR_STATE INT,
#ERROR_NUMBER INT,
#ERROR_LINE INT,
#ERROR_MESSAGE NVARCHAR(4000);
SELECT
#ERROR_SEVERITY = ERROR_SEVERITY(),
#ERROR_STATE = ERROR_STATE(),
#ERROR_NUMBER = ERROR_NUMBER(),
#ERROR_LINE = ERROR_LINE(),
#ERROR_MESSAGE = ERROR_MESSAGE();
RAISERROR('Msg %d, Line %d, :%s',
#ERROR_SEVERITY,
#ERROR_STATE,
#ERROR_NUMBER,
#ERROR_LINE,
#ERROR_MESSAGE);
END CATCH
yes it is important to explicitly rollback the transaction in the case that it does not work.
I usually tell my son you only have to brush the teeth you want to keep.
In this case, you only need to rollback the commands you don't want to execute.
This will automatically rollback the transaction in case off error
SET XACT_ABORT ON
BEGIN TRANSACTION
-- CODE HERE
COMMIT TRANSACTION
For transaction control you use begin, commit and rollback. You begin a transaction by supplying BEGIN TRANSACTION. Then you put the various SQL statements you need. Then you end the transaction by issuing either a commit or rollback. COMMIT TRANSACTION will commit all the changes that you did to the database after the BEGIN statement and make them permanent, so to speak. ROLLBACK TRANSACTION will rollback all changes that you did to the database after the BEGIN statement. However, it will not change variable values.
Example:
BEGIN TRANSACTION
UPDATE table SET column = 'ABC' WHERE column = '123'
COMMIT TRANSACTION
--//column now has a value of 'ABC'
BEGIN TRANSACTION
UPDATE table SET column = 'ABC' WHERE column = '123'
ROLLBACK TRANSACTION
--//column still has it's previous value ('123') No changes were made.

Resources