Dynamically create tables from table parameters SQL Server - 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.

Related

Select same table name from all databases in SQL Server

I have this dynamic query, that is union from all my databases (that is start with "Db") the same table ("Tbl_SameTable").
DECLARE #tableName nvarchar(256) = 'Tbl_SameTable'
DECLARE #sql nvarchar(max) = ''
SELECT #sql = #sql + CASE WHEN #sql <> '' THEN 'UNION ALL ' ELSE '' END
+ 'SELECT * FROM [' + dbs.name + ']..[' + #tableName + '] '
FROM sys.sysdatabases dbs
WHERE left(dbs.name,2) = 'Db'
EXEC(#sql)
I want to add two things to this query:
Add a column of database name
Assign the query result to a "temp table" or "table variable"
I do not know if this is important but, the "Tbl_SameTable" is a 5 column table (int, nvarchar, int,nvarchar,nvarchar)
This is untested, however, you'll want something like this. As this is pseudo SQL, you'll need to replace {Columns} with the actual names (not *) for it to work. For the CREATE TABLE you'll need to define the data type of said columns too.
DECLARE #SchemaName sysname = N'dbo',
#TableName sysname = N'YourTable';
CREATE TABLE #Temp (DatabaseName sysname,
{Columns});
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = STUFF((SELECT #CRLF + N'UNION ALL' + #CRLF +
N'SELECT N' + QUOTENAME(d.[name],'''') + N' AS DatabaseName, {Columns}' + #CRLF +
N'FROM ' + QUOTENAME(d.[name]) + N'.' + QUOTENAME(#SchemaName) + N'.' + QUOTENAME(#TableName)
FROM sys.databases d
WHERE d.[name] LIKE 'Db%'
ORDER BY database_id
FOR XML PATH(N''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,13, N'') + N';'
--PRINT #SQL; --Your Best Friend
INSERT INTO #Temp(DatabaseName, {Columns})
EXEC sys.sp_executesql #SQL;
And, of course, if it doesn't work your best friend will be there to help you out.
How about using sp_MSforeachdb and adding the results to another table?
DROP TABLE IF EXISTS #tmp
CREATE TABLE #tmp (col1 INT, col2 INT,...)
DECLARE #command varchar(1000)
SELECT #command = 'IF ''?'' LIKE ''Db%'' BEGIN USE ?
EXEC(''INSERT INTO #tmp (col1, col2,...) SELECT col1, col2,... from Tbl_SameTable'') END'
EXEC sp_MSforeachdb #command
SELECT * FROM #tmp

Only Change Decimal's Scale

I've too many columns with DECIMAL(A,B). Some of them have column default, some of them nullable, etc.
Instead of using:
ALTER TABLE TABLE_NAME ALTER COLUMN COLUMN_NAME DECIMAL(A,C)
Is there method that simply updates the SCALE of the DECIMAL?
You can try with modify keyword
ALTER TABLE "table_name" MODIFY "column_name" "New Data Type";
Here is one way
DECLARE #sql VARCHAR(max)= ''
SET #sql = (SELECT 'ALTER TABLE TABLE_NAME ALTER COLUMN ' + COLUMN_NAME + ' DECIMAL(A,C);'
FROM information_schema.columns
WHERE table_name = 'TABLE_NAME'
AND data_type = 'DECIMAL'
AND NUMERIC_SCALE = --B
--add relevant filters
FOR xml path(''))
--print #sql
EXEC (#sql)
Don't forget to replace A and C with proper Precision and Scale
Use can achieve it by executing a dynamic SQL query. Use stuff function to concatenate each alter statement and retrieve the column names and other details from information_schema.columns. Create an other variable to hold the new numeric_scale value and take numeric_precision from the information_schema.columns itself.
Query
declare #sql as varchar(max);
declare #i as int = 3; -- change accordingly
select #sql = stuff((
select 'alter table ' + [table_name]
+ ' alter column ' + [column_name] + ' decimal('
+ cast([numeric_precision] as varchar(100)) + ',' + cast(#i as varchar(100)) + ');'
from information_schema.columns
where table_name = 'your_table_name'
and data_type = 'decimal'
for xml path('')
)
, 1, 0, ''
);
exec(#sql);

Add columns in multiple databases

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.

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

Does anybody know of a proc to turn a row into an INSERT statement?

Does anybody know of a proc or script which will generate any row into an insert statement into the same table?
Basically, I'd like to call something like
exec RowToInsertStatement 'dbo.user', 45;
And the following code would be output
insert into dbo.MyTable( FirstName, LastName, Position)
values( 'John', 'MacIntyre', 'Software Consultant');
I realize I could
insert into dbo.MyTable
select * from dbo.MyTable where id=45;
But this obviously won't work, because the ID column will complain (I hope it complains) and there's no way to just override that one column without listing all columns, and in some tables there could be hundreds.
So, does anybody know of a proc that will write this simple insert for me?
EDIT 3:04: The purpose of this is so I can make a copy of the row, so after the INSERT is generated, I can modify it into something like
insert into dbo.MyTable( FirstName, LastName, Position)
values( 'Dave', 'Smith', 'Software Consultant');
.. no obviously this contrived example is so simple it doesn't make sense, but if you have a table with 60 columns, and all you need is to change 3 or 4 values, then it starts to be a hassle.
Does that make sense?
Update
I believe the following dynamic query is what you want:
declare #tableName varchar(100), #id int, #columns varchar(max), #pk varchar(20)
set #tableName = 'MyTable'
set #pk = 'id'
set #id = 45
set #columns = stuff((select ',['+c.name+']' [text()] from sys.tables t
join sys.columns c on t.object_id = c.object_id
where t.name = #tableName and c.name <> #pk for xml path('')),1,1,'')
print 'insert into [' + #tableName + '] (' + #columns + ')
select ' + #columns + '
from [' + #tableName + ']
where ' + #pk + ' = ' + cast(#id as varchar)
Update 2
The actual thing that you wanted:
declare #tableName varchar(100), #id int, #columns nvarchar(max), #pk nvarchar(20), #columnValues nvarchar(max)
set #tableName = 'MyTable'
set #pk = 'id'
set #id = 45
set #columns = stuff((select ',['+c.name+']' [text()] from sys.tables t
join sys.columns c on t.object_id = c.object_id
where t.name = #tableName and c.name <> #pk for xml path('')),1,1,'')
set #columnValues = 'set #actualColumnValues = (select' +
stuff((select ','','''''' + cast(['+c.name+'] as varchar(max)) + '''''''' [text()]' [text()]
from sys.tables t
join sys.columns c on t.object_id = c.object_id
where t.name = #tableName and c.name <> #pk for xml path('')),1,1,'')
+ 'from [' + #tableName + ']
where ' + #pk + ' = ' + cast(#id as varchar)
+ 'for xml path(''''))'
--select #columnValues
declare #actualColumnValues nvarchar(max), #columnValuesParams nvarchar(500)
SET #columnValuesParams = N'#actualColumnValues nvarchar(max) OUTPUT';
EXECUTE sp_executesql #columnValues, #columnValuesParams, #actualColumnValues OUTPUT;
--SELECT stuff(#actualColumnValues, 1,1, '')
declare #statement nvarchar(max)
set #statement =
'insert into [' + #tableName + '] (' + #columns + ')
select ' + stuff(#actualColumnValues,1,1,'')
print #statement
What it does is this:
It generates the insert statement and then it queries the actual data from the table and generates the select statement with that data. May not work correctly for some really complex datatypes but for varchars, datetimes and ints should work like a charm.
This stored proc works great for me:
http://vyaskn.tripod.com/code.htm#inserts
Did you know that in Enterprise Manager and SQL Server Management Studio that you can, from the object browser, drag the list of columns into the text window and it will drop the names of all the columns into the text, separated by commas?

Resources