Есть ли способ создать скрипт создания таблицы в TSQL?

22

Есть ли способ сгенерировать сценарий создания из существующей таблицы исключительно в 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

Однако вышеприведенный скрипт завершается неудачно со следующим сообщением:

Явное значение для столбца идентификаторов в таблице '#tmp' можно указывать только в том случае, если используется список столбцов, а IDENTITY_INSERT установлен в ON.

Добавление этого:

SET IDENTITY_INSERT #tmp ON

не помогает, так как я не могу указать список столбцов и сделать его общим.

В SQL нет способа отключить идентификацию для данной таблицы. Вы можете только удалить столбец и добавить столбец, который, очевидно, меняет порядок столбцов. И если порядок столбцов изменяется, вам снова нужно указать список столбцов, который будет отличаться в зависимости от таблицы, которую вы запрашиваете.

Поэтому я подумал, что если бы я мог получить скрипт создания таблицы в своем коде T-SQL, я мог бы манипулировать им с помощью выражений манипуляции со строками, чтобы удалить столбец идентификаторов, а также добавить столбец для имени базы данных. в набор результатов.

Кто-нибудь может придумать относительно простой способ добиться того, чего я хочу?

Андрей Савиных
источник

Ответы:

28

Еще в 2007 году я попросил простой способ создания CREATE TABLEсценария с помощью T-SQL, а не с помощью пользовательского интерфейса или SMO. Я был вкратце отклонен .

Тем не менее, SQL Server 2012 делает это очень просто. Давайте представим, что у нас есть таблица с одинаковой схемой для нескольких баз данных, например dbo.whatcha:

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

Следующий скрипт использует новую 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;

Полученный 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;

Когда вы уверены, что делаете то, что ожидаете, просто раскомментируйте EXEC.

(Это вам доверяет, что схема одна и та же; она не проверяет, что одна или несколько таблиц были с тех пор изменены, и может привести к ошибке в результате.)

Аарон Бертран
источник
Почему это не генерирует спецификации личности?
FindOutIslamNow
1
@Kilanny Вы прочитали весь ответ, например ту часть, где я говорю о том, почему мы игнорируем свойство идентичности?
Аарон Бертран
Мне нужно полное определение таблицы (включая идентичность, индексы, ограничения, ..). К счастью, я нашел этот замечательный скрипт stormrage.com/SQLStuff/sp_GetDDLa_Latest.txt В любом случае, спасибо
FindOutIslamNow
@Kilanny Отлично. Чтобы было ясно, ваше требование не соответствует требованию в этом вопросе. Им нужна была копия таблицы без идентификатора, потому что они использовали ее для копирования существующих данных, а не для создания новых строк.
Аарон Бертран
Аарон, это элегантное решение в крайнем случае, когда тебе не нужны ключи и т.д ...
GWR
5

В T-SQL невозможно создать полный сценарий создания таблицы. По крайней мере, нет пути в пути. Вы всегда можете написать свой собственный «генератор», просматривая информацию sys.columns.

Но в вашем случае вам не нужно получать полный сценарий создания. Все, что вам нужно, это предотвратить SELECT INTOкопирование свойства идентификации. Самый простой способ сделать это - добавить вычисление в этот столбец. Так что вместо

select * into #tmp from Database1.dbo.Table1 where 1=0

тебе нужно написать

select id*0 as id, other, column, names into #tmp from Database1.dbo.Table1 where 1=0

Чтобы сгенерировать этот оператор, вы можете снова использовать sys.columns, как в этой скрипте SQL

Настройка схемы MS SQL Server 2008 :

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

Две колонны , нам нужны nameи is_identity: Запрос 1 :

SELECT name,is_identity
  FROM sys.columns
 WHERE object_id = OBJECT_ID('dbo.testtbl');

Результаты :

|   NAME | IS_IDENTITY |
|--------|-------------|
|     id |           1 |
|  other |           0 |
| column |           0 |
|   name |           0 |

С этим мы можем использовать 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');

Результаты :

|        COLUMN_0 |
|-----------------|
| ,[id]*0 AS [id] |
|        ,[other] |
|       ,[column] |
|         ,[name] |

С небольшой хитростью 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,'')

Результаты :

|                               COLUMN_0 |
|----------------------------------------|
| [id]*0 AS [id],[other],[column],[name] |

Имейте в виду, что вы не можете создать таблицу #temp с использованием динамического SQL и использовать ее вне этого оператора, поскольку таблица #temp выходит из области действия после завершения выполнения вашего динамического оператора sql. Поэтому вам нужно либо сжать весь ваш код в одну и ту же динамическую строку SQL, либо использовать реальную таблицу. Если вам необходимо выполнять несколько этих сценариев / процедур одновременно, вам нужно использовать случайное имя таблицы, иначе они будут наступать друг на друга. Что-то вроде QUOTENAME(N'temp_'+CAST(NEWID() AS NVARCHAR(40))должно сделать достаточно хорошее имя.


Вместо того, чтобы копировать все данные вокруг, вы также можете использовать аналогичную технику, чтобы просто автоматически генерировать представление для каждой таблицы, объединяющее все воплощения этой таблицы во всех базах данных. Однако, в зависимости от размера таблицы, это может быть быстрее или медленнее, поэтому вы должны проверить это. Если вы пойдете по этому пути, я бы поместил эти представления в отдельную базу данных.

Себастьян Майн
источник
1
Вы можете создать таблицу #temp и затем ссылаться на нее из динамического SQL. Только если вы создадите его в этой области, он не будет виден после выполнения динамического SQL.
Аарон Бертран
3

Есть хороший сценарий для достижения этой цели в статье SQLServerCentral:

Текущая последняя версия скрипта также доступна в виде текста здесь (stormrage.com).

Я хотел бы, чтобы был способ включить весь сценарий здесь, потому что он работает для меня. Сценарий слишком длинный, чтобы вставить его здесь.

Уведомление об авторских правах:

--#################################################################################################
-- 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.
--#################################################################################################
Марчелло Миорелли
источник
-1

Вы можете генерировать грубый, CREATE TABLEиспользуя динамический SQL из данных в INFORMATION_SCHEMA.COLUMNS.

Если вам нужно добавить ограничения и т. Д., Вам нужно будет добавить информацию из некоторых других INFORMATION_SCHEMAпредставлений.

Документация Microsoft

david25272
источник