Try Catch Can't handle alter table - sql

Why I this can't handle the alter table?
Begin Try
alter table nyork add [Qtr] varchar(20)
End Try
Begin Catch
Print 'Column already exist'
End Catch'

Because one of them is a transact sql command (the try catch) and the other is a DDL statement.
You'd probably do better off querying to see if the column exists before doing the alter statement.
To do this with MSSQL, see How to check if a column exists in a SQL Server table?
Specifically for your case,
IF COL_LENGTH('nyork', 'Qtr') IS NULL
BEGIN
alter table nyork
add [Qtr] varchar(20)
END

You cannot do such a thing. TRY...CATCH can only handle runtime errors. Your script will run as long as the column does not exist but not when it is already there. The name resolution of the objects is done at compile time. Therefore SQL Server will always recognize the missing column before it starts any execution. For that reason, you can't also do such a thing with dynamic SQL.

You can wrap it with exec('alter goes here'). Then catch will catch

As #Marcus Vinicius Pompeu said: "You'd probably do better off querying to see if the column exists before doing the alter statement."
But, if you really want to use TRY...CATCH with DDL. There is two way for do this.
Use dynamic SQL in the TRY block - It has been answered here.
Use stored procedure in the TRY block - Based on documentation.
Example based on your code:
DROP PROCEDURE IF EXISTS dbo.sp_my_proc
GO
CREATE PROCEDURE dbo.sp_my_proc
AS
--Your original code here:
ALTER TABLE nyork ADD [Qtr] VARCHAR(20)
GO
BEGIN TRY
EXECUTE dbo.sp_my_proc
--Optional
DROP PROCEDURE IF EXISTS dbo.sp_my_proc
END TRY
BEGIN CATCH
--Catch your error here
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_MESSAGE() AS ErrorMessage;
END CATCH;

Related

Running a conditional sql script dependent on existence of database

I'm trying to automate a sql script to add a column to existing databases on a given system. The script will be run on a system with one database or a different one. The script should not cause error in any of the cases:
1. One of the two databases exists
2. Both databases exist
3. Neither database exists.
I tried this but I keep getting error when the database 'DatabaseName' does not exist. I want the script to be ignored in that case.
IF DB_ID('DatabaseName') IS NOT NULL
BEGIN
PRINT 'DatabaseName Exists'
IF COL_LENGTH('[DatabaseName].[dbo].[Table]', 'NewColumn') IS NULL
BEGIN
ALTER TABLE [DatabaseName].[dbo].[Table]
ADD [NewColumn] bit NOT NULL DEFAULT 0;
PRINT 'Modified DatabaseName.Table'
END
END
ELSE
BEGIN
PRINT 'DatabaseName Does Not Exist'
-- therefore do nothing
END
This gives me the error:
Msg 2702, Level 16, State 2, Line 6
Database 'DatabaseName' does not exist.
I had also tried different variations of
Use 'DatabaseName'
with the same or similar errors because they are not existent.
To clarify: it is okay if it does not exist. I am just trying to handle the error gracefully so an installation continues
Use dynamic SQL. The problem is occurring during the compilation phase of the code. Dynamic SQL will "hide" the reference to the database from the initial compilation phase.
For instance, in SQL Server, this looks like:
IF COL_LENGTH('[DatabaseName].[dbo].[Table]', 'NewColumn') IS NULL
BEGIN
exec sp_executesql N'
ALTER TABLE [DatabaseName].[dbo].[Table]
ADD [NewColumn] bit NOT NULL DEFAULT 0';
PRINT 'Modified DatabaseName.Table'
END
You can use a try catch block:
BEGIN TRY
-- Generate divide-by-zero error.
SELECT 1/0;
END TRY
BEGIN CATCH
-- Execute code if error
END CATCH;

SQL Server TRY CATCH FINALLY

I have a scenario where I need something similar to .NET's try-catch-finally block.
On my try, I will CREATE a #temp table, INSERT data to it & process other data sets based on #temp.
On CATCH then RAISERROR.
Is it possible to have a FINALLY block to DROP #temp?
Below is the pseudo code:
BEGIN TRY
CREATE TABLE #temp
(
--columns
)
--Process data with other data sets
END TRY
BEGIN CATCH
EXECUTE usp_getErrorMessage
END CATCH
BEGIN FINALLY
DROP TABLE #temp
END FINALLY
While not exactly the same as FINALLY, the T-SQL version of Try-Catch does allow that code that needs execute after both the Try and Catch blocks can occur after the end of the END CATCH statement.
Using the question code as an example:
BEGIN TRY
CREATE TABLE #temp
(
--columns
)
--Process data with other data sets
END TRY
BEGIN CATCH
EXECUTE usp_getErrorMessage
END CATCH;
IF OBJECT_ID('tempdb..#temp') IS NOT NULL -- Check for table existence
DROP TABLE #temp;
The DROP TABLE command will execute whether the Try or Catch execute.
See: BOL Try...Catch
Instead of creating a table you could just declare a table variable (which will automatically go away when the query ends).
BEGIN TRY
DECLARE #temp TABLE
(
--columns
)
--do stuff
END TRY
BEGIN CATCH
--do other stuff
END CATCH
there is no FINALLY equivalent.
an alternative may be table variables but is not exactly the same and must be evaluated on a case by case basis. there is a SO question with details very useful to make an informed choice.
with table variables you don't need to clean up like you do with temp tables
"FINALLY" is often, but not always, functionally identical to having the "final" code follow the TRY/CATCH (without a formal "FINALLY" block). Where it is different is the case where something in the TRY/CATCH blocks could cause execution to end, such as a return statement.
For example, a pattern I've used is to open a cursor, then have the cursor-using code in the TRY block, with the cursor close/deallocate following the TRY/CATCH block. This works fine if the blocks won't exit the code being executed. However, if the TRY CATCH block does, for example, a RETURN (which sounds like a bad idea), if there were a FINALLY block, it would get executed, but with the "final" code placed after the TRY / CATCH, as T-SQL requires, should those code blocks cause the execution to end, that final code won't be called, potentially leaving an inconsistent state.
So, while very often you can just put the code after the TRY/CATCH, it will be a problem if anything in those blocks could terminate without falling through to the cleanup code.
Local temp tables (e.g., "#Temp") are automatically dropped when the SQL connection ends. It's good practice to include an explicit DROP command anyway, but if it doesn't execute, the table will still be dropped.
If you must ensure that a DROP executes as soon as possible, you'll have to repeat the DROP command in a CATCH clause, since there's no FINALLY:
-- create temp table;
BEGIN TRY
-- use temp table;
-- drop temp table;
END TRY
BEGIN CATCH
-- drop temp table;
THROW; -- rethrow the error
END CATCH
Table variables are an alternative: they're dropped when the variable goes out of scope. However, table variables do not support statistics, so if the table variable is large and used in multiple queries, it may not perform as well as a temp table.
using custom error number to indicate there no real error, just final code?
-- create temp table;
BEGIN TRY
-- use temp table;
THROW 50555;
END TRY
BEGIN CATCH
-- drop temp table;
IF ERROR_NUMBER() <> 50555
THROW; -- rethrow the error
END CATCH
The correct answer in this case is the one proposed by #Dave Bennett; after the TRY/CATCH block check for the existence of the table and drop it.
But what if you are raising an exception out of your CATCH and you need to do some "FINALLY" type processing?
Could it be as simple as setting a variable in the CATCH and checking it after you fall out of the CATCH?
DECLARE #is_error BIT = 0;
BEGIN TRY
--Process data with other data sets
END TRY
BEGIN CATCH
-- Your exception handling code here
SET #is_error = 1;
END CATCH
-- Your "FINALLY" code here.
-- Then Check if you need to RAISERROR
IF #is_error = 0
BEGIN
-- Your success code
END
ELSE
BEGIN
-- Your fail code
-- RAISERROR
END;
With T-SQL, code placed after the TRY-CATCH block will get executed. So the answer to the question is simply to add a DROP TABLE IF EXISTS #temp right after the END CATCH like so:
BEGIN TRY
CREATE TABLE #temp(
--columns
)
--Process data with other data sets
END TRY
BEGIN CATCH
EXECUTE usp_getErrorMessage
END CATCH
-- Anything under this line will execute regardless of if the code reached the CATCH block or not.
DROP TABLE IF EXISTS #temp; -- Works in SQL Server 2016+, for older versions see here: https://stackoverflow.com/a/31171650/15596537

TSQL Alter procedure if it exists - syntax error

I'm trying to understand what specifically is happening in SQL that means the following syntax is not allowed (and I'm finding it hard to search for):
IF (OBJECT_ID('..sp_cake', 'P') is not null)
ALTER PROC sp_cake
as
select 1
I would expect the ALTER to be valid because T-SQL is wrapping it up in its own BEGIN-END block and nothing bad could happen with the rest of the script block.
This is what T-SQL is doing, wrapping everything up and keeping it cleanly separated:
IF (OBJECT_ID('..sp_cake', 'P') is not null)
BEGIN
ALTER PROC [dbo].[sp_cake]
as
BEGIN
select 1
END
END
And these examples would be the simplest expression of what I think I'm doing (and these are syntactically correct)
IF (OBJECT_ID('..sp_cake', 'P') is not null)
select 1
IF (OBJECT_ID('..sp_cake', 'P') is null)
select 1 -- i.e. this works and 1 is the output
I have read that the CREATE or ALTER must be the first statement in a query block, but I don't understand why.
I know that I can get around this problem by either:
creating a dummy sproc and then altering it outside of an IF block, or;
creating a string of the entire sproc and executing it as a statement;
but I don't see why it is not valid to test for existence and then ALTER.
I'm not sure what you mean by this statement:
I have read that the CREATE or ALTER must be the first statement in a
query block, but I don't understand why.
You are correct that these need to be the first statements in a batch. That is a property of the T-SQL language -- not something whose cause needs to be understood but something that you need to know to use the language properly. Typically, the structure to do what you want in SQL Server is:
IF (OBJECT_ID('..sp_cake', 'P') is not null)
BEGIN
DROP PROCEDURE dbo.sp_code
END;
GO
CREATE PROC [dbo].[sp_cake] as
BEGIN
select 1
END;
I do agree that it would be nice to have a create procedure if not exists or create or alter procedure. Getting that functionality requires lobbying Microsoft.

Create Database and Table Conditionally

I'm trying to write a small script to create a database if it doesn't exist, and create a table for that database if the table doesn't exist. What I have is this:
IF (db_id('db') is null) BEGIN
print 'Must create the database!';
CREATE DATABASE db;
END
USE db;
IF (object_id('test_table', 'U') is null) BEGIN
print 'Must create the table!';
CREATE TABLE test_table (
id int
);
END
I'm getting a strange error with this:
Database 'db' does not exist. Make sure that the name is entered correctly.
I'm guessing that it's parsing the script before running it and finding that 'db' doesn't exist, so it can't use it.
There must be a solution to this. Any help is appreciated.
SOLVED!
I realised 5 minutes after posting that the GO keyword solves the problem. Here is the fixed code:
IF (db_id('db') is null) BEGIN
print 'Must create the database!'
CREATE DATABASE db;
END
GO
USE db
IF (object_id('test_table', 'U') is null) BEGIN
print 'Must create the table!';
CREATE TABLE test_table (
id int
);
END
Sorry for wasting everyone's time.
SQL statements are parsed as one batch unless you break them apart. In SQL Server, you can use GO to do this. In both MySQL and SQL Server, you can use BEGIN and END.
If you want to commit the separate blocks to the database in different instances you can use BEGIN TRANS / COMMIT TRANS and START TRANSACTION / COMMIT for SQL Server and MySQL, respectively.
Something along the lines of Check if table exists in SQL Server would probably work (With a slight change)
IF (NOT EXISTS (SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'TheSchema'
AND TABLE_NAME = 'TheTable'))
BEGIN
--Do Stuff
END
I might suggest using the built-in SQL syntax -
CREATE DATABASE name IF NOT EXISTS;
And subsequently
CREATE TABLE name(definition) IF NOT EXISTS;

DDL exception caught on table but not on column

Assuming that the table MyTable already exists, Why does the "In catch" is printed on the first statement, but not on the second?
It seems to be catching errors on duplicate table names but not on duplicate column names
First:
BEGIN TRY
BEGIN TRANSACTION
CREATE TABLE MyTable (id INT)
COMMIT TRANSACTION
END TRY
BEGIN CATCH
PRINT 'in Catch'
ROLLBACK TRANSACTION
END CATCH
Second:
BEGIN TRY
BEGIN TRANSACTION
ALTER TABLE MyTable ADD id INT
COMMIT TRANSACTION
END TRY
BEGIN CATCH
PRINT 'in Catch'
ROLLBACK TRANSACTION
END CATCH
The difference is that the alter table statement generates a compile time error, not a runtime error, so the catch block is never executed as the batch itself is not executed.
You can check this by using the display estimated execution plan button in SQL server management studio, you will see for the CREATE TABLE statement, an estimated plan is displayed, whereas for the ALTER TABLE statement, the error is thrown before SQL server can even generate a plan as it cannot compile the batch.
EDIT - EXPLANATION:
This is to do with the way deferred name resolution works in SQL server, if you are creating an object, SQL server does not check that the object already exists until runtime. However if you reference columns in an object that does exist, the columns etc that you reference must be correct or the statement will fail to compile.
An example of this is with stored procedures, say you have the following table:
create table t1
(
id int
)
then you create a stored procedure like this:
create procedure p1
as
begin
select * from t2
end
It will work as deferred name resolution does not require the object to exist when the procedure is created, but it will fail if it is executed
If, however, you create the procedure like this:
create procedure p2
as
begin
select id2 from t1
end
The procedure will fail to be created as you have referenced an object that does exist, so deferred name resolution rules no longer apply.