Add columns in multiple databases - sql-server

I need to add 3 new columns to a table named Requirements in all the databases (of same instance). I searched in net to find sp_MSforeachdb can be used to execute same query on multiple databases, but could not find any example with a alter command.
Thanks in advance

Presuming the tables will all be in the same schema then using sp_msforeachdb
EXEC sp_msforeachdb '
IF DB_ID(''?'') > 4
BEGIN
IF EXISTS(SELECT 1 FROM [?].INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME=''Requirements'' AND TABLE_SCHEMA=''dbo''
AND TABLE_TYPE=''BASE TABLE'')
BEGIN
ALTER TABLE [?].dbo.Requirements
ADD col1 INT, col2 INT, col3 INT
END
END
'
Aaron Bertrand wrote a superior version here that you may want to use.
Another way with dynamic sql
DECLARE #sql VARCHAR(MAX) = CAST((SELECT 'IF EXISTS(SELECT 1 FROM [' + name + '].INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=''Requirements'' AND TABLE_SCHEMA=''dbo'' AND TABLE_TYPE=''BASE TABLE'')' + CHAR(10)
+ ' ALTER TABLE [' + name + '].[dbo].[Requirements] ADD Col1 INT, Col2 INT, Col3 INT' + CHAR(10) + CHAR(10)
FROM master.sys.databases
WHERE database_id > 4
FOR XML PATH('')) AS NVARCHAR(MAX))
PRINT #sql
--EXEC(#sql)
If you have to take into consideration the possibility that the table is not in dbo or a common schema then you can use sp_msforeachdb to retrieve the schema information along the lines of
CREATE TABLE ##tmp (DatabaseName SYSNAME, SchemaName SYSNAME, TableName SYSNAME)
EXEC sp_msforeachdb '
IF DB_ID(''?'') > 4
BEGIN
INSERT INTO ##tmp (DatabaseName, SchemaName, TableName)
SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME
FROM [?].INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = ''Requirements''
AND TABLE_TYPE=''BASE TABLE''
END
'
DECLARE #sql VARCHAR(MAX) = CAST((SELECT 'ALTER TABLE [' + DatabaseName + '].[' + SchemaName + '].[' + TableName + '] ADD Col1 INT, Col2 INT, Col3 INT' + CHAR(10) + CHAR(10)
FROM ##tmp
FOR XML PATH('')) AS NVARCHAR(MAX))
PRINT #sql
--EXEC(#sql)
DROP TABLE ##tmp

From mysql command line:
ALTER TABLE database1.table_name ADD column_name column-name;
ALTER TABLE database2.table_name ADD column_name column-name;
..

The following code would generate commands for each database:
select 'ALTER TABLE [' + d.name + '].[dbo].[table_name] ADD column_name column-name;' as cmd
from sys.databases d
where d.name NOT IN ( 'tempdb', 'msdb', 'master','model');
You could also write a cursor to loop over these and execute them, but I would advise looking at what you're executing and making sure you aren't updating the wrong databases.

Related

Alter tables in a schema

I am trying to set a default value to a column(Inserted_time), but first i need to check if the column exists in the tables. If the column doesn't exist, I need to add that column and give it a default value.
I am working with Sql Server Management Studio.
So far I have written this code:
IF EXISTS ( select TABLE_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_CATALOG = 'DB_COPY' and COLUMN_NAME = 'Inserted_Time')
begin
ALTER TABLE table_name ADD CONSTRAINT [Inserted_Time_Def] SET DEFAULT (sysdatetimeoffset()) FOR [Inserted_Time]
end
else
ALTER TABLE table_name ADD COLUMN [Inserted_Time] CONSTRAINT [Inserted_Time_Def] DEFAULT (sysdatetimeoffset()) WITH VALUES
Once I retrieve the tables that has the column, I need to add that table_name to the Alter command. But I am not able to do that. Can someone please tell me how to use the table_names retrieved from select statement in the alter statement?
First, you want to put all the table names in a temporary table so you can loop through it.
After, you can use a cursor to execute a command for each table name.
In my example, I only printed the command I wanted to execute. That way you can be sure the code will do what you want first.
Example :
select TABLE_NAME As TableName INTO #TablesList from INFORMATION_SCHEMA.COLUMNS where TABLE_CATALOG = 'DB_COPY' and COLUMN_NAME = 'Inserted_Time'
DECLARE #TablesCursor as CURSOR;
DECLARE #TableName as NVARCHAR(max);
DECLARE #CommandToExecute as NVARCHAR(max);
SET #TablesCursor = CURSOR FOR SELECT TableName FROM #TablesList;
OPEN #TablesCursor;
FETCH NEXT FROM #TablesCursor INTO #TableName;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #CommandToExecute = 'ALTER TABLE ' + #TableName + ' WHAT YOU WANNA DO '
PRINT #CommandToExecute
--EXEC(#CommandToExecute)
FETCH NEXT FROM #TablesCursor INTO #TableName;
END
CLOSE #TablesCursor;
DEALLOCATE #TablesCursor;
Assuming that every table is in a different schema, then you could do something like this:
DECLARE #SQL nvarchar(MAX);
SET #SQL = STUFF((SELECT NCHAR(13) + NCHAR(10) +
CASE WHEN EXISTS (SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS C
WHERE T.TABLE_SCHEMA = C.TABLE_SCHEMA
AND T.TABLE_NAME = C.TABLE_SCHEMA
AND C.COLUMN_NAME = N'Inserted_Time') THEN N'ALTER TABLE ' + QUOTENAME(T.TABLE_SCHEMA) + N'.' + QUOTENAME(T.TABLE_NAME) + N' ADD CONSTRAINT [Inserted_Time_Def] DEFAULT (sysdatetimeoffset()) FOR [Inserted_Time];'
ELSE N'ALTER TABLE ' + QUOTENAME(T.TABLE_SCHEMA) + N'.' + QUOTENAME(T.TABLE_NAME) + N' ADD COLUMN [Inserted_Time] CONSTRAINT [Inserted_Time_Def] DEFAULT (sysdatetimeoffset());'
END
FROM INFORMATION_SCHEMA.TABLES T
WHERE T.TABLE_CATALOG = N'DB_COPY'
FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,2,N'');
PRINT #SQL; --Your best friend. If more than 4,000 characters, use SELECT
EXECUTE sp_executesql #SQL;
This will very likely hugely out perform a CURSOR solution if you have a large number of schemas.

Dynamically create tables from table parameters SQL Server

I want someway to automate table creations as every day customer can add some columns ,remove some ,so my idea is to pass table name and columns into a table then use this table in stored procedure to automatically creates the table.
This is table that will hold tables structure
create table nada_test
(
table_name varchar(500),
col_name varchar(100),
col_type varchar(100)
)
Sample data:
insert into nada_test
values ('data', 'salary', 'int'), ('data', 'id', 'int'),
('data', 'job', 'varchar(100)')
Could someone show me how to achieve this?
How about that
CREATE TABLE T
(
TableName varchar(500),
ColName varchar(100),
ColType varchar(100)
);
INSERT INTO T VALUES
('data','salary','int'),
('data', 'id', 'int'),
('data', 'job', 'varchar(100)');
DECLARE #SQL NVARCHAR(MAX);
SELECT #SQL = N'CREATE TABLE Data ('+ STUFF((
SELECT ',' + ColName + ' ' + ColType
FROM T
FOR XML PATH('')
), 1, 1, '') + N' );'
FROM T;
SELECT #SQL [CreateTable];
--EXECUTE sp_executesql #SQL;
But that won't help you
What will happen to the data already exists in your table?
What if the table already exists, ok you can pass that by IF OBJECT_ID() .., but still, what will happen to the data already in your table?
You will face another problem even if you store the data in temp table because the structure of both tables is not the same even the datatypes of the columns.
As it already been mentioned, your approach is very vulnerable to SQL injections.
See example:
insert into #nada_test
values ('TestTable] (TestColumn int);SELECT * FROM sys.tables--', 'TestColumn', 'INT')
GO
DECLARE #TableName sysname, #ColumnName sysname, #Type VARCHAR(100), #SQL VARCHAR(2000)
WHILE EXISTS (SELECT TOP 1 1 FROM #nada_test)
BEGIN
SELECT TOP 1 #TableName = table_name, #ColumnName = [col_name], #Type = col_type FROM #nada_test
DELETE FROM #nada_test WHERE #TableName = table_name and #ColumnName = [col_name]
IF NOT EXISTS ( SELECT TOP 1 1 FROM sys.tables WHERE name = #TableName)
SET #SQL = 'CREATE TABLE [' + #TableName + '] ([' + #ColumnName + '] ' + #Type + ');'
ELSE IF NOT EXISTS ( SELECT TOP 1 1 FROM sys.columns WHERE name = #ColumnName AND object_id = OBJECT_ID(#TableName))
SET #SQL = 'ALTER TABLE [' + #TableName + '] ADD [' + #ColumnName + '] ' + #Type + ';'
ELSE
SET #SQL = 'PRINT ''TABLE name [' + #TableName + '] with column [' + #ColumnName + '] is already exists'';'
PRINT #SQL
EXEC (#SQL)
END
Generally we can use like
create table x as select * from y ;
using some existing table structure say y in this case
You can create a ddl trigger on your existing requirement i.e. if theres any change to this table
then fire the same query above.

MSSQL Find Matching Columns in tables with no defined relationships

Is there a script out there that will let MSSQL find columns with records that have the same data in multiple tables.
What I want to do is find the primary keys to data tables that we imported from excel spread sheets that were made from another database.
Thanks,
Chris
You're going to want to look up the SysObjects and SysColumn system tables, very handy for this sort of thing.
Here's an example that looks through all tables for the integer value 500. Note that if you want to look for a different type of column you'll need to change the xtype. It's not a full blown "Compare every column in my database against every other column" example however it should give you the basic idea and hopefully get you started.
Additionally I'm using a memory table for this example. If your database is large you will want to use a temporary table and a cursor likely.
This returns a single column recordset with the value of "Table - ColumnName = Search Value"
-- declare my search table
DECLARE #Columns TABLE (TableName varchar(50), ColumnName varchar(50))
DECLARE #Results TABLE (Results VARCHAR(255))
DECLARE #SearchData INT
SET #SearchData = 500
DECLARE #TableName VARCHAR(50)
DECLARE #ColumnName VARCHAR(50)
DECLARE #Command VARCHAR(1024)
-- Find all tables with an integer column
Insert INTO #Columns
Select sysobjects.[Name] as TableName, syscolumns.[Name] as ColumnName
from dbo.sysobjects INNER Join dbo.syscolumns ON dbo.sysobjects.id = dbo.syscolumns.id
Where sysobjects.xtype = 'U' and syscolumns.xtype = 56 Order By TableName, ColumnName
--Loop!
WHILE NOT (Select TOP 1 TableName from #Columns) IS NULL
BEGIN
Select TOP 1 #TableName = TableName, #ColumnName = ColumnName from #Columns
SET #Command = 'Select ''' + #TableName + ' - ' + #ColumnName + ' = ' + CAST(#SearchData as varchar(32)) + ''' FROM ' + #TableName + ' WHERE ' + #ColumnName + ' = ' + CAST(#SearchData as VARCHAR(32))
Insert INTO #Results
exec(#Command)
Delete from #Columns where TableName = #TableName AND ColumnName = #ColumnName
END
-- Export all results
Select * from #Results

ALTER TABLE my_table ADD #column INT

If i want to use a variable as name of the new column, is this posible in MS SQL?
Example that dont work:
ALTER TABLE my_table ADD #column INT
This worked great for me:
EXEC ('ALTER TABLE my_table ADD ' + #column + ' INT')
This is possible using dynamic sql to build your DDL and using the EXEC command to execute the string.
Declare #SQL VarChar(1000)
SELECT #SQL = 'ALTER TABLE my_table ADD ' + #column + ' INT'
Exec (#SQL)
See this article.
I will also add that the moment you venture to the land of dynamic sql, you need to take care to not expose yourself to SQL Injection attacks. Always clean up the parameters coming in.
As Philip mentions - think long and hard before doing this. The fact that it is possible does not make it a good thing...
Erland Sommarskog wrote an extensive article about using dynamic sql - The curse and blessings of dynamic SQL which I recommend reading fully.
Have a look at (EXECUTE (Transact-SQL))
CREATE TABLE MyTable(
ID INT
)
GO
SELECT * FROM MyTable
GO
DECLARE #column VARCHAR(100)
SET #column = 'MyNewCol'
EXEC('ALTER TABLE MyTable ADD ' + #column + ' INT')
GO
SELECT * FROM MyTable
GO
DROP TABLE MyTable
alter procedure sp_check_table_column
(
#field_name varchar(max),
#data_type varchar(max),
#mandatory varchar(max)
)
as
if not exists (select COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = '<table_name>' and COLUMN_NAME = #field_name)
begin
declare #sql varchar(max)
set #sql = ('ALTER TABLE <table_name> ADD ' + #field_name + ' ' + #data_type + ' ' + #mandatory)
exec (#sql)
end

Drop multiple tables with string

I have tables like lg-010-a..., lg-010-ac..., and so on, I have abc database,
I have a command window:
drop table from abc where Table_Name like 'lg-010-%'
Will this drop all the tables starting with lg-010-?
Try something like this:
declare #sql varchar(max)
declare #tablenames varchar(max)
select #tablenames = coalesce(#tablenames + ', ','') + Table_Name from INFORMATION_SCHEMA.TABLES
where Table_Name like ('lg-010-%')
set #sql = 'drop table ' + #tablenames
exec (#sql)
This queries the INFORMATION_SCHEMA.TABLES table to retrieve table names that match your criteria, then concatenates them together into a comma delimited string.
This string is than added to a 'Drop table ' statement and executed.
Drop table can take multiple comma delimited table names.
(I had originally had this query sys.tables but some research revealed that while they are currently equivalent, the Information_Schema method is quaranteed to work in future versions)
Unfortunately you can't do it like that. One way is:
SELECT 'DROP TABLE ' + name FROM sysobjects WHERE name LIKE '%lg-010-a%' AND [type] IN ('P')
This will just print out the DROP TABLE statement for each table - you can then copy and paste this output and run it. You can just put an EXECUTE in the loop instead of the PRINT, but I've done it this way so you can see what's going on/check the output first.
I had an issue where the accepted answer was not doing anything. I discovered that I had to add the prefix name of the database to the code to get it to work. If your tables are not dbo.tablename try this.
declare #sql varchar(max)
declare #tablenames varchar(max)
SELECT
#tablenames = COALESCE(#tablenames + ', ','') + 'YourDatabaseName.' + Table_Name
FROM
INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND TABLE_NAME LIKE 'AP2%'
AND (RIGHT(TABLE_NAME, 6) < 201708)
SET #sql = 'drop table ' + #tablenames
EXEC (#sql)
GO
Unfortunately you can't do it like that.
One way is:
DECLARE #TableName NVARCHAR(128)
SELECT TOP 1 #TableName = TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME LIKE 'lg-010-%'
ORDER BY TABLE_NAME ASC
WHILE (##ROWCOUNT > 0)
BEGIN
PRINT 'DROP TABLE [' + #TableName + ']'
SELECT TOP 1 #TableName = TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME LIKE 'lg-010-%'
AND TABLE_NAME > #TableName
ORDER BY TABLE_NAME ASC
END
This will just print out the DROP TABLE statement for each table - you can then copy and paste this output and run it. You can just put an EXECUTE in the loop instead of the PRINT, but I've done it this way so you can see what's going on/check the output first.
CREATE PROCEDURE dbo.drop_MsSqlTables1 #createDate smalldatetime
AS
declare #flag int =1
declare #tname varchar(50)
declare #sql varchar(max)
select row_number() over (order by name) as num, '[dbo].[' + name +']' as table_name into #temp from sys.tables where name like ('EmpInfo_%') and create_date<#createDate
declare #count int = (select count(*) from #temp)
select * from #temp
while #flag <= #count
begin
set #tname = (select table_name from #temp where num = #flag)
set #sql = 'drop table ' + #tname
exec (#sql)
set #flag = #flag+1
end

Resources