Equivalent of Debug.Assert for SQL Server - 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.
)

Related

TSQL - Only execute a line if run manually (not in job)

Can I make an IF statement that only executes when run manually from SSMS?
I have a SQL Server job that executes TSQL code. That TSQL code is maintained in a separate .sql text file. When it needs to be edited, I edit the text file and copy&paste the final results into the job.
This normally works very well but there is one critical line that is only used for testing (it sets a variable to a specific value). How can I guarantee that line only executes when run manually?
Is there something like If ManualExecution() then Blah?
IF APP_NAME() LIKE 'Microsoft SQL Server Management Studio%'
BEGIN
PRINT 'Running inside SSMS'
END;
If you use SQL Agent to run the job, it's app name should be SQLAgent - TSQL JobStep (Job 0x... : Step ...). If you use some other software, just make sure that it doesn't set its Application Name to "Microsoft SQL Server Management Studio"...
You can use the following code to get the SQL Server Agent JobId of the current process (otherwise NULL):
declare #JobId as UniqueIdentifier;
begin try
-- The executed statement will result in a syntax error if not in a SQL Server Agent job.
execute sp_executesql
#stmt = N'select #JobId = Cast( $(ESCAPE_NONE(JOBID)) as UniqueIdentifier );',
#params = N'#JobId UniqueIdentifier output',
#JobId = #JobId output;
end try
begin catch
if ##Error != 102 -- 102 = Syntax error.
select ##Error; -- Handle unexpected errors here.
end catch
select #JobId as JobId; -- NULL if not running as a SQL Server Agent job.
Note that the JobId can be used to access additional information about the current job in dbo.sysjobs.

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.

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.

SQL Server: How to abort a series of batches in Query Analyzer?

i have a series of T-SQL statements separated by the special Query Analyzer batch separator keyword:
GO
If one batch fails, i need Query Analyzer to not try subsequent batches - i want it to stop processing the series of batches.
For example:
PRINT 'This runs'
go
SELECT 0/0, 'This causes an error'
go
PRINT 'This should not run'
go
Output:
This runs
Server: Msg 8134, Level 16, State 1, Line 2
Divide by zero error encountered.
This should not run
Possible?
Update
An example of this in real use might be:
sp_rename 'Shelby', 'Kirsten'
go
DROP VIEW PeekAView
go
CREATE VIEW PeekAViewAS
SELECT * FROM Kirsten
go
Here is how I'd do it:
PRINT 'This runs'
go
SELECT 0/0, 'This causes an error'
go
if (##error <> 0)
Begin
set nocount on
set noexec on
End
GO
PRINT 'This should not run'
go
set noexec off
set nocount off
GO
The "noexec" mode puts SSMS is a state where it just compiles the T-SQL and doesn't actually execute it. It is similar to accidentally pressing the Parse toolbar button (Ctrl+F5) instead of Execute (F5).
Don't forget to turn noexec back off at the end of your script. Otherwise users are going to get confused by permanent "Command(s) completed successfully." messages.
I use the check against ##error in the subsequent batch instead of using TRY CATCH blocks. Using ##error in the next batch will catch compile errors, like "table doesn't exist".
In addition to the noexec mode, I also toggle the nocount mode. With noexec mode on and nocount off, your queries will still report a message "(0 rows(s) affected)". The message always reports zero rows, because you're in noexec mode. However, turning nocount on suppresses these messages.
Also note that if running SQL Server 2005 the command you are skipping might still give error messages if it references a table that doesn't exist and the command if the first command in the batch. Forcing the command to be the second command in the batch with a bogus Print statement can suppress this. See MS Bug #569263 for more details.
You can activate the "Query, SQLCMD Mode" menu option and place the following at the beginning of the script:
:on error exit
This will stop execution when an error occurs, even if there are subsequent batches.
Just make sure that you don't accidentally run the script without SQLCMD mode on because you will get the typical behavior where errors are ignored.
When I need to do this, I issue a RAISERROR of severity 20. This, or higher, will kill the current connection, and prevent subsequent "GO batches" from executing. Yes, it can be awkward, but it does the job.
Create a temporary table; and update it after each step (if successful); and then check the success of the previous step by validating against the table.
create table #ScriptChecker (SuccessfullStep int)
-- Do Step One
Insert into #ScriptChecker
Select 1
-- Step 2
If exists (select * from #ScriptChecker where SuccessfullStep = 1)
-- Do Step 2 ...
based on #u07ch idea, but only insert on failure...
create table #test (failure int)
if not exists (select * from #test)
BEGIN
print 'one' --sql here
END
go
if not exists (select * from #test)
BEGIN
print 'two'--sql here
END
go
if not exists (select * from #test)
BEGIN
print 'three' ---SQL SERVER 2000 version
--error--
SELECT 0/0, 'This causes an error'
IF ##ERROR!=0
BEGIN
insert into #test values (1)
PRINT 'ERROR'
END
end
go
if not exists (select * from #test)
BEGIN
print 'three' ---SQL SERVER 2005/2008 version
BEGIN TRY
--error--
SELECT 0/0, 'This causes an error'
END TRY
BEGIN CATCH
insert into #test values (1)
PRINT 'ERROR'
END CATCH
END
go
if not exists (select * from #test)
BEGIN
--sql here
print 'four'
END
go
output 2000:
one
two
three
----------- --------------------
Msg 8134, Level 16, State 1, Line 7
Divide by zero error encountered.
(1 row(s) affected)
ERROR
output 2005/2008:
one
two
three
----------- --------------------
(0 row(s) affected)
(1 row(s) affected)
ERROR
Erland Sommarskog in the microsoft.public.sqlserver.programming group had a very good idea:
In a change script such as the one you
posted, you need to be defensive, and
start each batch with IF ##trancount >
0.
Using
IF ##trancount > 0
is much cleaner.

How can I attach to and debug a running SQL Server stored procedure?

I am investigating an odd error from a SQL Server 2005 stored procedure which I cannot reproduce by calling it directly from Management Studio.
Therefore I'd like to be able to:
set a breakpoint in the stored procedure
wait for the procedure to be called externally and the breakpoint hit
see the values of the passed-in parameters
step through the stored procedure
Is this possible, and if so how?
You could try the "server explorer" from visual studio but the sqlserver needs to be configured to allow debugging. Here is some info: https://web.archive.org/web/20211020102846/https://www.4guysfromrolla.com/articles/051607-1.aspx. But I think that you first should try Profiler like Eppz say. :)
Use SQL Profiler to view what is happening when the procedure gets called and what params are passed in.
If you can't step through the code, here are two ways:
#1 concatenate a string, and insert into a log file.
Declare a variable like:
DECLARE #Loginfo varchar(7500)
append debug info into it as you progress through the code:
SET #LogInfo=ISNULL(#LogInfo)+'#01> #x='+COALESCE(CONVERT(varchar(10),#x),'NULL')
..
SET #LogInfo=ISNULL(#LogInfo)+'#02>'
..
SET #LogInfo=ISNULL(#LogInfo)+'#03> top loop'
at all exit points (after any rollbacks) add:
INSERT INTO YourLogTable VALUES (... ,#LogInfo )
depending on the transaction usage and you error in particular, you may be able to just insert many times with no fear of rollback, so you will need change this to your situation.
#2 write to a text file on the sq server
this may not be an option because it uses the very insecure xp_cmdshell stored procedure. However, if you can use that and if transactions from the calling app are causing a problem try creating this stored procedure:
CREATE PROC log_message
#Message varchar(255)
,#FileName varchar(100)
,#OverWrite char(1) = 'N'
AS
/*
Log messages to server side text files from stored procedures/triggers/sql scripts
Input parameters:
Message - message to put in the log file
FileName - path and name of the file to log the message into
OverWrite - 'Y'=overwrite entire file with current message
'N'=append current message onto end of file
Return code:
0 - everything was fine
1 - there was an error
NOTE: the command to log the message can not be longer than 255 characters,
as a result the message and file name should be less than 245 chars combined
Example: EXEC log_message 'Duplicates found','C:\logfile.txt', 'N'
append the "Duplicates found" message onto the server's "C:\logfile.txt" file
*/
BEGIN
SET NOCOUNT ON
DECLARE #ExecuteString VARCHAR(255) --command string can only be 255 chars long
DECLARE #ReturnValue int
--build command string
SET #ExecuteString = RTRIM('echo ' + COALESCE(LTRIM(#Message),'-')
+ CASE WHEN (#OverWrite = 'Y') THEN ' > ' ELSE ' >> ' END + RTRIM(#FileName))
--run command string
EXEC #ReturnValue=master..xp_cmdshell #ExecuteString
--IF #ReturnValue!=0
-- PRINT 'command failed, return value='+CONVERT(varchar(40),#ReturnValue)
RETURN #ReturnValue
SET NOCOUNT OFF
END
sprinkle calls to this procedure these through your code write what you need into a file on the server
Update: I am not sure if there is a way to attach to a running stored proc. You can use profiler to get a real time trace of the statements getting executed (SP:StmtStarting). Also check out Apex SQL Debug which seems to have more capabilities and is available as an Add-in to Management Studio.
If you have Visual Studio, it is easy to debug:
Debugging Stored Procedures in Visual Studio 2005
More answers here:
What’s your favored method for debugging MS SQL stored procedures?
I would use a combination of SQL Profiler and print statements inside of your SQL statement. Not sure of a way to step thru line by line but using profiler in combination with print and select statements (if using temp tables) to view their contents as the proc runs will quickly shed light on what's happening.

Resources