Gibt es eine Möglichkeit, ein Tabellenerstellungsskript in TSQL zu generieren?


22

Gibt es eine Möglichkeit, ein Erstellungsskript ausschließlich in T-SQL aus einer vorhandenen Tabelle zu generieren (dh ohne Verwendung von SMO, da T-SQL keinen Zugriff auf SMO hat)? Angenommen, eine gespeicherte Prozedur empfängt einen Tabellennamen und gibt eine Zeichenfolge zurück, die das Erstellungsskript für die angegebene Tabelle enthält.

Lassen Sie mich nun die Situation beschreiben, mit der ich konfrontiert bin, da es einen anderen Weg geben kann, dies zu tun. Ich habe eine Instanz mit mehreren Dutzend Datenbanken. Diese Datenbanken haben alle dasselbe Schema, dieselben Tabellen, denselben Index usw. Sie wurden im Rahmen einer Softwareinstallation eines Drittanbieters erstellt. Ich muss eine Möglichkeit haben, mit ihnen zu arbeiten, damit ich Daten von ihnen ad-hoc aggregieren kann. Nette Leute bei dba.se haben mir hier schon geholfen Wie erstelle ich einen Trigger in einer anderen Datenbank?

Derzeit muss ich einen Weg finden, eine Auswahl aus einer Tabelle in allen Datenbanken zu treffen. Ich habe alle Datenbanknamen in einer Tabelle mit dem Namen aufgezeichnet Databaseesund das folgende Skript geschrieben, um eine select-Anweisung für alle von ihnen auszuführen:

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

Das obige Skript schlägt jedoch mit der folgenden Meldung fehl:

Ein expliziter Wert für die Identitätsspalte in der Tabelle '#tmp' kann nur angegeben werden, wenn eine Spaltenliste verwendet wird und IDENTITY_INSERT auf ON gesetzt ist.

Dies hinzufügen:

SET IDENTITY_INSERT #tmp ON

hilft nicht, da ich die Spaltenliste nicht spezifizieren und generisch halten kann.

In SQL gibt es keine Möglichkeit, die Identität für eine bestimmte Tabelle auszuschalten. Sie können nur eine Spalte löschen und eine Spalte hinzufügen, was natürlich die Spaltenreihenfolge ändert. Und wenn sich die Spaltenreihenfolge ändert, müssen Sie erneut die Spaltenliste angeben, die je nach der von Ihnen abgefragten Tabelle unterschiedlich ist.

Daher überlegte ich, ob ich den Scrip create table in meinem T-SQL-Code abrufen und mit Zeichenfolgenmanipulationsausdrücken bearbeiten könnte, um die Identitätsspalte zu entfernen und dem Resultset auch eine Spalte für den Datenbanknamen hinzuzufügen .

Kann sich jemand einen relativ einfachen Weg vorstellen, um das zu erreichen, was ich will?

Antworten:


28

2007 habe ich nach einer einfachen Möglichkeit gefragt, ein CREATE TABLESkript über T-SQL zu generieren, anstatt die Benutzeroberfläche oder SMO zu verwenden. Ich wurde kurzerhand abgelehnt .

SQL Server 2012 macht dies jedoch sehr einfach. Angenommen, wir haben eine Tabelle mit demselben Schema in mehreren Datenbanken, z dbo.whatcha.

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

Das folgende Skript verwendet die neue sys.dm_exec_describe_first_results_setdynamische Verwaltungsfunktion , um die richtigen Datentypen für jede der Spalten abzurufen (und die IDENTITYEigenschaft zu ignorieren ). Es erstellt die # tmp-Tabelle, die Sie benötigen, fügt aus jeder der Datenbanken in Ihrer Liste ein und wählt dann aus # tmp aus, alles innerhalb eines einzelnen dynamischen SQL-Stapels und ohne Verwendung einer WHILESchleife (das macht es nicht besser, nur einfacher) schau es dir an und Database_Ref_Nolass es dich gänzlich ignorieren :-)).

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;

Die resultierende PRINTAusgabe:

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;

Wenn Sie sicher sind, dass es das tut, was Sie erwarten, entfernen Sie einfach das Kommentarzeichen EXEC.

(Dies vertraut darauf, dass das Schema identisch ist. Es wird nicht überprüft, ob eine oder mehrere Tabellen seitdem geändert wurden, und schlägt möglicherweise fehl.)


Warum generiert das nicht die Identitätsspezifikationen?
FindOutIslamNow

1
@ Kilanny Hast du die ganze Antwort gelesen, wie den Teil, in dem ich darüber spreche, warum wir die Identitätseigenschaft ignorieren?
Aaron Bertrand

Ich benötige die vollständige Tabellendefinition (einschließlich Identität, Indizes, Einschränkungen, ..). Zum Glück fand ich dieses großartige Skript stormrage.com/SQLStuff/sp_GetDDLa_Latest.txt Trotzdem vielen Dank
FindOutIslamNow

@ Kilanny Großartig. Um klar zu sein, Ihre Anforderung entspricht nicht der Anforderung in dieser Frage. Sie benötigten eine Kopie der Tabelle ohne Identität, da sie damit vorhandene Daten kopierten und keine neuen Zeilen generierten.
Aaron Bertrand

Aaron, dies ist eine elegante Lösung, in der Sie zur Not keine Schlüssel usw. benötigen.
GWR

5

Es ist in T-SQL nicht möglich, ein vollständiges Erstellungsskript einer Tabelle zu generieren. Zumindest gibt es keine Einbauten. Sie könnten immer Ihren eigenen "Generator" schreiben, der die Informationen durchgeht sys.columns.

In Ihrem Fall benötigen Sie jedoch nicht das vollständige Erstellungsskript. Sie müssen lediglich verhindern SELECT INTO, dass die Identitätseigenschaft kopiert wird. Am einfachsten ist es, dieser Spalte eine Berechnung hinzuzufügen. Also statt

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

du musst schreiben

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

Um diese Anweisung zu generieren, können Sie wieder sys.columns wie in dieser SQL-Geige verwenden

MS SQL Server 2008-Schema-Setup :

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

Die zwei Spalten, die wir benötigen, sind nameund is_identity: Abfrage 1 :

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

Ergebnisse :

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

Damit können wir eine CASEAnweisung verwenden, um jede Spalte für die Spaltenliste zu generieren:

Abfrage 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');

Ergebnisse :

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

Mit ein wenig XML-Trick können wir all dies zusammenfassen, um die vollständige Spaltenliste zu erhalten:

Abfrage 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,'')

Ergebnisse :

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

Bedenken Sie, dass Sie keine #temp-Tabelle mit dynamischem SQL erstellen und außerhalb dieser Anweisung verwenden können, da die #temp-Tabelle nach Abschluss Ihrer dynamischen SQL-Anweisung den Gültigkeitsbereich verlässt. Sie müssen also entweder Ihren gesamten Code in dieselbe dynamische SQL-Zeichenfolge packen oder eine echte Tabelle verwenden. Wenn Sie in der Lage sein müssen, mehrere dieser Skripte / Prozeduren gleichzeitig auszuführen, müssen Sie uns einen zufälligen Tabellennamen geben, sonst treten sie aufeinander. So etwas QUOTENAME(N'temp_'+CAST(NEWID() AS NVARCHAR(40))sollte einen guten Namen machen.


Anstatt alle Daten zu kopieren, können Sie auch eine ähnliche Technik verwenden, um automatisch eine Ansicht für jede Tabelle zu generieren, die alle Inkarnationen dieser Tabelle in allen Datenbanken vereint. Abhängig von der Tischgröße kann dies jedoch schneller oder langsamer sein, daher sollten Sie es testen. Wenn Sie diesen Weg gehen, würde ich diese Ansichten in eine separate Datenbank stellen.


1
Sie können eine # temporäre Tabelle erstellen und sie dann in Dynamic SQL referenzieren. Nur wenn Sie es in diesem Bereich erstellen, ist es nach der Ausführung von Dynamic SQL nicht sichtbar.
Aaron Bertrand

3

Im SQLServerCentral-Artikel finden Sie ein gutes Skript, um dies zu erreichen:

Die aktuellste Version des Skripts ist auch als Text hier verfügbar (stormrage.com).

Ich wünschte, es gäbe eine Möglichkeit, das gesamte Skript hier einzuschließen, da es für mich funktioniert. Das Skript ist einfach zu lang, um es hier einzufügen.

Urheberrechtshinweis:

--#################################################################################################
-- 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

Sie können CREATE TABLEmithilfe von dynamischem SQL aus den Daten in ein Grobformat generieren INFORMATION_SCHEMA.COLUMNS.

Wenn Sie Einschränkungen usw. hinzufügen müssen, müssen Sie Informationen aus einigen anderen INFORMATION_SCHEMAAnsichten hinzufügen .

Microsoft-Dokumentation

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.