Using TRY...CATCH in Transact-SQL - sql-server

I have the written the T-SQL code below. I want to put it in a SQL Server TRY...CATCH block. However, because I must execute some statements before proceeding with another statement, I am using the GO keyword and this makes the code crash with out executing the code in the CATCH block. It just crashes as if there was no CATCH block. If I remove the GOs in the code and the code crashes, the execution jumps to the CATCH block which is the desired behavior.
Any ideas on what I can do?
BEGIN TRY
RESTORE FILELISTONLY
FROM DISK = 'D:\Folder1\Database1.bak'
GO
ALTER DATABASE BusinessData
SET SINGLE_USER WITH
ROLLBACK IMMEDIATE
ALTER DATABASE BusinessData
SET RECOVERY Simple
RESTORE DATABASE BusinessData
FROM DISK = 'D:\Folder1\Database1.bak'
WITH MOVE 'BusinessData' TO 'C:\MyDATA
\BusinessData.mdf',
MOVE 'BusinessData_log' TO 'C:\MyDATA
\BusinessData_log.ldf'
ALTER DATABASE BusinessData SET MULTI_USER
GO
IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = N'SERVER1\user1')
CREATE LOGIN [SERVER1\user1] FROM WINDOWS WITH DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=
[us_english]
GO
USE [ProjectServer_Authentication]
GO
IF NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = N'SERVER1\user1')
CREATE USER [SERVER1\user1] FOR LOGIN [SERVER1\user1] WITH DEFAULT_SCHEMA=[dbo]
GO
EXEC sp_addrolemember 'db_owner',N'SERVER1\user1'
GO
USE [BusinessData]
IF NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = N'SERVER1\user1')
CREATE USER [SERVER1\user1] FOR LOGIN [SERVER1\user1] WITH DEFAULT_SCHEMA=[dbo]
GO
EXEC sp_addrolemember 'db_owner',N'SERVER1\user1'
GO
END TRY
BEGIN CATCH
USE msdb
GO
EXEC sp_send_dbmail #profile_name='My Mail Profile',
#recipients='myemailaccount#mydomain.org',
#subject='Refresh Error',
#body='Email body'
END CATCH

I think GO is the Problem as
GO is not a Transact-SQL statement;
it is a command recognized by the sqlcmd and osql utilities and SQL Server Management Studio Code editor.
SQL Server utilities interpret GO as a signal that they should send the current batch of Transact-SQL statements to an instance of SQL Server. The current batch of statements is composed of all statements entered since the last GO, or since the start of the ad hoc session or script if this is the first GO.
With every GO you start a new Statement, wich means your begin Try and your End Try are in 2 different Statements and therefore not working

Related

Automate SNAPSHOT isolation level for a particular SQL user

I need to have some users that are systematiically in the TRANSACTION ISOLATION LEVEL SNAPSHOT when they logs to the database.
I tried to use a LOGON trigger to set the user session to that isolation level when the ORIGINAL_LOGIN() was the user I wanted to promote... But this does not works...
I tried to see if it was possible to configure this in the ODBC connection strings.... but I found nothing
This is my actual code to test...
First part the source database :
CREATE DATABASE DB_SOURCE;
GO
ALTER DATABASE DB_SOURCE
SET ALLOW_SNAPSHOT_ISOLATION ON;
GO
USE DB_SOURCE;
GO
CREATE TABLE T (C INT);
GO
INSERT INTO T VALUES (1), (2), (3);
GO`enter code here`
Second part, the login and the user :
USE master;
GO
CREATE LOGIN CNX_USER_TEST
WITH PASSWORD = 'foo',
DEFAULT_DATABASE = DB_SOURCE;
GO
USE DB_SOURCE;
GO
CREATE USER USR_USER_TEST
FROM LOGIN CNX_USER_TEST;
GO
GRANT SELECT TO USR_USER_TEST;
GO
Third part, the logon trigger :
USE master;
GO
CREATE TRIGGER E_LOGON
ON ALL SERVER WITH EXECUTE AS N'sa'
FOR LOGON
AS
BEGIN
IF ORIGINAL_LOGIN()= N'CNX_USER_TEST'
BEGIN
COMMIT;
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
END;
END;
Fourth, the test...
In SSMS window 1 :
BEGIN TRAN ;
UPDATE T SET C = C + 1;
In SSMS in another window connected with CNX_USER_TEST :
SELECT * FROM T;
Blocked !

Read-only clause for a query

I am wanting to allow users, via an ASP page, to run a query on my database, however I want this query to be restricted to read-only.
I will try to detect as much as I can in the ASP itself (detect words drop, update, delete etc.) and not run the query in these scenarios, but, is there a way I can just append a flag to the query when my ASP submits it to have it execute as read-only regardless of its content?
I realise that, if this is possible, update/delete/drop statements will error - this is fine.
I also want to avoid setting the whole DB or even table as read-only.
I also don't want to replicate my database in any form.
Thankyou for your help.
Dan
Your query should be dynamic. Main problem - sql injection.
I recommend this way:
create database role [DYNAMIC_SQL]. Give required select permissions on tables to [DYNAMIC_SQL] role.
create database user without login [dynamic_sql_proxy]. Map with [DYNAMIC_SQL] role.
create procedure [dynamic_sql__call] with hardcoded execution rights as [dynamic_sql_proxy]. Pass your custom query to this procedure.
Scripts:
(1)
USE [your_db]
GO
CREATE ROLE [DYNAMIC_SQL]
GO
GRANT SELECT ON [dbo].[your_table] TO [DYNAMIC_SQL] AS [dbo]
***
(2)
CREATE USER [dynamic_sql_proxy] WITHOUT LOGIN WITH DEFAULT_SCHEMA = [dbo]
ALTER ROLE [DYNAMIC_SQL] ADD MEMBER [dynamic_sql_proxy]
(3)
CREATE PROCEDURE [dbo].[dynamic_sql__call]
#err_code INT OUTPUT
,#err_msg NVARCHAR(4000) OUTPUT
,#sql NVARCHAR(MAX)
WITH EXECUTE AS 'dynamic_sql_proxy'
AS
BEGIN
SET #err_code = 0;
SET #err_msg = SPACE(0);
BEGIN TRY
EXEC(#sql);
END TRY
BEGIN CATCH
PRINT 'Please handle your error';
END CATCH;
RETURN #err_code;
END;
Thankyou Allan S. Hansen, I used the username/password method to good effect.

Stop Script Execution if USE [database] Fails in SQL Server

Typically in a SQL Server script I will have a USE [database] statement at the start. This would be for a Schema Table creation script for example.
The script is assuming that the database already exists. However, to prevent accidentally running the script against a master database, I just want the script to terminate execution.
So error checking and try...catch does not work.
Error Check
USE [MYDATABASE]
IF ##ERROR <> 0
BEGIN
RAISERROR ('Cannot find database so skipping script creation', 1, 1);
GOTO AbortScript
END
...
AbortScript:
PRINT 'Aborted!'
Try Catch
BEGIN TRY
USE [MYDATABASE]
PRINT 'xxxx'
END TRY
BEGIN CATCH
PRINT 'OOps Errored'
END CATCH
Can you trap these errors? I am currently using SQL Server 2008 R2.
Check if the database exists first:
IF (NOT EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE name = 'mydatabase'))
BEGIN
RAISERROR ('Cannot find database so skipping script creation', 1, 1);
GOTO AbortScript
END;
USE [MYDATABASE]
I've been trying to abort a large SQL script that includes many batches (marked with "GO"). Unfortunately you can't use a GOTO block, IF block, or TRY-CATCH to skip multiple batches, but you can turn off command execution, which has the same effect.
IF EXISTS (SELECT 1 FROM sys.tables WHERE name = 'MyTable')
SET NOEXEC ON
(Don't forget to SET NOEXEC OFF at the end of your script)
Detailed reference here.

Dropping SQL Server database: handling concurrent connections

During my integration tests, I try to drop database using:
USE master
ALTER DATABASE TestXyz SET SINGLE_USER WITH ROLLBACK IMMEDIATE
DROP DATABASE TestXyz
However, quite often (given the number of tests) one of the application background processes manages to get between SET SINGLE_USER and DROP DATABASE, which makes it single user of the database and breaks the DROP.
I can not use RESTRICTED_USER, as the application currently has db_owner permission (due to a large amount of legacy code, some of which requires it, so it will not be changed just for the tests).
I can not use OFFLINE as it does not delete database files from the disk.
How would you solve this problem?
OK plan b... iterate a drop of connections and rename the DB to get it away from the applications domain. Then drop it. To handle iterating through connections a try catch on the rename will hopefully allow it to run until it is able to drop the connection. Example code below creates a DB TestDB; renames it to testdb2 in the while loop before dropping it after the loop has succeeded.
-- Setup a scratch Db for testing
create database testdb
go
use testdb
while exists (select name from sys.databases where name = 'testdb')
Begin
DECLARE #DbName nvarchar(50) SET #DbName = N'testdb'
DECLARE #EXECSQL varchar(max) SET #EXECSQL = ''
SELECT #EXECSQL = #EXECSQL + 'Kill ' + Convert(varchar, SPId) + ';'
FROM MASTER..SysProcesses
WHERE DBId = DB_ID(#DbName) AND SPId <> ##SPId
EXEC(#EXECSQL)
Begin try
EXEC sp_renamedb 'testdb', 'testdb2'
end try
Begin Catch
print 'failed to rename'
End Catch
end
drop database testdb2
Try this once:
Stop application services and run your query.
Stop application services and restart SQL Server Services and then run your query.
I have finally solved it using the following approach:
ALTER LOGIN MyAppUser DISABLE
ALTER DATABASE TestXyz SET SINGLE_USER WITH ROLLBACK IMMEDIATE
DROP DATABASE TestXyz
ALTER LOGIN MyAppUser ENABLE
Since I can use different login for test database management process, this allows me to block application from accessing the DB. (The reason for SINGLE_USER here is just to kick already connected users. I haven't checked if ALTER LOGIN already does that, but I assume it does not).
Alternative option is to delete MyAppUser from the database before dropping it, however I thought about it only now and do not have code for it.

How can I ensure that I reset login permissions to MULTI_USER upon SQL script failure?

I have the following SQL script:
USE MyDatabase
ALTER DATABASE MyDatabase SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
BEGIN TRANSACTION
-- Write to tables here
COMMIT TRANSACTION
ALTER DATABASE MyDatabase SET MULTI_USER
GO
I want to make sure that my database is not accessed while the script is running, which is why I am setting the database to SINGLE_USER at the top.
Now, if anything inside the transaction fails (e.g. syntax error) I will never reset the database back to MULTI_USER which means it will be stuck in single user mode permanently (not what I want).
Is there a way to ensure we always go back to MULTI_USER, even on failure?
I guess I'm looking for the equivalent of a finally block in SQL, but from what I have read this doesn't exist. Is there another way?
It's worth noting that I cannot move the ALTER DATABASE commands into the transaction, as this is not permitted (since they are committed immediately).
You should be able to use a TRY CATCH to make sure you always go back to MULTI_USER.
You will just need to move the command to switch back to MULTI_USER right after the TRY CATCH block, since FINALLY is not supported in SQL Server.
I ran a quick test with the following SQL and it worked as expected in SQL Server 2005. Just make sure you ROLLBACK the transaction in the CATCH block. I used SELECT 1/0 to force the code into the CATCH block. For debugging purposes, I added the SELECT user_access_desc ... statements to show that the database was indeed switching from single user back to multi user mode.
USE MyDatabase
ALTER DATABASE MyDatabase SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
SELECT user_access_desc from sys.databases WHERE Name = 'MyDatabase'
DECLARE #errNum AS INT
DECLARE #errMsg AS VARCHAR(MAX)
SET #errNum = 0
BEGIN TRY
BEGIN TRANSACTION
SELECT 1/0
COMMIT TRANSACTION
END TRY
BEGIN CATCH
SELECT #errNum = ERROR_NUMBER(), #errMsg = ERROR_MESSAGE()
ROLLBACK TRANSACTION
END CATCH
IF #errNum <> 0
SELECT 'An error occurred: ' + CAST(#errNum AS VARCHAR) + '- ' + #errMsg
ALTER DATABASE MyDatabase SET MULTI_USER
GO
SELECT user_access_desc from sys.databases WHERE Name = 'MyDatabase'
EDIT
In my original answer, I had the ALTER ... MULTI_USER statement inside both the TRY and the CATCH block, but that was unnecessary so I moved the statement to right after the TRY CATCH block. I also added some error reporting. Some things to watch out for with this approach are:
If you do any error handling or reporting, you'll need to make sure that SQL doesn't error. I would probably write the #errNum and #errMsg values to a table (wrapped in a TRY CATCH), switch back to MULTI_USER mode, and then perform whatever other error handling measures that are required, as the local variables will go out of scope after the GO statement.
Some errors are unaffected by TRY CATCH. The documentation I linked to above does list out what those conditions are.

Resources