有没有办法在TSQL中生成表创建脚本?

And*_*ykh 22 sql-server dynamic-sql t-sql sql-server-2012

有没有办法纯粹在 T-SQL 中从现有表生成创建脚本(即不使用 SMO,因为 T-SQL 无法访问 SMO)。假设一个存储过程接收一个表名并返回一个包含给定表的创建脚本的字符串?

现在让我描述一下我面临的情况,因为可能有不同的方法来解决这个问题。我有一个包含几十个数据库的实例。这些数据库都具有相同的架构、相同的表、索引等。它们是作为第三方软件安装的一部分创建的。我需要有一种与他们合作的方法,以便我可以以特别的方式从他们那里收集数据。dba.se 的好人已经在这里帮助了我如何在不同的数据库中创建触发器?

目前我需要找到一种方法来从所有数据库的表中进行选择。我已将所有数据库名称记录到一个名为的表中,Databasees并编写了以下脚本来对所有数据库执行 select 语句:

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
Run Code Online (Sandbox Code Playgroud)

但是,上面的脚本失败并显示以下消息:

只有在使用列列表且 IDENTITY_INSERT 为 ON 时,才能为表 '#tmp' 中的标识列指定显式值。

添加这个:

SET IDENTITY_INSERT #tmp ON
Run Code Online (Sandbox Code Playgroud)

没有帮助,因为我无法指定列列表并使其保持通用。

在 SQL 中,无法关闭给定表上的标识。您只能删除一列并添加一列,这显然会更改列顺序。如果列顺序发生变化,您再次需要指定列列表,这取决于您查询的表。

所以我在想,如果我可以在我的 T-SQL 代码中获得创建表脚本,我可以使用字符串操作表达式来操作它以删除标识列,并将数据库名称的列添加到结果集中。

谁能想到一个相对简单的方法来实现我想要的?

Aar*_*and 28

早在 2007 年,我就寻求一种CREATE TABLE通过 T-SQL 而不是使用 UI 或 SMO生成脚本的简单方法。我被立即拒绝了

但是,SQL Server 2012 使这变得非常容易。让我们假设我们有一个跨多个数据库具有相同模式的表,例如dbo.whatcha

CREATE TABLE dbo.whatcha
(
  id INT IDENTITY(1,1), 
  x VARCHAR(MAX), 
  b DECIMAL(10,2), 
  y SYSNAME
);
Run Code Online (Sandbox Code Playgroud)

以下脚本使用新的sys.dm_exec_describe_first_results_set动态管理函数来检索每个列的正确数据类型(并忽略该IDENTITY属性)。它构建您需要的 #tmp 表,从列表中的每个数据库插入,然后从 #tmp 中选择,所有这些都在单个动态 SQL 批处理中并且不使用WHILE循环(这并没有使它变得更好,只是更简单查看并允许您Database_Ref_No完全忽略:-))。

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;
Run Code Online (Sandbox Code Playgroud)

结果PRINT输出:

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;
Run Code Online (Sandbox Code Playgroud)

当您确信它正在按照您的预期运行时,只需取消注释EXEC.

(这让您相信架构是相同的;它不会验证一个或多个表是否已更改,因此可能会失败。)


Seb*_*ine 5

int T-SQL 不可能生成表的完整创建脚本。至少没有内置的方式。你总是可以编写自己的“生成器”来浏览信息sys.columns

但在您的情况下,您不需要获取完整的创建脚本。您所需要的只是防止SELECT INTO复制身份属性。最简单的方法是向该列添加计算。所以代替

select * into #tmp from Database1.dbo.Table1 where 1=0
Run Code Online (Sandbox Code Playgroud)

你需要写

select id*0 as id, other, column, names into #tmp from Database1.dbo.Table1 where 1=0
Run Code Online (Sandbox Code Playgroud)

要生成此语句,您可以再次使用 sys.columns,就像在此SQL Fiddle 中一样

MS SQL Server 2008 架构设置

CREATE TABLE dbo.testtbl(
    id INT IDENTITY(1,1),
    other NVARCHAR(MAX),
    [column] INT,
    [name] INT
);
Run Code Online (Sandbox Code Playgroud)

我们需要的两列是nameand is_identity查询 1

SELECT name,is_identity
  FROM sys.columns
 WHERE object_id = OBJECT_ID('dbo.testtbl');
Run Code Online (Sandbox Code Playgroud)

结果

|   NAME | IS_IDENTITY |
|--------|-------------|
|     id |           1 |
|  other |           0 |
| column |           0 |
|   name |           0 |
Run Code Online (Sandbox Code Playgroud)

有了这个,我们可以使用一个CASE语句来为列列表生成每一列:

查询 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');
Run Code Online (Sandbox Code Playgroud)

结果

|        COLUMN_0 |
|-----------------|
| ,[id]*0 AS [id] |
|        ,[other] |
|       ,[column] |
|         ,[name] |
Run Code Online (Sandbox Code Playgroud)

使用一点 XML 技巧,我们可以将所有这些连接在一起以获得完整的列列表:

查询 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,'')
Run Code Online (Sandbox Code Playgroud)

结果

|                               COLUMN_0 |
|----------------------------------------|
| [id]*0 AS [id],[other],[column],[name] |
Run Code Online (Sandbox Code Playgroud)

请记住,您不能使用动态 SQL 创建 #temp 表并在该语句之外使用它,因为一旦您的动态 sql 语句完成,#temp 表就会超出范围。因此,您必须将所有代码压缩到同一个动态 SQL 字符串中,或​​者使用真实表。如果你需要能够同时执行多个这些脚本/过程,你需要给我们一个随机的表名,否则它们会互相踩踏。像这样的东西QUOTENAME(N'temp_'+CAST(NEWID() AS NVARCHAR(40))应该是一个足够好的名字。


除了复制所有数据之外,您还可以使用类似的技术为每个表自动生成一个视图,该视图将所有数据库中该表的所有化身联合起来。根据表的大小,这可能会更快或更慢,因此您应该对其进行测试。如果你走这条路,我会把这些视图放到一个单独的数据库中。