If I do the following query
EXEC spFoo
PRINT 'TEST'
and spFoo throws an error, it still executes the print statement.
However if I do
BEGIN TRY
EXEC cdb.spFoo
PRINT 'TEST'
END TRY
BEGIN CATCH
THROW;
END CATCH
It behaves as I expected and does not continue after an error.
Could someone please explain this behaviour for me? It still continues on even if I encapsulate it in a transaction. It is not just the with a print-statement but also with any other thing. My initial thought was that it was a severity problem but the severity was level 16. Is this normal T-SQL behaviour? If so, what motivates this design that contradicts every other language I have ever worked with that directly escalates the error?
I have tried and seen the same behaviour in SQL Server 2012, 2014 and 2017 across several different machines. The stored procedure in question is linked to a SQL CLR.
Severity was level 16 is a warning-level. The user is required to handle any errors - including defining when termination is required.
With your first example:
EXEC spFoo
PRINT 'TEST'
these are independent statements and, although spFoo may fail, the server will move onto the next statement. This is because severity is less than 20, the batch has not automatically been terminated.
With your second example,
BEGIN TRY
EXEC cdb.spFoo
PRINT 'TEST'
END TRY
BEGIN CATCH
THROW;
END CATCH
you have taken ownership of deciding what is associated with what.
Since one item in the TRY block failed, it would not move onto the next.
THROW always terminates a batch.
Once you called THROW, if you have any code that continues afterwards, it will not be carried out. If that's important, you can use RAISERROR to continue.
A detailed explanation of errors
Part 2 of explanations
An answer from the same person
Severity levels
One would think error handling was straightforward. Not really.
I have taken a screenshot from Erland Sommarskog's excellent article, which shows how different errors behave:
Seeing that PRINT 'TEST' is being ran after the failed exec, I guess you are in the Name=Statement-terminating, XACT_ABORT OFF, TRY-CATCH OFF cell which indicates "Aborts statement", which means the next statement still runs. Catching works and skips the next step.
You should explanation given here for TRY...CATCH
A TRY…CATCH construct catches all execution errors that have a
severity higher than 10 that do not close the database connection.
In SQL, TRY..CATCH works very differently than C#/VB etc. so based on severity it works.
If you need to see if any error occurred in previous statement, use ##ERROR.
Example, IF ##ERROR <> 0 PRINT 'TEST'
Try printing what result ##ERROR giving. It may give you any hint.
Returns 0 if the previous Transact-SQL statement encountered no errors.
(From above link) ##ERROR Returns an error number if the previous statement encountered an
error. If the error was one of the errors in the sys.messages catalog
view, then ##ERROR contains the value from the sys.messages.message_id
column for that error. You can view the text associated with an
##ERROR error number in sys.messages.
Check if any helpful information found from ##ERROR
TRY.. CATCH
Will not stop the process of the query. If you do not want to continue after catching the error, then please use RETURN. This will stop the process. Also if you are using BEGIN TRANSACTION, please use ROLLBACK before returning from the process. Otherwise it will end up in uncommitted transactions.
Related
I am not able to understand why the code is not working. There is data in vendorproducts table, however no data is retrieved after executing and it is also not printing error when I am passing in #vendor=1
ALTER PROCEDURE HW5UD1
#vendor VARCHAR(64),
#Perct DECIMAL(3,2)
AS
BEGIN TRANSACTION
BEGIN TRY
UPDATE VendorProducts
SET paidPrice = paidPrice * (1 + #perct)
WHERE vendor = #vendor
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
PRINT 'Error occurred while trying to update vendor products table'
RETURN -11001
END CATCH
COMMIT TRANSACTION
SELECT *
FROM VendorProducts
WHERE vendor = #vendor
RETURN 0
Too long for a comment so let's start. First, learn to ask smart questions. How long did it take for you to reveal any error message? And did you post the complete message or an abbreviated version?
ALTER PROCEDURE HW5UD1
Start over. This name is useless. Anyone reading your code (or the code that calls this procedure) should have some idea of what your procedure does just by reading that name.
And let's look at how you call this procedure. You first said you are calling this procedure with "#vendor=1". And a bit later you say "vendor means vendor name like DVDemporium". Something seems wrong and inconsistent here. From a schema perspective, you should have a foreign key (FK) from VendorProducts to Vendors. Do you? Do you even have a Vendors table? And is the FK column varchar or numeric? I would expect numeric since you passed a value of 1.
Next, your procedure contains a single DML statement. A single statement (update - in this case) is atomic and will either be successful or not. There is no need for a transaction. If a transaction was started before this procedure was called, you have now nested a transaction for no particular reason. Worse, you catch any error and then hide the TRUE error while throwing one of your own. You just made debugging any actual errors impossible with the hiding. Error handling is complicated in tsql - I suggest you have a read Erland's discussion.
In your CATCH clause, you use print. Nope - don't. Do not attempt to provide "output" from a procedure (or trigger) using print. These are returned to the caller in a manner that most callers don't expect and won't process (either at all or as intended by you the developer). They simply generate extra work. It can be used for debugging purposes but should not be in production code. Because you throw an error, the caller implicitly knows that "Error occurred ..." - your print message adds nothing useful.
Your last statement in the procedure simply selects rows from the table you attempted to update. Why? If the caller has all that information when calling, this only creates extra "work". If the caller does not, then why do you assume the caller wants or needs this information. Generally speaking, your procedure should do one "thing". Just update the row(s).
So perhaps this is all an exercise for learning. Unfortunately people tend to provide working code without much explanation. Sure, you could just copy that and turn it in for a grade. But you will not have learned much (if any) and that is unfortunate. Without understanding and learning you will not develop the skills you need to be an effective developer.
And lastly, there are numerous discussions of best practices for tsql coding. These you should read and start implementing.
We have a number of configuration scripts which are a bit complicated. Those scripts are using a number of available stored procedure in our database to insert the configuration data. If any script attempts to insert invalid data, stored procedures will make a call to
RAISERROR #msg, 16, 1
And then the SP will return. The SPs start their own named transactions and they will rollback/commit the named transaction, however, LB doesn't detect the error raised and it takes the execution as a successful execution. But we don't want to continue if that happens.
failOnError in the changeSets are set to true but LB still proceeds. Even in DATABASECHANGELOG that failed changeset marked as executed as if it was successful.
We also tried removing nested transactions (named transactions), no luck.
We removed the name from the transaction and just using BEGIN TRAN, the LB execution stops at the incorrect script but the problem is, LB fails to commit its own transaction and can't release the lock so it remains LOCKED.
Is there anyway to tell LB that an error happened and make it stop?
We are using Liquibase 3.5.0. and Microsoft SQL Server
=== EDIT
So after debugging Liquibase, we found two things:
When connected to MS SQL Server, if a RAISERROR occurs and there are also resultsets in the script, it won't throw exception unless we make a call to statement.getMoreResults(). The same thing happens with Sybase (we tested it with Sybase too). So we thought maybe in LB, after executing the statement we need to make a call to getMoreResults() until it throws exception or it returns false which means no error happened.
A script makes a call to a stored procedure. The stored procedure, has 'BEGIN TRAN' and at the end it either COMMIT or ROLLBACK. If a rollback occurs, it also does RAISERROR. Note that our scripts don't do any update/insert, they are only providing the data in a temp table, so we don't do transaction handling in our scripts. In this scenario, consider we added code to make a call to getMoreResults(), the exception is throws correctly but then in LB, the executor tries to database.rollback() and then later again in StandardLockService, before releasing the lock, it tries to database.rollback() which ends in exception because our SP has rolled back the transaction already. This last rollback in LB, causes the error raised by JDBC to be swallowed and as the result not only do we see the error that caused it but also the lock remained unreleased and this is the most concern because even if we re-run the script and fix it, the lock hasn't been released and we need to do it manually.
One may argue that our transaction handling is not correct but all I am trying to say is that releasing lock should not be affecting if our script is incorrect. LB should be releasing the lock and throw exception or continue if a script/changeset is not run successfully.
If anybody is facing this too: In my case I had a very complex SQL script only for MS SQL Server. This also failed to stop the execution of the LB changes if an error occures in the SQL script, anyway if I use RAISERROR or THROW.
Things I need to do, to get it to work:
remove (or comment) all places where resultsets were created (SELECT)
start the SQL script with "SET NOCOUNT ON;" to avoid results from
insert or update (... lines affected)
end the SQL script with "SET NOCOUNT OFF;" to enable LB to work properly just after executing the SQL script (set EXECTYPE)
Use the precondition https://docs.liquibase.com/concepts/advanced/preconditions.html
create another changeset and check for the execution result before proceeding to next.
We are currently on SQL 2005 at work and I am migrating an old Foxpro system to new web application backed by SQL Server. I am using TRY CATCH in T-SQL for transaction processing and it seems to be working very well. One of the other programmers at work was worried about this as he said he had heard of issues where the catch phrase did not always catch the error. I have beat the sproc to death and cannot get it to fail (miss a catch) and the only issues I have found searching around the net is that it will not return the correct error number for error numbers < 5000. Has anyone experienced any other issues with TRY CATCH in T-SQL - especially if it misses a catch? Thanks any input you may wish to provide.
TRY ... CATCH doesn't catch every possible error but the ones not caught are well documented in BOL Errors Unaffected by a TRY…CATCH Construct
TRY…CATCH constructs do not trap the
following conditions:
Warnings or informational messages that have a severity of 10 or lower.
Errors that have a severity of 20 or higher that stop the SQL Server
Database Engine task processing for
the session. If an error occurs that
has severity of 20 or higher and the
database connection is not disrupted,
TRY…CATCH will handle the error.
Attentions, such as client-interrupt requests or broken
client connections.
When the session is ended by a system administrator by using the KILL
statement.
The following types of errors are not
handled by a CATCH block when they
occur at the same level of execution
as the TRY…CATCH construct:
Compile errors, such as syntax errors, that prevent a batch from
running.
Errors that occur during statement-level recompilation, such as
object name resolution errors that
occur after compilation because of
deferred name resolution.
These errors are returned to the level
that ran the batch, stored procedure,
or trigger.
There was one case in my experience when TRY...CATCH block didn't catch the error. There was error connected with collation:
Cannot resolve the collation conflict between "Latin1_General_CI_AS" and "Latin1_General_CI_AI" in the equal to operation.
Maybe this error correspond one of the error type documented in BOL.
Errors that occur during statement-level recompilation, such as object
name resolution errors that occur after compilation because of
deferred name resolution.
TRY ... CATCH will fail to catch an error if you pass a "bad" search term to CONTAINSTABLE
For example:
DECLARE #WordList VARCHAR(800)
SET #WordList = 'crap"s'
CON
TAINSTABLE(table, *, #WordList)
The CONTAINSTABLE will give you a "syntax error", and any surrounding TRY ... CATCH does not catch this.
This is particularly nasty because the error is caused by data, not by a "real" syntax error in your code.
I'm working in SQL Server 2008. I built a big sql statement that had a try/catch. I tested it by renaming a table (in dev). The statement blew up and didn't catch the error. Try/catch in SQL Server is weak, but better than nothing. Here's a piece of my code. I can't put any more in because of my company's restrictions.
COMMIT TRAN T1;
END TRY
BEGIN CATCH
-- Save the error.
SET #ErrorNumber = ERROR_NUMBER();
SET #ErrorMessage = ERROR_MESSAGE();
SET #ErrorLine = ERROR_LINE();
-- Put GSR.dbo.BlahBlahTable back the way it was.
ROLLBACK TRAN T1;
END CATCH
-- Output a possible error message. Knowing what line the error happened at really helps with debugging.
SELECT #ErrorNumber as ErrorNumber,#ErrorMessage as ErrorMessage,#ErrorLine AS LineNumber;
I have never hit a situation where TRY...CATCH... failed. Neiteher, probably, have many of the people who read this question. This, alas, only means that if there is such a SQL bug, then we haven't seen it. The thing is, that's a pretty big "if". Believe it or not, Microsoft does put some effort into making their core software products pretty solid, and TRY...CATCH... is hardly a new concept. A quick example: In SQL 2005, I encountered a solid, demonstrable, and replicable bug while working out then-new table partitioning--which bug that had already been fixed by a patch. And TRY...CATCH... gets used a bit more frequently than table partitioning.
I'd say the burden of proof falls on your co-worker. If he "heard it somewhere", then he should try and back it up with some kind of evidence. The internet is full of proof for the old saying "just because everyone says its so, doesn't mean they're right".
I wrote a script in management studio that uses nested cursors and it inserts data in different tables.
Since many insert statemnets are executed there are many messages like
231 line(s) affected
the problem is that it seems there is a limit for these messages. So after a while they are not displayed anymore.
So if the error happens in the first "cursor loops" I see the error message, but if it happens near the end the error is not displayed, I just see a generic "Query completed with errors".
In my particular case I simply started inserted from the end (so the error came at first and I found the problem.
But how to do better?
Ideally I would like to have an option to log in the messages, just the errors, not the
231 line(s) affected
kind of messages.
Which technique do you suggest?
Add SET NOCOUNT ON at the top to suppress these messages?
Note: this doesn't affect ##ROWCOUNT if you use it
While checking some code on the web and scripts generated by SQL Server Management Studio I have noticed that some statements are ended with a semicolon.
So when should I use it?
From a SQLServerCentral.Com article by Ken Powers:
The Semicolon
The semicolon character is a statement terminator. It is a part of the ANSI SQL-92 standard, but was never used within Transact-SQL. Indeed, it was possible to code T-SQL for years without ever encountering a semicolon.
Usage
There are two situations in which you must use the semicolon. The first situation is where you use a Common Table Expression (CTE), and the CTE is not the first statement in the batch. The second is where you issue a Service Broker statement and the Service Broker statement is not the first statement in the batch.
By default, SQL statements are terminated with semicolons. You use a semicolon to terminate statements unless you've (rarely) set a new statement terminator.
If you're sending just one statement, technically you can dispense with the statement terminator; in a script, as you're sending more than one statement, you need it.
In practice, always include the terminator even if you're just sending one statement to the database.
Edit: in response to those saying statement terminators are not required by [particular RDBMS], while that may be true, they're required by the ANSI SQL Standard. In all programming, if we can adhere to a Standard without loss of functionality, we should, because then neither our code or our habits are tied to one proprietary vendor.
With some C compilers, it's possible to have main return void, even though the Standard requires main to return int. But doing so makes our code, and ourselves, less portable.
The biggest difficulty in programming effectively isn't learning new things, it's unlearning bad habits. To the extent that we can avoid acquiring bad habits in the first place, it's a win for us, for our code, and for anyone reading or using our code.
You must use it.
The practice of using a semicolon to terminate statements is standard and in fact is a requirement
in several other database platforms. SQL Server requires the semicolon only in particular
cases—but in cases where a semicolon is not required, using one doesn’t cause problems.
I strongly recommend that you adopt the practice of terminating all statements with a semicolon.
Not only will doing this improve the readability of your code, but in some cases it can
save you some grief. (When a semicolon is required and is not specified, the error message SQL
Server produces is not always very clear.)
And most important:
The SQL Server documentation indicates that not terminating T-SQL statements with
a semicolon is a deprecated feature. This means that the long-term goal is to enforce use
of the semicolon in a future version of the product. That’s one more reason to get into the
habit of terminating all of your statements, even where it’s currently not required.
Source: Microsoft SQL Server 2012 T-SQL Fundamentals by Itzik Ben-Gan.
An example of why you always must use ; are the following two queries (copied from this post):
BEGIN TRY
BEGIN TRAN
SELECT 1/0 AS CauseAnException
COMMIT
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
THROW
END CATCH
BEGIN TRY
BEGIN TRAN
SELECT 1/0 AS CauseAnException;
COMMIT
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE();
THROW
END CATCH
In SQL2008 BOL they say that in next releases semicolons will be required. Therefore, always use it.
Reference:
Transact-SQL Syntax Conventions (Transact-SQL)
Deprecated Database Engine Features in SQL Server 2008 R2 ("Features Not Supported in a Future Version of SQL Server" section, "Transact-SQL" area)
If I read this correctly, it will be a requirement to use semicolons to end TSQL statements.
http://msdn.microsoft.com/en-us/library/ms143729%28v=sql.120%29.aspx
EDIT:
I found a plug-in for SSMS 2008R2 that will format your script and add the semicolons. I think it is still in beta though...
http://www.tsqltidy.com/tsqltidySSMSAddin.aspx
EDIT:
I found an even better free tool/plugin called ApexSQL...
http://www.apexsql.com/
Personal opinion: Use them only where they are required. (See TheTXI's answer above for the required list.)
Since the compiler doesn't require them, you can put them all over, but why? The compiler won't tell you where you forgot one, so you'll end up with inconsistent use.
[This opinion is specific to SQL Server. Other databases may have more-stringent requirements. If you're writing SQL to run on multiple databases, your requirements may vary.]
tpdi stated above, "in a script, as you're sending more than one statement, you need it." That's actually not correct. You don't need them.
PRINT 'Semicolons are optional'
PRINT 'Semicolons are optional'
PRINT 'Semicolons are optional';
PRINT 'Semicolons are optional';
Output:
Semicolons are optional
Semicolons are optional
Semicolons are optional
Semicolons are optional
I still have a lot to learn about T-SQL, but in working up some code for a transaction (and basing code on examples from stackoverflow and other sites) I found a case where it seems a semicolon is required and if it is missing, the statement does not seem to execute at all and no error is raised. This doesn't seem to be covered in any of the above answers. (This was using MS SQL Server 2012.)
Once I had the transaction working the way I wanted, I decided to put a try-catch around it so if there are any errors it gets rolled back. Only after doing this, the transaction was not committed (SSMS confirms this when trying to close the window with a nice message alerting you to the fact that there is an uncommitted transaction.
So this
COMMIT TRANSACTION
outside a BEGIN TRY/END TRY block worked fine to commit the transaction, but inside the block it had to be
COMMIT TRANSACTION;
Note there is no error or warning provided and no indication that the transaction is still uncommitted until attempting to close the query tab.
Fortunately this causes such a huge problem that it is immediately obvious that there is a problem. Unfortunately since no error (syntax or otherwise) is reported it was not immediately obvious what the problem was.
Contrary-wise, ROLLBACK TRANSACTION seems to work equally well in the BEGIN CATCH block with or without a semicolon.
There may be some logic to this but it feels arbitrary and Alice-in-Wonderland-ish.
According to Transact-SQL Syntax Conventions (Transact-SQL) (MSDN)
Transact-SQL statement terminator. Although the semicolon is not required for most statements in this version of SQL Server, it will be required in a future version.
(also see #gerryLowry 's comment)
It appears that semicolons should not be used in conjunction with cursor operations: OPEN, FETCH, CLOSE and DEALLOCATE. I just wasted a couple of hours with this. I had a close look at the BOL and noticed that [;] is not shown in the syntax for these cursor statements!!
So I had:
OPEN mycursor;
and this gave me error 16916.
But:
OPEN mycursor
worked.
When using either a DISABLE or ENABLE TRIGGER statement in a batch that has other statements in it, the statement just before it must end with a semicolon. Otherwise, you'll get a syntax error. I tore my hair out with this one... And afterwards, I stumbled on this MS Connect item about the same thing. It is closed as won't fix.
see here
If you like getting random Command Timeout errors in SQLServer then leave off the semi-colon at the end of your CommandText strings.
I don't know if this is documented anywhere or if it is a bug, but it does happen and I have learnt this from bitter experience.
I have verifiable and reproducible examples using SQLServer 2008.
aka -> In practice, always include the terminator even if you're just sending one statement to the database.
Note: This answers the question as written, but not the problem as stated. Adding it here, since people will be searching for it
Semicolon is also used before WITH in recursive CTE statements:
;WITH Numbers AS
(
SELECT n = 1
UNION ALL
SELECT n + 1
FROM Numbers
WHERE n+1 <= 10
)
SELECT n
FROM Numbers
This query will generate a CTE called Numbers that consists of integers [1..10]. It is done by creating a table with the value 1 only, and then recursing until you reach 10.
Semicolons do not always work in compound SELECT statements.
Compare these two different versions of a trivial compound SELECT statement.
The code
DECLARE #Test varchar(35);
SELECT #Test=
(SELECT
(SELECT
(SELECT 'Semicolons do not always work fine.';);););
SELECT #Test Test;
returns
Msg 102, Level 15, State 1, Line 5
Incorrect syntax near ';'.
However, the code
DECLARE #Test varchar(35)
SELECT #Test=
(SELECT
(SELECT
(SELECT 'Semicolons do not always work fine.')))
SELECT #Test Test
returns
Test
-----------------------------------
Semicolons do not always work fine.
(1 row(s) affected)