Sometimes there is a requirement to build out a table based on another table in your SQL Server programmatically, for example you may have a list of tables you wish to build but have no idea what the columns in the schema are.
In this post, we are going to look at how we can use dynamic SQL to build a table programmatically which you can then use in your scripts.
We are going to make use of the Information Schema, this is specific to each database, in this example, all of the table we want to target are within the same StackOverflow database. If you would like to find out more about getting information from the Information Schema we wrote a post on that here.
SELECT
COLUMN_NAME
FROM
StackOverflow.INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = 'Posts'
Here is the base query that will get the columns for the table Posts, this is no good to us in Dynamic SQL in it’s current form as it would produce our dynamic SQL for every row a column is returned on and we don’t want that.
SELECT
substring(list, 1, len(list) - 1)
FROM
(
SELECT
COLUMN_NAME + ','
FROM
StackOverflow.INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = 'Posts'
ORDER BY
ORDINAL_POSITION
FOR XML PATH('')
) AS T(list)
What we can do is use XML PATH to list the columns out to a single list, you can see that we are concatenating a comma to the end of each column inside the inner select, this is so that each column is properly separated, in the outer query we are making use of SUBSTRING to remove the comma from the end of the string, this is because a comma is not required for the last column in the create table expression.
CONCAT(QUOTENAME(UPPER(COLUMN_NAME)), + '' + UPPER(DATA_TYPE), + CASE WHEN DATA_TYPE IN ('DATE','DATETIME','INT') THEN ',' ELSE '(' + CAST(CHARACTER_MAXIMUM_LENGTH as varchar) + '),' END)
What we now need to do is create properly create our column expressions for the command, each column needs to have a data type where a data type is available, so what we are going to do is CONCAT the following columns
If -1 is provided in the CHARACTER_MAXIMUM_LENGTH this is the MAX value for that data type E.G NVARCHAR(MAX)
SELECT
substring(list, 1, len(list) - 1)
FROM
(
SELECT CONCAT(QUOTENAME(UPPER(COLUMN_NAME)), + '' + UPPER(DATA_TYPE), + CASE WHEN DATA_TYPE IN ('DATE','DATETIME','INT') THEN ',' ELSE '(' + CASE WHEN CHARACTER_MAXIMUM_LENGTH = '-1' THEN 'MAX' ELSE CAST(CHARACTER_MAXIMUM_LENGTH as varchar) END + '),' END)
FROM
StackOverflow.INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = 'Posts'
ORDER BY
ORDINAL_POSITION
FOR XML PATH('')
) AS T(list)
This should now give you something that looks like the above.
DECLARE
@destinationDatabase SYSNAME,
@destSchemaName SYSNAME,
@tableName SYSNAME,
@sql NVARCHAR(MAX)
SET @destinationDatabase = 'StackOverflow_New'
SET @destSchemaName = 'dbo'
SET @tableName = 'Posts'
DECLARE
@cStat varchar(400) = 'IF NOT EXISTS (SELECT table_name FROM ' + QUOTENAME(@destinationDatabase) + '.INFORMATION_SCHEMA.TABLES WHERE table_schema = ''' + @destSchemaName + ''' AND table_name = ''' + @tableName + ''') CREATE TABLE ' + @destSchemaName + '.' + @tableName + ' ( ',
@cEnd varchar(400) = ' )',
@column_listOut nvarchar(MAX)
Now comes the fun part, here we are declaring some variables
DECLARE @column_list nvarchar(MAX) = 'SELECT @column_list = substring(list, 1, len(list) - 1)
FROM (SELECT CONCAT(QUOTENAME(UPPER(COLUMN_NAME)), + '' '' + UPPER(DATA_TYPE), + CASE WHEN DATA_TYPE IN (''DATE'',''DATETIME'',''INT'') THEN '','' ELSE ''('' + CASE WHEN CHARACTER_MAXIMUM_LENGTH = ''-1'' THEN ''MAX'' ELSE CAST(CHARACTER_MAXIMUM_LENGTH as varchar) END + '' ),'' END)
FROM ' + @destinationDatabase + '.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = ' + '''' + @tableName + '''' + '
ORDER BY ORDINAL_POSITION
FOR XML PATH('''')) AS T(list)'
We need to put the columns that we have obtained from the table’s Information Schema into a variable, this is declared as NVARCHAR(MAX)
EXEC sp_executesql @column_list, N'@column_list nvarchar(MAX) out', @column_listOut OUT
We then need to execute the @column_list SQL to return the column list which will be populated into @column_listOut
SET @sql = @cStat + @column_listOut + @cEnd
The last thing to do is bring everything together, concatenate the cStat which is the start of the CREATE TABLE command then the column_listOutput finally the cEnd which is just the closing brackets at the end of the query.
IF NOT EXISTS (
SELECT
table_name
FROM
[StackOverflow_New].INFORMATION_SCHEMA.TABLES
WHERE
table_schema = 'dbo'
AND table_name = 'Posts'
) CREATE TABLE dbo.Posts (
[ID] INT,
[ACCEPTEDANSWERID] INT,
[ANSWERCOUNT] INT,
[BODY] NVARCHAR(MAX),
[CLOSEDDATE] DATETIME,
[COMMENTCOUNT] INT,
[COMMUNITYOWNEDDATE] DATETIME,
[CREATIONDATE] DATETIME,
[FAVORITECOUNT] INT,
[LASTACTIVITYDATE] DATETIME,
[LASTEDITDATE] DATETIME,
[LASTEDITORDISPLAYNAME] NVARCHAR(40),
[LASTEDITORUSERID] INT,
[OWNERUSERID] INT,
[PARENTID] INT,
[POSTTYPEID] INT,
[SCORE] INT,
[TAGS] NVARCHAR(150),
[TITLE] NVARCHAR(250),
[VIEWCOUNT] INT
)
If we select the final query, we should have something like this.
Here is what the entire script should look like once your done
DECLARE
@destinationDatabase SYSNAME,
@destSchemaName SYSNAME,
@tableName SYSNAME,
@sql NVARCHAR(MAX)
SET @destinationDatabase = 'StackOverflow'
SET @destSchemaName = 'dbo'
SET @tableName = 'Posts'
DECLARE
@cStat varchar(400) = 'IF NOT EXISTS (SELECT table_name FROM ' + QUOTENAME(@destinationDatabase) + '.INFORMATION_SCHEMA.TABLES WHERE table_schema = ''' + @destSchemaName + ''' AND table_name = ''' + @tableName + ''') CREATE TABLE ' + @destSchemaName + '.' + @tableName + ' ( ',
@cEnd varchar(400) = ' )',
@column_listOut nvarchar(MAX)
DECLARE @column_list nvarchar(MAX) = 'SELECT @column_list = substring(list, 1, len(list) - 1)
FROM (SELECT CONCAT(QUOTENAME(UPPER(COLUMN_NAME)), + '' '' + UPPER(DATA_TYPE), + CASE WHEN DATA_TYPE IN (''DATE'',''DATETIME'',''INT'') THEN '','' ELSE ''('' + CASE WHEN CHARACTER_MAXIMUM_LENGTH = ''-1'' THEN ''MAX'' ELSE CAST(CHARACTER_MAXIMUM_LENGTH as varchar) END + '' ),'' END)
FROM ' + @destinationDatabase + '.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = ' + '''' + @tableName + '''' + '
ORDER BY ORDINAL_POSITION
FOR XML PATH('''')) AS T(list)'
EXEC sp_executesql @column_list, N'@column_list nvarchar(MAX) out', @column_listOut OUT
SET @sql = @cStat + @column_listOut + @cEnd
Here is a solution we made to loop over some tables dynamically and create them.
DECLARE
@cnt INT,
@sql nvarchar(MAX),
@archiveConfig sysname
/* ================================================
--
-- Prepare the loop variables
--
-- ================================================ */
SET @cnt = 1
DECLARE
@mCNT nvarchar(max) = 'SELECT @mCNTOut = MAX(ID) FROM ' + QUOTENAME(@archiveConfig) + '.[dbo].Configuration'
DECLARE
@mCNTOut INT
EXEC sp_executesql @mCNT, N'@mCNTOut INT out', @mCNTOut OUT
/* ================================================
--
-- BEGIN THE WHILE LOOP
--
-- ================================================ */
WHILE @cnt <= @mCNTOut
BEGIN
DECLARE
@tableName nvarchar(max) = 'SELECT @tableNameOut = ObjectName FROM ' + QUOTENAME(@archiveConfig) + '.' + '[dbo].Configuration WHERE ID = ' + CAST(@cnt as varchar)
DECLARE
@tableNameOut SYSNAME
EXEC sp_executesql @tableName, N'@tableNameOut SYSNAME out', @tableNameOut OUT
DECLARE
@database nvarchar(max) = 'SELECT @database = DatabaseName FROM ' + QUOTENAME(@archiveConfig) + '.' + '[dbo].Configuration WHERE ID = ' + CAST(@cnt as varchar)
DECLARE
@databaseOut SYSNAME
EXEC sp_executesql @database, N'@database SYSNAME out', @databaseOut OUT
DECLARE
@archiveDatabase nvarchar(max) = 'SELECT @archiveDatabase = ArchiveDatabase FROM ' + QUOTENAME(@archiveConfig) + '.' + '[dbo].Configuration WHERE ID = ' + CAST(@cnt as varchar)
DECLARE
@archiveDatabaseOut SYSNAME
EXEC sp_executesql @archiveDatabase, N'@archiveDatabase SYSNAME out', @archiveDatabaseOut OUT
DECLARE
@schemaName nvarchar(max) = 'SELECT @schemaName = TableSchema FROM ' + QUOTENAME(@archiveConfig) + '.' + '[dbo].Configuration WHERE ID = ' + CAST(@cnt as varchar)
DECLARE
@schemaNameOut SYSNAME
EXEC sp_executesql @schemaName, N'@schemaName SYSNAME out', @schemaNameOut OUT
DECLARE
@destSchemaName nvarchar(max) = 'SELECT @destSchemaName = DestinationTableSchema FROM ' + QUOTENAME(@archiveConfig) + '.' + '[dbo].Configuration WHERE ID = ' + CAST(@cnt as varchar)
DECLARE
@destSchemaNameOut SYSNAME
EXEC sp_executesql @destSchemaName, N'@destSchemaName SYSNAME out', @destSchemaNameOut OUT
DECLARE
@keycolumn nvarchar(max) = 'SELECT @keycolumn = KeyColumn FROM ' + QUOTENAME(@archiveConfig) + '.' + '[dbo].Configuration WHERE ID = ' + CAST(@cnt as varchar)
DECLARE
@keycolumnOut SYSNAME
EXEC sp_executesql @keycolumn, N'@keycolumn SYSNAME out', @keycolumnOut OUT
/* ================================================
--
-- Create the schema
-- Check if the schema exists in the archive
--
-- ================================================ */
SET @SQL = NULL
DECLARE
@sCnt nvarchar(max) = 'SELECT @sCnt = COUNT(SCHEMA_NAME) FROM ' + @archiveDatabaseout + '.INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ''' + @destSchemaNameout + ''''
DECLARE
@sCntOut INT
SET
@sCntOut = 0
EXEC sp_executesql @sCnt, N'@sCnt int out', @sCntOut OUT
SET @sql = 'CREATE SCHEMA ' + QUOTENAME(@destSchemaNameOut)
BEGIN
BEGIN TRY
IF @sCntOut = 0
BEGIN
EXEC(@SQL)
END
END TRY
BEGIN CATCH
ROLLBACK TRAN
END CATCH
END
/* ================================================
--
-- Build out the table
-- Check if the table exists in the archive
--
-- ================================================ */
SET @SQL = NULL
DECLARE
@cStat varchar(400) = 'IF NOT EXISTS (SELECT table_name FROM ' + QUOTENAME(@archiveDatabaseout) + '.INFORMATION_SCHEMA.TABLES WHERE table_schema = ''' + @destSchemaNameout + ''' AND table_name = ''' + @tableNameout + ''') CREATE TABLE ' + @destSchemaNameout + '.' + @tableNameout + ' ( ',
@cEnd varchar(400) = ' )',
@column_listOut nvarchar(MAX)
DECLARE @column_list nvarchar(MAX) = 'SELECT @column_list = substring(list, 1, len(list) - 1)
FROM (SELECT CONCAT(QUOTENAME(UPPER(COLUMN_NAME)), + '' '' + UPPER(DATA_TYPE), + CASE WHEN DATA_TYPE IN (''DATE'',''DATETIME'',''INT'') THEN '','' ELSE ''('' + CASE WHEN CHARACTER_MAXIMUM_LENGTH = ''-1'' THEN ''MAX'' ELSE CAST(CHARACTER_MAXIMUM_LENGTH as varchar) END + '' ),'' END)
FROM ' + @databaseout + '.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = ' + '''' + @tablenameout + '''' + '
ORDER BY ORDINAL_POSITION
FOR XML PATH('''')) AS T(list)'
EXEC sp_executesql @column_list, N'@column_list nvarchar(MAX) out', @column_listOut OUT
SET @sql = @cStat + @column_listOut + @cEnd
BEGIN
BEGIN TRY
EXEC(@SQL)
END TRY
BEGIN CATCH
ROLLBACK TRAN
END CATCH
END
SET @cnt = @cnt + 1
END