Is there a SQL equivalent of return? - sql

Consider the following bit of SQL
SET DATEFORMAT ymd
SET ARITHABORT, ANSI_PADDING, ANSI_WARNINGS, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER, ANSI_NULLS, NOCOUNT ON
SET NUMERIC_ROUNDABORT, IMPLICIT_TRANSACTIONS, XACT_ABORT OFF
GO
USE master
GO
IF DB_NAME() <> N'master' SET NOEXEC ON
--
-- Create database [myDatabaseName]
--
PRINT (N'Create database [myDatabaseName]')
GO
CREATE DATABASE myDatabaseName
There is then a very long script setting up tables, views, stored procedures etc etc.
I would like to know if SQL would allow something along the likes of the following pseudo code;
If (myDatabaseName Exists)
Return // in other word abort the script here but don't throw an error
Else
//Carry on and install the database
I am aware of the Exists function in SQL but I can't seem to find anything that would simply abort the remains of the script straightaway.
This script will end up in an installation routine. In theory it should never be in an installer where the database is already present, however I would prefer not to take chances and prepare properly for a potential mistake. It is also crucial that the script does not throw any error as that will just cause the installer to roll back and install nothing.
I'm hoping that something exists in SQL that will just exit a script cleanly if particular conditions are met. By exit I really do mean exit as opposed to simply breaking out of the condition being currently evaluated.

The problem is, your client tool (SSMS, SQLCMd, etc) splits your script into batches based on the location of the GO keyword (it's a client tool thing, not SQL Server at all).
It then sends the first batch. After the first batch is complete (no matter what the outcome), it sends the second batch, then the third after the second, etc.
If you're running with sufficient permissions, a high-valued RAISERROR (severity 20-25) should stop the client tool in its tracks (because it forces the connection closed). It's not that clean though.
Another option is to try to set NOEXEC ON which still does some work with each subsequent batch (compilation) but won't run any of the code1. This allows you a slightly better recovery option if you want some batches at the end to always run, by turning it OFF again.
1Which means you still will see error messages for compilation errors for later batches which rely upon database structures that would have been created in earlier batches, if they weren't being skipped.

You can use GOTO as follows :
If (myDatabaseName Exists)
GOTO QUIT; // in other word abort the script here but don't throw an error
Else
//Carry on and install the database
QUIT:
SELECT 0;

There are several methods for that kind of request :
raiserror('Oh no a fatal error', 20, -1) with log
OR
print 'Fatal error, script will not continue!'
set noexec on
They should work and close the connection.
See here : Answer

Related

Will a stored procedure fail if one of the queries inside it fails?

Let's say I have a stored procedure with a SELECT, INSERT and UPDATE statement.
Nothing is inside a transaction block. There are no Try/Catch blocks either.
I also have XACT_ABORT set to OFF.
If the INSERT fails, is there a possibility for the UPDATE to still happen?
The reason the INSERT failed is because I passed in a null value to a column which didn't allow that. I only have access to the exception the program threw which called the stored procedure, and it doesn't have any severity levels in it as far as I can see.
Potentially. It depends on the severity level of the fail.
User code errors are normally 16.
Anything over 20 is an automatic fail.
Duplicate key blocking insert would be 14 i.e. non-fatal.
Inserting a NULL into a column which does not support it - this is counted as a user code error (16) - and consequently will not cause the batch to halt. The UPDATE will go ahead.
The other major factor would be if the batch has a configuration of XACT_ABORT to ON. This will cause any failure to abort the whole batch.
Here's some further reading:
list-of-errors-and-severity-level-in-sql-server-with-catalog-view-sysmessages
exceptionerror-handling-in-sql-server
And for the XACT_ABORT
https://www.red-gate.com/simple-talk/sql/t-sql-programming/defensive-error-handling/
https://learn.microsoft.com/en-us/sql/t-sql/statements/set-xact-abort-transact-sql
In order to understand the outcome of any of the steps in the stored procedure, someone with appropriate permissions (e.g. an admin) will need to edit the stored proc and capture the error message. This will give feedback as to the progress of the stored proc. An unstructured error (i.e. not in try/catch) code of 0 indicates success, otherwise it will contain the error code (which I think will be 515 for NULL insertion). This is non-ideal as mentioned in the comments, as it still won't cause the batch to halt, but it will warn you that there was an issue.
The most simple example:
DECLARE #errnum AS int;
-- Run the insert code
SET #errnum = ##ERROR;
PRINT 'Error code: ' + CAST(#errornum AS VARCHAR);
Error handling can be a complicated issue; it requires significant understanding of the database structure and expected incoming data.
Options can include using an intermediate step (as mentioned by HLGEM), amending the INSERT to include ISNULL / COALESCE statements to purge nulls, checking the data on the client side to remove troublesome issues etc. If you know the number of rows you are expecting to insert, the stored proc can return SET #Rows=##ROWCOUNT in the same way as SET #errnum = ##ERROR.
If you have no authority over the stored proc and no ability to persuade the admin to amend it ... there's not a great deal you can do.
If you have access to run your own queries directly against the database (instead of only through stored proc or views) then you might be able to infer the outcome by running your own query against the original data, performing the stored proc update, then re-running your query and looking for changes. If you have permission, you could also try querying the transaction log (fn_dblog) or the error log (sp_readerrorlog).

Terminating a job in SQL Server during runtime

Is there a way to make a scheduled job terminate (stop, quit, stop executing, report failure) within a stored procedure in that job?
For example, I have some check queries within a TRY block that do RAISERROR (59834,16,1) to go to the CATCH block to send an email saying that the check queries found a bad situation and the job must stop. The number 59834 was arbitrary but specific.
However, every time I test the job, even though I raised an error, the job continues to execute; it never reports a failure.
I have tried using the same RAISERROR in the CATCH block as I did in the TRY block, but that did not work either.
I want the job to stop in this once instance, it will run every day.
It sounds like the XACT_ABORT setting is not on. Without XACT_ABORT on, the execution will continue regardless of any errors encountered. More information about XACT_ABORT:
Microsoft BOL entry for XACT_ABORT
What is the benefit of using SET XACT_ABORT ON in a stored procedure?
So you might consider a SET XACT_ABORT ON at the start of the job.

Is there a 'Are you sure want to continue?' SQL Command?

We have many SQL Server scripts. But there are a few critical scripts that should only be run at certain times under certain conditions. Is there a way to protect us from ourselves with some kind of popup warning?
i.e. When these critical scripts are run, is there a command to ask the user if they want to continue?
(We've already made some rollback scripts to handle these, but it's better if they not be accidentally run at all).
No, there is no such thing.
You can write an application (windows service?) that will only run the scripts as and when they should be.
The fact that you are even asking the question shows that this is something that should be automated, the sooner the better.
You can mitigate the problem in the meanwhile by using if to test for these conditions and only execute if they are met. If this is a series of scripts you should wrap them in transactions to boot.
One work-around you can use is the following, which would require you to update a value in another table:
CREATE PROC dbo.MyProc
AS
WHILE (SELECT GoBit FROM dbo.OKToRun) = 0
BEGIN
RAISERROR('Waiting for GoBit to be set!', 0,1)
WAITFOR DELAY '00:00:10'
END
UPDATE dbo.OKtoRun
SET GoBit = 0
... DO STUFF ...
This will require you to, in another spid or session, update that table manually before it'll proceed.
This gets a lot more complicated with multiple procedures, so it will only work as a very short-term workaround.
sql is a query language. does not have ability to accept user inputs.
only thing i can think of would be to have it #variable driven. first part should update #shouldRunSecond = 1. and the second part should be wrapped in a
if #shouldRunSecond = 1
begin
...
end
second portion will be skipped if not desired.
The question is - where are these scripts located ?
If you have them as .sql file that you open every time before you run, then you can simply add some "magic numbers" before beginning of the script, that you will have to calculate every time, before you run it. In example below each time before you run your script you have to put correct date and minute into IF fondition, other wise script will not run
IF DATEPART(dd,GETDATE())!=5 or DATEPART(mi,(GETDATE()))!=43
BEGIN
RAISERROR ('You have tried occasionally to run your dangerous script !!!',16,1);
RETURN
END
--Some dangerous actions
drop database MostImportantCustomer
update Personal set Bonus=0 where UserName=SUSER_SNAME()
If your scripts reside in stored procedure - you can add some kind of "I am sure, I know what I do" parameter, where you will always pass, for example Minute multiplied by Day.
Hote it helps
I have seen batch scripts containing SQLCMD ..., so instead of running the .sql script from code or management studio, you could add a prompt in the script.
I have (on limited occasion) created an #AreYouSure parameter that must be passed into a stored procedure, then put comments next to the declaration in the stored procedure explaining the danger of running said procedure.
At least that way, no RANDOs will wander into your environment and kick off stored procedures when they don't understand the consequences. The parameter could be worked into an IF statement that checks it's value, or it doesn't really have to be used at all, but if it must be passed, then they have to at least figure out what to pass.
If you use this too much, though, others may just start passing a 'Y' or a 1 into every stored procedure without reading the comments. You could switch up the datatypes, but at some point it becomes more work to maintain this scheme than it is worth. That is why I use it on limited occasion.

Can the use or lack of use of "GO" in T-SQL scripts effect the outcome?

We have an SSIS package that ran in production on a SQL 2008 box with a 2005 compatibility setting. The package contains a SQL Task and it appears as though the SQL at the end of the script did not run.
The person who worked on that package noted before leaving the company that the package needed "GOs" between the individual SQL commands to correct the issue. however, when testing in development on SQL Server 2008 with 2008 compatibility, the package worked fine.
From what I know, GO's place commands in batches, where commands are sent to the database provider in a batch, for efficiency's sake. I am thinking that the only way that GO should effect the outcome is if there was an error in that script somewhere above it. I can imagine GO in that case, and only that case, effecting the outcome. However, we have seen no evidence of any errors logged.
Can someone suggest to me whether or not GO is even likely related to the problem? Assuming no error was encountered, my understanding of the "GO" command suggests that it use or lack of use is most likely unrelated to the problem.
The GO keyword is, as you say, a batch separator that is used by the SQL Server management tools. It's important to note, though, that the keyword itself is parsed by the client, not the server.
Depending on the version of SQL Server in question, some things do need to be placed into distinct batches, such as creating and using a database. There are also some operations that must take place at the beginning of a batch (like the use statement), so using these keywords means that you'll have to break the script up into batches.
A couple of things to keep in mind about breaking a script up into multiple batches:
When an error is encountered within a batch, execution of that batch stops. However, if your script has multiple batches, an error in one batch will only stop that batch from executing; subsequent batches will still execute
Variables declared within a batch are available to that batch only; they cannot be used in other batches
If the script is performing nothing but CRUD operations, then there's no need to break it up into multiple batches unless any of the above behavioral differences is desired.
All of your assumptions are correct.
One thing that I've experienced is that if you have a batch of statements that is a pre-requisite for another batch, you may need to separate them with a GO. One example may be if you add a column to a table and then update that column (I think...). But if it's just a series of DML queries, then the absence or presence of GO shouldn't matter.
I've noticed that if you set up any variables in the script their state (and maybe the variables themselves) are wiped after a 'GO' statement so they can't be reused. This was certainly the case on SQL Server 2000 and I presume it will be the case on 2005 and 2008 as well.
Yes, GO can affect outcome.
GO between statements will allow execution to continue if there is an error in between. For example, compare the output of these two scripts:
SELECT * FROM table_does_not_exist;
SELECT * FROM sys.objects;
...
SELECT * FROM table_does_not_exist;
GO
SELECT * FROM sys.objects;
As others identified, you may need to issue GO if you need changes applied before you work on them (e.g. a new column) but you can't persist local or table variables across GO...
Finally, note that GO is not a T-SQL keyword, it is a batch separator. This is why you can't put GO in the middle of a stored procedure, for example ... SQL Server itself has no idea what GO means.
EDIT however one answer stated that transactions cannot span batches, which I disagree with:
CREATE TABLE #foo(id INT);
GO
BEGIN TRANSACTION;
GO
INSERT #foo(id) SELECT 1;
GO
SELECT ##TRANCOUNT; -- 1
GO
COMMIT TRANSACTION;
GO
DROP TABLE #foo;
GO
SELECT ##TRANCOUNT; -- 0

Why does Microsoft SQL Server Implicitly Rollback when a CREATE statement fails?

I am working on pymssql, a python MSSQL driver. I have encountered an interesting situation that I can't seem to find documentation for. It seems that when a CREATE TABLE statement fails, the transaction it was run in is implicitly rolled back:
-- shows 0
select ##TRANCOUNT
BEGIN TRAN
-- will cause an error
INSERT INTO foobar values ('baz')
-- shows 1 as expected
select ##TRANCOUNT
-- will cause an error
CREATE TABLE badschema.t1 (
test1 CHAR(5) NOT NULL
)
-- shows 0, this is not expected
select ##TRANCOUNT
I would like to understand why this is happening and know if there are docs that describe the situation. I am going to code around this behavior in the driver, but I want to make sure that I do so for any other error types that implicitly rollback a transaction.
NOTE
I am not concerned here with typical transactional behavior. I specifically want to know why an implicit rollback is given in the case of the failed CREATE statement but not with the INSERT statement.
Here is the definitive guide to error handling in Sql Server:
http://www.sommarskog.se/error-handling-I.html
It's long, but in a good way, and it was written for Sql Server 2000 but most of it is still accurate. The part you're looking for is here:
http://www.sommarskog.se/error-handling-I.html#whathappens
In your case, the article says that Sql Server is performing a Batch Abortion, and that it will take this measure in the following situations:
Most conversion errors, for instance conversion of non-numeric string to a numeric value.
Superfluous parameter to a parameterless stored procedure.
Exceeding the maximum nesting-level of stored procedures, triggers and functions.
Being selected as a deadlock victim.
Mismatch in number of columns in INSERT-EXEC.
Running out of space for data file or transaction log.
There's a bit more to it than this, so make sure to read the entire section.
It is often, but not always, the point of a transaction to rollback the entire thing if any part of it fails:
http://www.firstsql.com/tutor5.htm
One of the most common reasons to use transactions is when you need the action to be atomic:
An atomic operation in computer
science refers to a set of operations
that can be combined so that they
appear to the rest of the system to be
a single operation with only two
possible outcomes: success or failure.
en.wikipedia.org/wiki/Atomic_(computer_science)
It's probably not documented, because, if I understand your example correctly, it is assumed you intended that functionality by beginning a transaction with BEGIN TRAN
If you run as one batch (which I did first time), the transaction stays open because the INSERT aborts the batch and CREATE TABLE is not run. Only if you run line-by-line does the transaction get rolled back
You can also generate an implicit rollback for the INSERT by setting SET XACT_ABORT ON.
My guess (just had a light bulb moment as I typed the sentence above) is that CREATE TABLE uses SET XACT_ABORT ON internalls = implicit rollback in practice
Some more stuff from me on SO about SET XACT_ABORT (we use it in all our code because it releases locks and rolls back TXNs on client CommandTimeout)