Problem
Do you recognize the need to copy a table structure in MS SQL Server, for temporary or permanent usage?
Most of you will know the famous
INTO [dbo].[some unexisting table]
FROM [dbo].[some existing table]
But this approach has some limitations: Computed columns change into regular columns, and primary and foreign keys are not copied. Sometimes you just need more than this.
Solution
So I have made a script to copy a SQL server table structure with foreign keys but without indexes.
This script handles user defined types and computed columns gracefully.
My credits go to Aaron Bertand (see his post on mssqltips). I used his script for copying the foreign keys and added my own parts to it (download it here).
--\
---) Author: Hans Michiels
---) Script to copy a sql server table structure with foreign keys but without indexes.
---) This script handles user defined types and computed columns gracefully.
--/
/*
(c) Copyright 2016 - hansmichiels.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see
*/
--\
---) VARIABLES DECLARATIONS
--/
DECLARE @CrLf NVARCHAR(2)
DECLARE @Indent NVARCHAR(2)
DECLARE @nsql NVARCHAR(MAX)
DECLARE @SimulationMode CHAR(1)
DECLARE @SourceSchemaAndTable NVARCHAR(260)
DECLARE @TargetSchemaAndTable NVARCHAR(260)
DECLARE @FkNameSuffix NVARCHAR(128)
DECLARE @TableOptions NVARCHAR(500)
--\
---) CONFIGURATION: set the source and target schema/tablename here, and some other settings.
--/
SELECT
@SimulationMode = 'Y' -- Use Y if you only want the SQL statement in the output window without it being executed.
, @SourceSchemaAndTable = '[dbo].[Order]'
, @TargetSchemaAndTable = '[dbo].[OrderCopy]'
, @TableOptions = ' WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]'
, @FkNameSuffix = '_' + REPLACE(CAST(NEWID() AS VARCHAR(40)), '-', '') -- A Guid is added to the foreign key name to make it unique.
, @CrLf = CHAR(13) + CHAR(10)
, @Indent = SPACE(2)
-- For 'min' script use this (in case sql is near 4000 characters):
-- , @CrLf = ' '
-- , @Indent = ''
--\
---) BUILD SQL FOR CLONING TABLE
--/
SELECT @nsql
= ISNULL(@nsql, '')
+ CASE col_sequence WHEN 1 THEN
@CrLf + 'IF OBJECT_ID(N''' + @TargetSchemaAndTable + ''', ''U'') IS NOT NULL DROP TABLE ' + @TargetSchemaAndTable + ';'
+ @CrLf + 'CREATE TABLE ' + @TargetSchemaAndTable + @CrLf + @Indent + '( ' ELSE @CrLf + @Indent + ', ' END
+ [definition]
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY tb.object_id ORDER BY tb.object_id, col.column_id) AS col_sequence
, QUOTENAME(col.name) + ' '
+ COALESCE(
'AS ' + cmp.definition + CASE ISNULL(cmp.is_persisted, 0) WHEN 1 THEN ' PERSISTED ' ELSE '' END,
CASE
WHEN col.system_type_id != col.user_type_id THEN QUOTENAME(usr_tp.schema_name) + '.' + QUOTENAME(usr_tp.name)
ELSE
QUOTENAME(sys_tp.name) +
CASE
WHEN sys_tp.name IN ('char', 'varchar', 'binary', 'varbinary') THEN '(' + CONVERT(VARCHAR, CASE col.max_length WHEN -1 THEN 'max' ELSE CAST(col.max_length AS varchar(10)) END) + ')'
WHEN sys_tp.name IN ('nchar', 'nvarchar') THEN '(' + CONVERT(VARCHAR, CASE col.max_length WHEN -1 THEN 'max' ELSE CAST(col.max_length/2 AS varchar(10)) END) + ')'
WHEN sys_tp.name IN ('decimal', 'numeric') THEN '(' + CAST(col.precision AS VARCHAR) + ',' + CAST(col.scale AS VARCHAR) + ')'
WHEN sys_tp.name IN ('datetime2') THEN '(' + CAST(col.scale AS VARCHAR) + ')'
ELSE ''
END
END
)
+ CASE col.is_nullable
WHEN 0 THEN ' NOT NULL'
ELSE CASE WHEN cmp.definition IS NULL THEN ' NULL' ELSE ' ' END
END AS [definition]
FROM sys.tables tb
JOIN sys.schemas sch
ON sch.schema_id = tb.schema_id
JOIN sys.columns col
ON col.object_id = tb.object_id
JOIN sys.types sys_tp
ON col.system_type_id = sys_tp.system_type_id
AND col.system_type_id = sys_tp.user_type_id
LEFT JOIN
(
SELECT tp.*, sch.name AS [schema_name]
FROM sys.types tp
JOIN sys.schemas sch
ON tp.schema_id = sch.schema_id
) usr_tp
ON col.system_type_id = usr_tp.system_type_id
AND col.user_type_id = usr_tp.user_type_id
LEFT JOIN sys.computed_columns cmp
ON cmp.object_id = tb.object_id
AND cmp.column_id = col.column_id
WHERE tb.object_id = OBJECT_ID(@SourceSchemaAndTable, 'U')
) subqry
;
SELECT @nsql
= ISNULL(@nsql, '')
+ CASE col_sequence
WHEN 1 THEN @CrLf + ', PRIMARY KEY ' + CASE is_clustered_index WHEN 1 THEN 'CLUSTERED' ELSE 'NONCLUSTERED' END
+ @CrLf + @Indent + '( '
ELSE @CrLf + @Indent + ', '
END
+ QUOTENAME(pk_cols.column_name)
+ CASE is_descending_key
WHEN 1 THEN ' DESC'
ELSE ' ASC'
END
FROM (
SELECT TOP 2147483647 sch.name as schema_name, tb.name as table_name, col.name as column_name
, ROW_NUMBER() OVER (PARTITION BY tb.object_id ORDER BY tb.object_id, ic.key_ordinal) AS col_sequence
, ic.is_descending_key
, CASE WHEN idx.index_id = 1 THEN 1 ELSE 0 END AS [is_clustered_index]
FROM sys.tables tb
JOIN sys.schemas sch
ON sch.schema_id = tb.schema_id
JOIN sys.indexes idx
ON idx.is_primary_key = 1
AND idx.object_id = tb.object_id
JOIN sys.index_columns ic
ON is_included_column = 0
AND ic.object_id = tb.object_id
AND ic.index_id = idx.index_id
JOIN sys.columns col
ON col.column_id = ic.column_id
AND col.object_id = tb.object_id
WHERE tb.object_id = OBJECT_ID(@SourceSchemaAndTable, 'U')
ORDER BY tb.object_id, ic.key_ordinal
) pk_cols
SELECT @nsql = @nsql + @CrLf + @indent + ') ' + @TableOptions
IF @SimulationMode = 'Y'
BEGIN
PRINT '-- Simulation mode: script is not executed.'
END
PRINT @nsql;
IF @SimulationMode != 'Y'
BEGIN
EXEC(@nsql);
END
--\
---) Copy foreign key constraints
---) A guid is added to the foreign key name to make it unique within the database.
--/
SET @nsql = N'';
SELECT @nsql += N'
ALTER TABLE '
+ @TargetSchemaAndTable
+ ' ADD CONSTRAINT [' + LEFT(fk.name + @FkNameSuffix, 128) + '] '
+ ' FOREIGN KEY (' + STUFF((SELECT ',' + QUOTENAME(col.name)
-- get all the columns in the constraint table
FROM sys.columns AS col
JOIN sys.foreign_key_columns AS fkc
ON fkc.parent_column_id = col.column_id
AND fkc.parent_object_id = col.[object_id]
WHERE fkc.constraint_object_id = fk.[object_id]
ORDER BY fkc.constraint_column_id
FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'')
+ ') REFERENCES ' + QUOTENAME(rs.name) + '.' + QUOTENAME(rtb.name)
+ '('
+ STUFF((SELECT ',' + QUOTENAME(col.name)
-- get all the referenced columns
FROM sys.columns AS col
JOIN sys.foreign_key_columns AS fkc
ON fkc.referenced_column_id = col.column_id
AND fkc.referenced_object_id = col.[object_id]
WHERE fkc.constraint_object_id = fk.object_id
ORDER BY fkc.constraint_column_id
FOR XML PATH(N''), TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'') + ');'
FROM sys.foreign_keys AS fk
JOIN sys.tables AS rtb -- referenced table
ON fk.referenced_object_id = rtb.[object_id]
JOIN sys.schemas AS rs
ON rtb.[schema_id] = rs.[schema_id]
JOIN sys.tables AS ctb -- constraint table
ON fk.parent_object_id = ctb.[object_id]
WHERE rtb.is_ms_shipped = 0 AND ctb.is_ms_shipped = 0
AND ctb.object_id = OBJECT_ID(@SourceSchemaAndTable, 'U');
IF @SimulationMode = 'Y'
BEGIN
PRINT '-- Simulation mode: script is not executed.'
END
PRINT @nsql;
IF @SimulationMode != 'Y'
BEGIN
EXEC(@nsql);
END
Download the script here.
Conclusion / Wrap up
With this script you can copy the structure of a table including primary and foreign keys, computed columns and columns using a user defined datatype.
Last updated: 14 July, 2016.
(c) 2016 hansmichiels.com – Do not steal the contents – spread the link instead – thank you.