Stop Script Execution if USE [database] Fails in SQL Server - 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.

Related

How can I stop SSMS from checking database existence when running a script?

I have a script that checks for the existence of the database and if it doesn't exist exits gracefully with some instructions for the user. However when the database doesn't exist, SSMS flags the USE statement as an error and generates its own error without even running my script. So in the following code, the line
SSTDB doesnot exist. Run 1MakeSSTDB.sql first. Exiting script.
never gets executed. If I comment out the USE SSTDB line, then the script works as expected. Any ideas how to get this to work? (Using SqlServer 2014.)
USE master
GO
IF NOT EXISTS (SELECT name
FROM master.dbo.sysdatabases
WHERE ('[' + name + ']' = N'SSTDB' OR name = N'SSTDB'))
BEGIN
Print 'SSTDB doesnot exist. Run 1MakeSSTDB.sql first. Exiting script.'
END
ELSE
BEGIN
Print 'exists'
USE SSTDB
END
Print 'done'
Error message from SSMS:
Msg 911, Level 16, State 1, Line 14
Database 'SSTDB' does not exist. Make sure that the name is entered correctly.
Yeah SSMS always validates the existence of objects even if you used an IF block like this.
One way to do what you want is to use dynamic sql, like this:
USE master
GO
IF NOT EXISTS (SELECT name
FROM master.dbo.sysdatabases
WHERE ('[' + name + ']' = N'SSTDB' OR name = N'SSTDB'))
BEGIN
Print 'SSTDB doesnot exist. Run 1MakeSSTDB.sql first. Exiting script.'
END
ELSE
BEGIN
Print 'exists'
DECLARE #sql varchar(max) =
'USE SSTDB;
--All code here uses SSTDB database
'
EXECUTE (#sql);
END
--All code here still uses master database
Print 'done'
You can make a fairly reliable version by doing what SSDT does:
Use SQLCMD mode
Test for SQLCMD mode in case the user forgot to enable it, using SET NOEXEC ON
Set the whole script to exit on error instead of continuing execution
This is adapted from the SSDT template code:
:on error exit
:setvar dbname SSTDB
/*
Detect SQLCMD mode and disable script execution if SQLCMD mode is not supported.
To re-enable the script after enabling SQLCMD mode, execute the following line:
SET NOEXEC OFF;
*/
:setvar __IsSqlCmdEnabled "True"
GO
IF N'$(__IsSqlCmdEnabled)' NOT LIKE N'True'
BEGIN
PRINT N'SQLCMD mode must be enabled to successfully execute this script.';
SET NOEXEC ON;
END
GO
IF NOT EXISTS (SELECT name
FROM master.dbo.sysdatabases
WHERE ( name = N'SSTDB' OR name = N'SSTDB')
) RAISERROR( 'SSTDB doesnot exist. Run 1MakeSSTDB.sql first. Exiting script.', 11, 1 )
GO
PRINT 'Starting script.'
USE $(dbname)
-- Do work
PRINT 'End script'
GO
Also - side issue - the square brackets '[' + name + ']' looks broken. The sysdatabases table does not use them, and you don't have them on the right side of that WHERE condition.

Using TRY...CATCH in Transact-SQL

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

Stop Running a Procedure in Batch File

I am creating a deployment package which will run a stored procedure that's in VSS. I am able to do this but my problem is this. I have more or less 30 databases which I need to deploy to and I only have two databases which doesn't need to get the update. With that, I included the following codes to the .sql file which the batch file runs:
IF OBJECT_ID('CONFIG') IS NULL OR DB_NAME() LIKE '%SampleDB%'
BEGIN
PRINT 'This is not a store database. Skipping this database.'
SET NOEXEC ON
RETURN
END
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE ID = OBJECT_ID(N'[dbo].[sp_ThisIsMySampleProcedure]'))
DROP PROCEDURE [dbo].[sp_ThisIsMySampleProcedure]
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS OFF
GO
CREATE PROCEDURE [dbo].[sp_ThisIsMySampleProcedure]
-- Everything else follows...
This sql code runs perfectly on SQL Server. Whenever I run it against Sample DB, it automatically skips proceeding to the checking if the procedure exists and everything after that.
But whenever I try to run it against SampleDB using my batch file, it prompts the error message but continues to run the procedure disregarding my RETURN keyword. So the message would look like this:
This is not a store database. Skipping this database.
Msg 3701, Level 11, State 5, Line 8
Cannot drop the procedure 'sp_ThisIsMySampleProcedure', because it does not exist or you do not have permission.
I understand that the error message is correct because the procedure really doesn't exist on my SampleDB. But why does it still keep running when I have the RETURN keyword there after it satisfied the condition to skip running the entire script?
Thank you!
EDIT:
Okay, I think people doesn't understand me completely. This is the scenario: My procedure (see above) works well on SQL Server Management Studio. What I mean is that whenever I try to run it on SampleDB, it gives me the message This is not a store database. Skipping this database.
But whenever I try to run my Batch-File which executes this stored procedure, I get this message on the command prompt:
This is not a store database. Skipping this database.
Msg 3701, Level 11, State 5, Line 8
Cannot drop the procedure 'sp_ThisIsMySampleProcedure', because it does not exist or you do not have permission.
Which basically means that the batch-file continued executing the whole SQL Script regardless that the first condition was satisfied.
My question is how do I make the batch-file know that whenever SQL Server throws the message This is not a store database. Skipping this database. the batch-file will immediately stop the execution of the sql file.
First. The keyword GO divides the file into separate requests. Each request separately processed by the server. RETURN exits only from first request, other requests will be run.
Try this:
select 1
RETURN
select 2
go
select 3
go
Second, SET NOEXEC ON is dangerous thing, it blocks all subsequent execution. Try this:
select 1
SET NOEXEC ON
RETURN
select 2
go
select 3
go
SET NOEXEC OFF
go
You can create procedure on all servers, but return from it in the beginig if database name like something. Or you can remove GO and create stored proc with dynamic SQL:
IF DB_NAME() like '%mydb%'
BEGIN
EXEC dbo.sp_executesql #statement = N'
CREATE PROCEDURE [dbo].[my proc]
AS
BEGIN
select 1
END'
END

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.

Equivalent of Debug.Assert for SQL Server

I'm adapting a large number of SQL Server 2005 scripts to merge two of our databases together. The scripts are run from a .cmd file which calls sqlcmd to run the scripts in order. However, I'm experiencing one or two issues where the scripts are failing.
I'd like a quick way to get a look at the state of some of the scripts where they go wrong - check variable values, the results of some queries, stuff like that.
If I was having this problem with a .NET assembly, I'd augment the code with Debug.Assert or set breakpoints where I knew failures were going to occur, which would pause program execution and allow me to check out variable values.
I was wondering, is there an equivalent in SQL Server 2005?
I've never managed to make the integrated debugging work well with SQL Server - I usually resort to "printf" debugging, using either PRINT or RAISERROR statements. RAISERROR can do some basic argument formatting, to spit the values out to the messages window. E.g. if you have a parameter #Val1, of type int, you can do:
RAISERROR('Val1 = %i',10,1,#Val1) WITH NOWAIT
(the WITH NOWAIT option causes the message to appear immediately, rather than the usual SQL behaviour of buffering messages/outputs)
This will work:
-- Assert procedure equivalent to other languages.
-- raiserror() will cause sql execution to stop and throw execep in C# code that is running this statement.
-- Usage:
-- declare #shouldBeTrue bit
-- set #shouldBeTrue = case when 1=0 then 1 else 0 end
-- exec _AT3Assert #shouldBeTrue, 'failed'
IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = '_AT3Assert' AND ROUTINE_SCHEMA = 'dbo' AND ROUTINE_TYPE = 'PROCEDURE')
EXEC ('DROP PROCEDURE dbo._AT3Assert')
GO
create procedure dbo._AT3Assert
#shouldBeTrue bit,
#errorMsg nvarchar (max)
AS
SET NOCOUNT ON;
if #shouldBeTrue is null or #shouldBeTrue <> 1
begin
raiserror (#errorMsg, -- Message text.
11, -- Severity.
1 -- State.
);
end
GO
I use batch files and check the error code like this:-
SQLCMD.EXE -b -l 30 -E -S <SERVER> -i "<SQLFILE>.sql">>"%LOG_FILE%"2>&1
IF ERRORLEVEL 1 (
ECHO. Failed.
) ELSE (
ECHO. Succeeded.
)

Resources