it-swarm.com.de

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

Gibt es eine Möglichkeit, ein Erstellungsskript aus einer vorhandenen Tabelle nur in T-SQL 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 möglicherweise einen anderen Weg gibt, dies zu erreichen. Ich habe eine Instanz mit mehreren Dutzend Datenbanken. Diese Datenbanken haben alle dasselbe Schema, dieselben Tabellen, denselben Index usw. Sie wurden als Teil einer Softwareinstallation von Drittanbietern 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, um eine Auswahl aus einer Tabelle in allen Datenbanken zu treffen. Ich habe alle Datenbanknamen in einer Tabelle namens Databasees aufgezeichnet und das folgende Skript geschrieben, um eine select-Anweisung für alle 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 aktiviert ist.

Hinzufügen dieses:

SET IDENTITY_INSERT #tmp ON

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

In SQL gibt es keine Möglichkeit, die Identität einer bestimmten Tabelle auszuschalten. Sie können nur eine Spalte löschen und eine Spalte hinzufügen, wodurch sich die Spaltenreihenfolge offensichtlich ändert. Wenn sich die Spaltenreihenfolge ändert, müssen Sie erneut die Spaltenliste angeben, die je nach der von Ihnen abgefragten Tabelle unterschiedlich ist.

Ich dachte also, wenn ich das Skript zum Erstellen einer Tabelle in meinem T-SQL-Code erhalten könnte, könnte ich es mit Zeichenfolgenmanipulationsausdrücken bearbeiten, um die Identitätsspalte zu entfernen und auch eine Spalte für den Datenbanknamen hinzuzufügen zur Ergebnismenge.

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

22
Andrew Savinykh

2007 habe ich nach einer einfachen Möglichkeit gefragt, ein CREATE TABLE - Skript ü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. Stellen wir uns vor, wir hätten eine Tabelle mit demselben Schema über mehrere Datenbanken hinweg, 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 dynamische Verwaltungsfunktion sys.dm_exec_describe_first_results_set , um die richtigen Datentypen für jede der Spalten abzurufen (und die Eigenschaft IDENTITY zu ignorieren). Es erstellt die von Ihnen benötigte # tmp-Tabelle, fügt sie 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 WHILE -Schleife (das macht nicht es ist besser, einfach einfacher anzusehen und erlaubt dir, Database_Ref_No komplett zu 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 PRINT Ausgabe:

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, kommentieren Sie einfach das EXEC aus.

(Dies vertraut darauf, dass das Schema dasselbe ist. Es überprüft nicht, ob eine oder mehrere der Tabellen seitdem geändert wurden, und kann daher fehlschlagen.)

28
Aaron Bertrand

In T-SQL ist es nicht möglich, ein vollständiges Erstellungsskript einer Tabelle zu generieren. Zumindest gibt es keinen Einbau. Sie können jederzeit Ihren eigenen "Generator" schreiben, indem Sie die Informationen sys.columns durchgehen.

In Ihrem Fall benötigen Sie jedoch nicht das vollständige Erstellungsskript. Sie müssen lediglich verhindern, dass SELECT INTO Die Identitätseigenschaft kopiert. Der einfachste Weg, dies zu tun, besteht darin, 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 diesem SQL Fiddle 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 name und 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 CASE - Anweisung 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] |

Beachten Sie, dass Sie mit dynamischem SQL keine # temp-Tabelle 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 drücken 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 wie QUOTENAME(N'temp_'+CAST(NEWID() AS NVARCHAR(40)) sollte einen ausreichend guten Namen haben.


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 Tabellengröße kann dies jedoch schneller oder langsamer sein. Sie sollten es daher testen. Wenn Sie diesen Weg gehen, würde ich diese Ansichten in eine separate Datenbank stellen.

5
Sebastian Meine

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

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

Ich wünschte, es gäbe eine Möglichkeit, das gesamte Skript hier aufzunehmen, 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.
--#################################################################################################
3