Multiple IF NOT EXISTS with one wrapper? - sql

I am wondering if there is any way to do this. We have a program that does data modeling for us (IDA), and it will generate at times hundreds of different alter/delete/update statements for us. The script works, except it does not meet the requirement of being able to be ran multiple times, which we sometimes need due to devops work. I haven't been able to find a way for the modeler to automatically add an IF NOT EXISTS to each statement, and so this means manually needing to add it to each one.
Is there a way to wrap the entire script in one IF NOT EXISTS? or handle this with some other kind of loop or flag I don't know about?
Example:
Currently we would have to do this:
IF NOT EXISTS (
SELECT * FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'USERS' AND COLUMN_NAME = 'LASTNAME'
)
BEGIN
ALTER TABLE DBO.USERS ADD LASTNAME CHAR(2) NULL
END;
GO
IF NOT EXISTS (
SELECT * FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'ASSETS' AND COLUMN_NAME = 'ASSETTYPE'
)
BEGIN
ALTER TABLE DBO.ASSETS ADD ASSETTYPE CHAR(2) NULL
END;
GO
IF NOT EXISTS (
SELECT * FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'ADDRESS' AND COLUMN_NAME = 'LINE3'
)
BEGIN
ALTER TABLE DBO.ADDRESS ADD LINE3 CHAR(2) NULL
END;
GO
Whereas I'd like to be able to not need to add the IF NOT EXISTS to every select, just something to indicate for the script to automatically check if it exists first.
Any thoughts? Thanks.

You could try:
IF COL_LENGTH('table_name','column_name') IS NULL
BEGIN
/* ALTER TABLE .... */
END
This was obtained from this forum: How to check if a column exists in a SQL Server table? (Got 958 up votes) or
Add a column to a table, if it does not already exist

You would need to load all the TableName and ColumnName combination into the #TEMP1
through which you need to loop it. Also you can change the data type of the column to a variable as well. Let me know if you need help adapting the code to your needs. You can scale that code to add DB_NAME and different datatypes. But you would need different versions of the code for update and delete
SELECT TABLENAME, COLNAME
INTO #TEMP1
FROM MYTABLE
DECLARE #TABLE_NAME VARCHAR(100)
DECLARE #COL_NAME VARCHAR(100)
DECLARE #SQL VARCHAR(MAX)
WHILE EXISTS (SELECT * FROM #TEMP1)
BEGIN
SELECT TOP 1 #TABLE_NAME = TABLENAME,
#COL_NAME = COLNAME
FROM #TEMP1
SET #SQL = '
IF NOT EXISTS (
SELECT * FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '+#TABLE_NAME+' AND COLUMN_NAME = '+#COL_NAME+'
)
BEGIN
ALTER TABLE DBO.'+#TABLE_NAME+' ADD '+#COL_NAME+' CHAR(2) NULL
END'
EXEC #SQL
DELETE FROM #TEMP1
WHERE TABLENAME = #TABLE_NAME AND COLNAME = #COL_NAME
END

Related

drop all SQL tables that appear in a query

I am attempting to develop a script to compare two databases to determine extra tables in one, then delete those tables. Here's my current script to locate the extraneous tables:
-- Any location where TARGET appears, replace TARGET with the database to be
-- modified
-- Any location where MODEL appears, replace MODEL with the database being
-- used as a model for comparison
select 'TARGET' as dbname, t1.table_name
from TARGET.[INFORMATION_SCHEMA].[tables] as t1
where table_name not in (select t2.table_name
from
MODEL.[INFORMATION_SCHEMA].[tables] as t2
)
That gives me the results I need, but now I need to fire out how to drop the tables. I'm afraid I'm utterly lost at this point. Wouldn't mind a way to declare variables instead of typing in the DBname repeatedly either, but not sure I can in this instance.
You could use dynamic SQL:
DECLARE #sql NVARCHAR(MAX) = N'';
select #sql += CONCAT('DROP TABLE ',QUOTENAME(t1.table_name,''''),';',CHAR(13))
from TARGET.[INFORMATION_SCHEMA].[tables] as t1
where table_name not in (select t2.table_name
from MODEL.[INFORMATION_SCHEMA].[tables] as t2);
SELECT #sql; -- debug
--EXEC(#sql);
EDIT:
MySQL(may need some nitpicking):
SET #s = (select GROUP_CONCAT('DROP TABLE ''' + t1.table_name + ''';' SEPARATOR CHAR(13))
from TARGET.INFORMATION_SCHEMA.tables as t1
where table_name not in (select t2.table_name
from MODEL.INFORMATION_SCHEMA.tables as t2));
SELECT #s; -- debug
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
My immediate thought is to assign each result in your query a Row Number and place your results into a temp table. Use a while loop starting at 1 and loop through the maximum number you have within that temp table getting the name of the table that Row Number is assigned to each loop. Use that name to delete from the database.
select 'TARGET' as dbname, t1.table_name
, ROW_NUMBER() OVER (Partition By t1.table_name) AS RowNumber
INTO #temp
from TARGET.[INFORMATION_SCHEMA].[tables] as t1
where table_name not in (select t2.table_name
from MODEL.[INFORMATION_SCHEMA].[tables] as t2)
DECLARE #counter INT = 1
DECLARE #maxNum INT
SELECT #maxNum = MAX(RowNumber) FROM #temp
While #counter <= #maxNum
BEGIN
DECLARE #tableName AS VARCHAR(MAX)
SELECT #tableName = table_name FROM #temp WHERE RowNumber = #counter
DELETE TABLE #tableName ' This may not be possible, but follow my lead
#counter += 1
END
DROP TABLE #temp
I am not sure if "DELETE TABLE #tableName" is a proper command but there is probably a very similar solution using what I have given you. I assume this is T-SQL..

How can I find potential not null columns?

I'm working with a SQL Server database which is very light on constraints and want to apply some not null constraints. Is there any way to scan all nullable columns in the database and select which ones do not contain any nulls or even better count the number of null values?
Perhaps with a little dynamic SQL
Example
Declare #SQL varchar(max) = '>>>'
Select #SQL = #SQL
+ 'Union All Select TableName='''+quotename(Table_Schema)+'.'+quotename(Table_Name)+''''
+',ColumnName='''+quotename(Column_Name)+''''
+',NullValues=count(*)'
+' From '+quotename(Table_Schema)+'.'+quotename(Table_Name)
+' Where '+quotename(Column_Name)+' is null '
From INFORMATION_SCHEMA.COLUMNS
Where Is_Nullable='YES'
Select #SQL='Select * from (' + replace(#SQL,'>>>Union All ','') + ') A Where NullValues>0'
Exec(#SQL)
Returns (for example)
TableName ColumnName NullValues
[dbo].[OD-Map] [Map-Val2] 185
[dbo].[OD-Map] [Map-Val3] 225
[dbo].[OD-Map] [Map-Val4] 225
For all table/columns with counts >= 0
...
Select #SQL=replace(#SQL,'>>>Union All ','')
Exec(#SQL)
Check this query. This was originally written by Linda Lawton
Original Article: https://www.daimto.com/sql-server-finding-columns-with-null-values
Finding columns with null values in your Database - Find Nulls Script
set nocount on
declare #columnName nvarchar(500)
declare #tableName nvarchar(500)
declare #select nvarchar(500)
declare #sql nvarchar(500)
-- check if the Temp table already exists
if OBJECT_ID('tempdb..#LocalTempTable') is null
Begin
CREATE TABLE #LocalTempTable(
TableName varchar(150),
ColumnName varchar(150))
end
else
begin
truncate table #LocalTempTable;
end
-- Build a select for each of the columns in the database. That checks for nulls
DECLARE check_cursor CURSOR FOR
select column_name, table_name, concat(' Select ''',column_name,''',''',table_name,''' from ',table_name,' where [',COLUMN_NAME,'] is null')
from INFORMATION_SCHEMA.COLUMNS
OPEN check_cursor
FETCH NEXT FROM check_cursor
INTO #columnName, #tableName,#select
WHILE ##FETCH_STATUS = 0
BEGIN
-- Insert it if there if it exists.
set #sql = 'insert into #LocalTempTable (ColumnName, TableName)' + #select
print #sql
-- Run the statment
exec( #sql)
FETCH NEXT FROM check_cursor
INTO #columnName, #tableName,#select
end
CLOSE check_cursor;
DEALLOCATE check_cursor;
SELECT TableName, ColumnName, COUNT(TableName) 'Count'
FROM #LocalTempTable
GROUP BY TableName, ColumnName
ORDER BY TableName
The query result would be something like this.
This will tell you which columns in your database are currently NULLABLE.
USE <Your_DB_Name>
GO
SELECT o.name AS Table_Name
, c.name AS Column_Name
FROM sys.objects o
INNER JOIN sys.columns c ON o.object_id = c.object_id
AND c.is_nullable = 1 /* 1 = NULL, 0 = NOT NULL */
WHERE o.type_desc = 'USER_TABLE'
AND o.type NOT IN ('PK','F','D') /* NOT Primary, Foreign of Default Key */
Yes, it is fairly straight forward. Note: if the table contains a lot of records, I suggest using SELECT TOP 1000 *, instead of SELECT *.
-- Identify records where a specific column is NOT NULL
SELECT *
FROM TableName
WHERE ColumNName IS NOT NULL
-- Identify the count of records where a specific column contains NULL
SELECT COUNT(1)
FROM TableName
WHERE ColumNName IS NULL
-- Identify all NULLable columns in a database
SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE IS_NULLABLE = 'YES'
For more information on the INFORMATION_SCHEMA views, see this: https://learn.microsoft.com/en-us/sql/relational-databases/system-information-schema-views/system-information-schema-views-transact-sql
If you want to scan all tables and columns in a given database for NULLs, then it is a two step process.
1.) Get the list of tables and columns that are NULLABLE.
-- Identify all NULLable columns in a database
SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE IS_NULLABLE = 'YES'
2.) Use Excel to create a SELECT statement to get the NULL counts for each table/column. To do this, copy and paste the query results from step 1 into EXCEL. Assuming you have copied the header row, then your data starts on row 2. In cell E2, enter the following formula.
="SELECT COUNT(1) FROM "&A2&"."&B2&"."&C2&" WHERE "&D2&" IS NULL"
Copy and paste that down the entire sheet. This will generate the SQL SELECT statement that you require. Copy the results in column E and paste into SQL Server and run it. This may take a while depending on the number of tables/columns to scan.

If dynamic table exists in the database Drop the table

I have a table which gets created Dynamically every 1st of a month. something like test201711 for November.
I want to check if test201711 exists in the DB Drop test201711
What I tried so far:
Declare #ste varchar(max)
set #ste = 'if exists (select * from INFORMATION_SCHEMA.TABLES where TABLE_NAME =''test'+CONVERT(varchar(6),getdate(),112) + ''');' + ' Drop table test'+ CONVERT(varchar(6),getdate(),112)
print #ste
exec(#ste)
When I am printing the query it is correct, but when I am executing it is giving me an error.
What is wrong with my code here??
Simply use like this. It works for me.
-- Create the table for testing
CREATE TABLE Test201711
(
id INT
)
SELECT NAME
FROM sys.tables AS t
WHERE t.name = 'Test201711'
DECLARE #TableName VARCHAR(100) = 'Test' + CONVERT(VARCHAR(6), GETDATE(), 112)
IF OBJECT_ID(#TableName) IS NOT NULL
EXEC ('DROP Table ' + #TableName)
SELECT NAME
FROM sys.tables AS t
WHERE t.name = 'Test201711'
I would guess the problem is the semicolon.
Declare #ste nvarchar(max);
set #ste = '
if exists (select *
from INFORMATION_SCHEMA.TABLES
where TABLE_NAME = ''[tablename]''
)
Drop table [tablename]
';
set #ste = replace(#ste, '[tablename]', CONVERT(varchar(6), getdate(), 112));
print #ste
exec sp_executesql #ste;
Notes:
This constructs the table name once and then uses replace() to put that into the query string.
The semicolon at the end of the if is removed.
Use sp_executesql. It is much more powerful than exec(), even if parameters are not useful in this case.
You can also do this with a try/catch block:
begin try
drop table [tablename]
end try
begin catch
-- assume table doesn't exist and ignore error
end catch;

Delete Record By ID From All Tables

I want a quick way to delete all records relating to a particular 'master' record from both the 'master' table and all associated tables. This is primarily so that I can easily enter test records and then delete all trace of them, which would take a long time manually given that there are dozens of tables where the record could be referenced.
So, in summary, in any table which contains a column called AdmissionID, I want to delete all records where the AdmissionID is equal to a value I will specify. I thought I could do this quite easily. Here's my stored procedure:
ALTER PROCEDURE [Admin].[sp_RemoveByAdmissionID]
#admissionID int
AS
--Removes records from all tables relating the AdmissionID entered
DECLARE #loop INT
DECLARE #object sysname
DECLARE #cmdstring varchar(500)
IF OBJECT_ID('tempdb..#TEMP') IS NOT NULL
BEGIN
DROP TABLE #TEMP
END
SELECT
TABLE_NAME
,ROW_NUMBER() OVER (ORDER BY TABLE_NAME) ROWID
INTO #TEMP
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
WHILE #loop <= (SELECT MAX(ROWID) FROM #TEMP)
BEGIN
SELECT #object = TABLE_NAME FROM #TEMP WHERE ROWID = #loop
IF EXISTS (SELECT * FROM SYS.COLUMNS WHERE Name = 'AdmissionID' AND Object_ID = Object_ID(#object))
BEGIN
SET #cmdstring = 'DELETE FROM ' + #object + ' WHERE AdmissionID = ' + #admissionID
Exec(#cmdstring)
END
SET #loop += 1
END
Unfortunately, executing this procedure doesn't delete any records from anywhere. I suspected the problem might be to do with my parameter not being enclosed in quotation marks in the line where I build up the cmdstring string, but even attempting to mitigate for this doesn't seem to have worked.
Any advice on where I'm going wrong, or if there's a simpler way to do this? I'd prefer not to use cascading deletes on my PK-FK relationships.
UPDATE
Following the advice of Luv I found that his query does return a very nice list of the exact commands I want to use. I thought it'd be easy from there, but I still can't make this work. The new SP I'm trying is as follows:
ALTER PROCEDURE TEST
#admissionID VARCHAR(10)
AS
DECLARE #loop INT
DECLARE #cmd VARCHAR(500)
IF OBJECT_ID('tempdb..#TEMP') IS NOT NULL
BEGIN
DROP TABLE #TEMP
END
SELECT 'DELETE FROM '+ C.TABLE_NAME +' WHERE AdmissionID = '+#admissionID AS cmd
,ROW_NUMBER() OVER (ORDER BY 'DELETE FROM '+ C.TABLE_NAME +' WHERE AdmissionID = '+#admissionID) ROWID
INTO #TEMP
FROM INFORMATION_SCHEMA.COLUMNS C
INNER JOIN INFORMATION_SCHEMA.TABLES T on C.TABLE_NAME=T.TABLE_NAME
WHERE C.COLUMN_NAME='AdmissionID'
AND T.TABLE_TYPE='BASE TABLE'
WHILE #loop <= (SELECT MAX(ROWID) FROM #TEMP)
BEGIN
SELECT #cmd = cmd
FROM #TEMP
WHERE ROWID = #loop
EXEC(#cmd)
SET #loop += 1
END
Copy the result from the Below Query and Run the Query.
declare #yourvalue varchar
set #yourvalue=''
select 'DELETE FROM '+ C.TABLE_NAME +' WHERE AdmissionID = '+#yourvalue 
from INFORMATION_SCHEMA.COLUMNS C
INNER JOIN INFORMATION_SCHEMA.TABLES T on C.TABLE_NAME=T.TABLE_NAME
where C.COLUMN_NAME='AdmissionID'
and T.TABLE_TYPE='BASE TABLE'
You can even make an SP and execute Dynamically too

Multi table Triggers SQL Server noob

I have a load of tables all with the same 2 datetime columns (lastModDate, dateAdded).
I am wondering if I can set up global Insert Update trigger for these tables to set the datetime values. Or if not, what approaches are there?
Any pointers much appreciated
I agree there is no such Global Trigger, but we can certainly reduce our efforts by creating script which will generate triggers for the tables.
Something like: http://weblogs.sqlteam.com/brettk/archive/2006/11/29/35816.aspx
No, there's no such thing as a "global" trigger or a multi-table triggers. Triggers are by design bound to a table, so if you need to have triggers on a load of tables, you need to create a load of triggers, one for each table, and deploy them. No way around that, I'm afraid.
since the code will be same and only table_name will be changed...
i think that best is to create procedure and then call this procedure from every trigger
You can use DEFAULT values for the inserts (dateAdded) and a TABLE trigger for the UPDATE.
Something like
CREATE TABLE MyTable (
ID INT,
Val VARCHAR(10),
lastModDate DATETIME DEFAULT CURRENT_TIMESTAMP,
dateAdded DATETIME DEFAULT CURRENT_TIMESTAMP
)
GO
CREATE TRIGGER MyTableUpdate ON MyTable
FOR UPDATE
AS
UPDATE MyTable
SET lastModDate = CURRENT_TIMESTAMP
FROM MyTable mt INNER JOIN
inserted i ON mt.ID = i.ID
GO
INSERT INTO MyTable (ID, Val) SELECT 1, 'A'
GO
SELECT *
FROM MyTable
GO
UPDATE MyTable
SET Val = 'B'
WHERE ID = 1
GO
SELECT *
FROM MyTable
GO
DROP TABLE MyTable
GO
Generate Triggers for all Tables
Well, I did this originally to generate triggers for all tables in a database to audit data changes, and that is simple enough, just move the entire row from the deleted table to a mirrored audit table.
But someone wanted to track activity on tables, so it's a little more simple. Here we create one log table, and any time a dml operation occurs, it is written there.
Enjoy
USE Northwind GO
CREATE TABLE LOG_TABLE (Add_dttm datetime DEFAULT (GetDate()),
TABLE_NAME sysname, Activity char(6)) GO
DECLARE #sql varchar(8000), #TABLE_NAME sysname SET NOCOUNT ON
SELECT #TABLE_NAME = MIN(TABLE_NAME) FROM INFORMATION_SCHEMA.Tables
WHILE #TABLE_NAME IS NOT NULL BEGIN SELECT #sql = 'CREATE TRIGGER
[' + #TABLE_NAME + '_Usage_TR] ON [' + #TABLE_NAME +'] ' + 'FOR
INSERT, UPDATE, DELETE AS ' + 'IF EXISTS (SELECT * FROM inserted)
AND NOT EXISTS (SELECT * FROM deleted) ' + 'INSERT INTO LOG_TABLE
(TABLE_NAME,Activity) SELECT ''' + #TABLE_NAME + ''', ''INSERT''' + '
' + 'IF EXISTS (SELECT * FROM inserted) AND EXISTS (SELECT * FROM
deleted) ' + 'INSERT INTO LOG_TABLE (TABLE_NAME,Activity) SELECT '''
+ #TABLE_NAME + ''', ''UPDATE''' + ' ' + 'IF NOT EXISTS (SELECT * FROM inserted) AND EXISTS (SELECT * FROM deleted) ' + 'INSERT INTO
LOG_TABLE (TABLE_NAME,Activity) SELECT ''' + #TABLE_NAME + ''',
''DELETE''' + ' GO' SELECT #sql EXEC(#sql) SELECT #TABLE_NAME =
MIN(TABLE_NAME) FROM INFORMATION_SCHEMA.Tables WHERE TABLE_NAME >
#TABLE_NAME END SET NOCOUNT OFF