Running a conditional sql script dependent on existence of database - sql

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;

Related

SQL Server behave differently to stop Execution in different types of error

I have list of Alter Table statements:
BEGIN
ALTER TABLE TABLE1 ALTER COLUMN AA INT -- Error Here
ALTER TABLE TABLE1 ALTER COLUMN BB INT
PRINT('CONTINUE AFTER ERROR')
END
After error its stopped execution and skipped other statements.
In output it shows only 1 error.
But in 2nd case where i have a list of DROP INDEX Statements
BEGIN
DROP INDEX TABLE1.INDEX1 -- Error Here
DROP INDEX TABLE2.INDEX2
PRINT('CONTINUE AFTER ERROR')
END
Here after error, it continues execution and prints error log and the text 'CONTINUE AFTER ERROR'.
Why this difference ?
The difference in behavior is because the first batch of ALTER TABLE statements is a compilation error whereas the second batch of DROP INDEX statements is a runtime error.
When a compilation error occurs on a batch, no code executes and only the compilation error is returned. Also, since no code executes with a compilation error, the error cannot even be caught with structured error handling:
BEGIN TRY
ALTER TABLE TABLE1 ALTER COLUMN AA INT -- Error Here
ALTER TABLE TABLE1 ALTER COLUMN BB INT
PRINT('CONTINUE AFTER ERROR')
END TRY
BEGIN CATCH
PRINT 'CAUGHT ERROR';
END CATCH;
Msg 4902, Level 16, State 1, Line 4 Cannot find the object "TABLE1"
because it does not exist or you do not have permissions.
When compilation is successful and a runtime error happens, subsequent statements in the same batch may or may not execute after an error depending the error severity and XACT_ABORT setting.
Most likely because the ALTER TABLE statements actually touch the data in the given tables. Removing the index does not have that impact, so I guess SQL decides it is OK to continue with the next statement.

Why is my code not returning an error message when ##ROWCOUNT=0?

I created the below sp. When I execute it and insert an ID that does not exist, I want an error message to appear. However, the error message does not get printed... Does anyone can help me understand what I'm doing wrong please? Thank you
create procedure campaign_data
#campaign_ID bigint
as
begin
select campaignname,totalspend,clicks,impressions,totalspend/clicks as cpc
from DSP_RawData_Archive
where #campaign_ID=Campaign_ID
end
exec campaign_data 2
if ##ROWCOUNT=0 print 'Campaign_ID does not exist'
The PRINT statement will be displayed under the Messages tab on the results window if you are using SSMS and doesn't affect control flow.
Try throwing an error using RAISERROR with high enough severity (the middle parameter), which can affect control flow (jumps to CATCH or stops execution, for example).
IF ##ROWCOUNT = 0
RAISERROR('Campaign_ID does not exist', 15, 1)
The problem here is the scope of your ##ROWCOUNT. ##ROWCOUNT returns the number of effected rows of the last executed statement in a batch, in this case that from exec campain_data 2 and not that from the first select. Another example:
BEGIN TRAN
SELECT 0 WHERE 1 = 0
PRINT ##ROWCOUNT -- Displays 0
EXEC dbo.DoSomething -- Say this procedure returns 2 rows...
PRINT ##ROWCOUNT -- Displays 2
COMMIT
Another thing here is that you maybe want to show a proper error message in your scenario (instead of a simple PRINTed line). You can do achieve this using either
RAISERROR('Display my custom error message via RAISERROR!',16,1)
or
THROW 50000,'Display my custom error message via THROW!',1
Helpful article:
http://sqlhints.com/2013/06/30/differences-between-raiserror-and-throw-in-sql-server/
Try :
create procedure campaign_data
#campaign_ID bigint
as
begin
select campaignname,totalspend,clicks,impressions,totalspend/clicks as cpc
from DSP_RawData_Archive
where #campaign_ID=Campaign_ID
end
exec campaign_data 2
IF (SELECT ##rowcount) = 0
SELECT 'Campaign_ID does not exist'
I tried your stored procedure in Sequel Server Management Studio, and I saw the right response in message tab.
Where are you trying it?

Try Catch Can't handle alter table

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;

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.

SQL Statement Termination using RAISERROR

(SQL 2005)
Is it possible for a raiserror to terminate a stored proc.
For example, in a large system we've got a value that wasn't expected being entered into a specific column. In an update trigger if you write:
if exists (select * from inserted where testcol = 7)
begin
raiseerror('My Custom Error', 16, 1)
end
the update information is still applied.
however if you run
if exists (select * from inserted where testcol = 7)
begin
select 1/0
end
a divide by 0 error is thrown that actually terminates the update.
is there any way i can do this with a raiseerror so i can get custom error messages back?
In a trigger, issue a ROLLBACK, RAISERROR and then RETURN.
see Error Handling in SQL Server - Trigger Context by Erland Sommarskog
Can you not just add a CHECK constraint to the column to prevent it from being inserted in the first place?
ALTER TABLE YourTable ADD CONSTRAINT CK_No_Nasties
CHECK (testcol <> 7)
Alternatively you could start a transaction in your insert sproc (if you have one) and roll it back if an error occurs. This can be implemented with TRY, CATCH in SQL Server 2005 and avoids having to use a trigger.
Begin try
#temp number
#temp=1/0
End try
Begin catch
#errormsg varchar(100)
#errormsg=error_massage()
Raiseerror(#errormsg,16,1)
End catch
You should check for valid data prior to performing the update.
IF (#testvalue = 7)
RAISERROR("Invalid value.", 16, 1);
ELSE
UPDATE...