Is there a way to generate a create script from an existing table purely in T-SQL (that is without using SMO, since T-SQL does not have access to SMO). Let's say a stored procedure that receives a table name and returns a string that contains the create script for the given table?
Now let me describe the situation I'm facing, as there may be a different way to approach this. I have an instance with several dozen databases. These database all have the same schema, all the same tables, index and so on. They were created as a part of a third party software installation. I need to have a way to work with them so that I can aggregate data from them in ad-hoc manner. Nice people at dba.se have already helped me here How to create a trigger in a different database?
Currently I need to find a way to make a select from a table across all the databases. I have recorded all the database names into a table called Databasees
and I wrote the following script to execute a select statement on all of them:
IF OBJECT_ID('tempdb..#tmp') IS NOT NULL
DROP TABLE #tmp
select * into #tmp from Database1.dbo.Table1 where 1=0
DECLARE @statement nvarchar(max) =
N'insert into #tmp select * from Table1 where Column1=0 and Cloumn2 =1'
DECLARE @LastDatabaseID INT
SET @LastDatabaseID = 0
DECLARE @DatabaseNameToHandle varchar(60)
DECLARE @DatabaseIDToHandle int
SELECT TOP 1 @DatabaseNameToHandle = Name,
@DatabaseIDToHandle = Database_Ref_No
FROM Databasees
WHERE Database_Ref_No > @LastDatabaseID
ORDER BY Database_Ref_No
WHILE @DatabaseIDToHandle IS NOT NULL
BEGIN
DECLARE @sql NVARCHAR(MAX) = QUOTENAME(@DatabaseNameToHandle) + '.dbo.sp_executesql'
EXEC @sql @statement
SET @LastDatabaseID = @DatabaseIDToHandle
SET @DatabaseIDToHandle = NULL
SELECT TOP 1 @DatabaseNameToHandle = Name,
@DatabaseIDToHandle = Database_Ref_No
FROM Databasees
WHERE Database_Ref_No > @LastDatabaseID
ORDER BY Database_Ref_No
END
select * from #tmp
DROP TABLE #tmp
However the script above fails with the following message:
An explicit value for the identity column in table '#tmp' can only be specified when a column list is used and IDENTITY_INSERT is ON.
Adding this:
SET IDENTITY_INSERT #tmp ON
does not help, since, I can't specify the column list and keep it generic.
In SQL there is no way to switch the identity on a given table off. You can only drop a column and add a column, which, obviously changes the column order. And if the column order changes, you, again, need to specify the column list, that would be different depending on the table you query.
So I was thinking if I could get the create table scrip in my T-SQL code, I could manipulate it with string manipulation expressions to remove the identity column and also add a column for the Database name to the result set.
Can anyone think of a relatively easy way to achieve what I want?
4 Answers 4
However, SQL Server 2012 makes this very easy. Let's pretend we have a table with the same schema across multiple databases, e.g. dbo.whatcha
:
CREATE TABLE dbo.whatcha
(
id INT IDENTITY(1,1),
x VARCHAR(MAX),
b DECIMAL(10,2),
y SYSNAME
);
The following script uses the new sys.dm_exec_describe_first_results_set
dynamic management function to retrieve the proper data types for each of the columns (and ignoring the IDENTITY
property). It builds the #tmp table you need, inserts from each of the databases in your list, and then selects from #tmp, all within a single dynamic SQL batch and without using a WHILE
loop (that doesn't make it better, just simpler to look at and allows you to ignore Database_Ref_No
entirely :-)).
SET NOCOUNT ON;
DECLARE @sql NVARCHAR(MAX), @cols NVARCHAR(MAX) = N'';
SELECT @cols += N',' + name + ' ' + system_type_name
FROM sys.dm_exec_describe_first_result_set(N'SELECT * FROM dbo.whatcha', NULL, 1);
SET @cols = STUFF(@cols, 1, 1, N'');
SET @sql = N'CREATE TABLE #tmp(' + @cols + ');'
DECLARE @dbs TABLE(db SYSNAME);
INSERT @dbs VALUES(N'db1'),(N'db2');
-- SELECT whatever FROM dbo.databases
SELECT @sql += N'
INSERT #tmp SELECT ' + @cols + ' FROM ' + QUOTENAME(db) + '.dbo.tablename;'
FROM @dbs;
SET @sql += N'
SELECT ' + @cols + ' FROM #tmp;';
PRINT @sql;
-- EXEC sp_executesql @sql;
The resulting PRINT
output:
CREATE TABLE #tmp(id int,x varchar(max),b decimal(10,2),y nvarchar(128));
INSERT #tmp SELECT id,x,b,y FROM [db1].dbo.tablename;
INSERT #tmp SELECT id,x,b,y FROM [db2].dbo.tablename;
SELECT id,x,b,y FROM #tmp;
When you are confident it's doing what you expect, just uncomment the EXEC
.
(This trusts you that the schema is the same; it does not validate that one or more of the tables has since been changed, and may fail as a result.)
-
Why does not that generate the identity specs?FindOutIslamNow– FindOutIslamNow2016年07月26日 11:00:24 +00:00Commented Jul 26, 2016 at 11:00
-
1@Kilanny Did you read the whole answer, like the part where I talk about why we're ignoring the identity property?Aaron Bertrand– Aaron Bertrand2016年07月26日 12:03:30 +00:00Commented Jul 26, 2016 at 12:03
-
I need the FULL table definition (including identity, indexes, constraints, ..). Thankfully I found this great script stormrage.com/SQLStuff/sp_GetDDLa_Latest.txt Thank you anywayFindOutIslamNow– FindOutIslamNow2016年07月26日 13:19:29 +00:00Commented Jul 26, 2016 at 13:19
-
1@Kilanny Great. To be clear, your requirement does not match the requirement in this question. They needed a copy of the table without identity because they were using it to copy existing data, not generating new rows.Aaron Bertrand– Aaron Bertrand2016年07月26日 14:05:20 +00:00Commented Jul 26, 2016 at 14:05
-
1@GWR Right, it's definitely not a robust solution for all requirements. The OP here simply wanted a staging table where the data types matched. If you need to generate a copy of an existing table with all the other information too, you should be borrowing the create table script from your source control system.Aaron Bertrand– Aaron Bertrand2017年10月13日 01:32:43 +00:00Commented Oct 13, 2017 at 1:32
It is not possible int T-SQL to generate a full create script of a table. At least there is no build in way. you could always write your own "generator" going through the information sys.columns
.
But in your case you don't need to get the full create script. All you need is the prevent the SELECT INTO
from copying the identity property. The easiest way to do that is to add a calculation to that column. So instead of
select * into #tmp from Database1.dbo.Table1 where 1=0
you need to write
select id*0 as id, other, column, names into #tmp from Database1.dbo.Table1 where 1=0
To generate this statement you can again use sys.columns as in this SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE dbo.testtbl(
id INT IDENTITY(1,1),
other NVARCHAR(MAX),
[column] INT,
[name] INT
);
The two columns we need are name
and is_identity
:
Query 1:
SELECT name,is_identity
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.testtbl');
Results :
| NAME | IS_IDENTITY |
|--------|-------------|
| id | 1 |
| other | 0 |
| column | 0 |
| name | 0 |
With that we can use a CASE
statement to generate each column for the column list:
Query 2:
SELECT ','+
CASE is_identity
WHEN 1 THEN QUOTENAME(name)+'*0 AS '+QUOTENAME(name)
ELSE QUOTENAME(name)
END
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.testtbl');
Results :
| COLUMN_0 |
|-----------------|
| ,[id]*0 AS [id] |
| ,[other] |
| ,[column] |
| ,[name] |
With a little XML trickery we can concatenate all of this together to get the full column list:
Query 3:
SELECT STUFF((
SELECT ','+
CASE is_identity
WHEN 1 THEN QUOTENAME(name)+'*0 AS '+QUOTENAME(name)
ELSE QUOTENAME(name)
END
FROM sys.columns
WHERE object_id = OBJECT_ID('dbo.testtbl')
ORDER BY column_id
FOR XML PATH(''),TYPE
).value('.','NVARCHAR(MAX)'),1,1,'')
Results :
| COLUMN_0 |
|----------------------------------------|
| [id]*0 AS [id],[other],[column],[name] |
Keep in mind, that you cannot create a #temp table using dynamic SQL and use it outside of that statement as the #temp table goes out of scope once your dynamic sql statement finishes. So you have to either squeeze all your code into the same dynamic SQL string or use a real table. If you need to be able to execute multiple of these scripts/procedures at the same time, you need to us a random table name, otherwise they will step on each other. Something like QUOTENAME(N'temp_'+CAST(NEWID() AS NVARCHAR(40))
should make a good enough name.
Instead of copying all the data around, you could also use a similar technique to just auto generate a view for each table that unions all the incarnations of that table across all databases. Depending on the table size however that might be faster or slower, so you should test it. If you go this route, I would put those views into a separate database.
-
1You can create a #temp table and then reference it from dynamic SQL just fine. It's only if you create it in that scope that it is not visible after the dynamic SQL has executed.Aaron Bertrand– Aaron Bertrand2013年11月11日 00:56:27 +00:00Commented Nov 11, 2013 at 0:56
There is a good script to achieve this in the SQLServerCentral article:
- Get DDL for any SQL 2005 table (registration required) by Lowell Izaguirre.
The current latest version of the script is also available as text here (stormrage.com).
I wish there was a way to include all the script here, because it works for me. The script is just too long to paste here.
Copyright notice:
--#################################################################################################
-- copyright 2004-2013 by Lowell Izaguirre scripts*at*stormrage.com all rights reserved.
-- http://www.stormrage.com/SQLStuff/sp_GetDDL_Latest.txt
--Purpose: Script Any Table, Temp Table or Object
--
-- see the thread here for lots of details: http://www.sqlservercentral.com/Forums/Topic751783-566-7.aspx
-- You can use this however you like...this script is not rocket science, but it took a bit of work to create.
-- the only thing that I ask
-- is that if you adapt my procedure or make it better, to simply send me a copy of it,
-- so I can learn from the things you've enhanced.The feedback you give will be what makes
-- it worthwhile to me, and will be fed back to the SQL community.
-- add this to your toolbox of helpful scripts.
--#################################################################################################
You can generate a rough CREATE TABLE
using dynamic SQL from the data in INFORMATION_SCHEMA.COLUMNS
.
If you need to add constraints etc you will need to add information from some of the other INFORMATION_SCHEMA
views.
Explore related questions
See similar questions with these tags.