C'è un modo per generare uno script di creazione di tabelle in TSQL?


22

C'è un modo per generare uno script di creazione da una tabella esistente puramente in T-SQL (ovvero senza usare SMO, poiché T-SQL non ha accesso a SMO). Diciamo una procedura memorizzata che riceve un nome di tabella e restituisce una stringa che contiene lo script di creazione per la tabella data?

Ora lasciami descrivere la situazione che sto affrontando, in quanto potrebbe esserci un modo diverso di affrontarlo. Ho un'istanza con diverse dozzine di database. Questi database hanno tutti lo stesso schema, tutte le stesse tabelle, indice e così via. Sono stati creati come parte di un'installazione software di terze parti. Devo avere un modo di lavorare con loro in modo da poter aggregare i dati da loro in maniera ad hoc. Le persone simpatiche su dba.se mi hanno già aiutato qui Come creare un trigger in un database diverso?

Attualmente ho bisogno di trovare un modo per effettuare una selezione da una tabella in tutti i database. Ho registrato tutti i nomi dei database in una tabella chiamata Databaseese ho scritto il seguente script per eseguire un'istruzione select su tutti loro:

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

Tuttavia lo script precedente non riesce con il seguente messaggio:

Un valore esplicito per la colonna identità nella tabella '#tmp' può essere specificato solo quando viene utilizzato un elenco di colonne e IDENTITY_INSERT è ON.

Aggiungendo questo:

SET IDENTITY_INSERT #tmp ON

non aiuta, poiché non posso specificare l'elenco di colonne e mantenerlo generico.

In SQL non è possibile disattivare l'identità su una determinata tabella. Puoi solo rilasciare una colonna e aggiungere una colonna, che ovviamente cambia l'ordine delle colonne. E se l'ordine delle colonne cambia, è necessario, ancora una volta, specificare l'elenco delle colonne, che sarebbe diverso a seconda della tabella in cui si esegue la query.

Quindi pensavo che se avessi potuto ottenere lo script di creazione della tabella nel mio codice T-SQL, avrei potuto manipolarlo con espressioni di manipolazione delle stringhe per rimuovere la colonna di identità e anche aggiungere una colonna per il nome del database al set di risultati.

Qualcuno può pensare a un modo relativamente semplice per ottenere ciò che voglio?

Risposte:


28

Nel 2007, ho chiesto un modo semplice per generare uno CREATE TABLEscript tramite T-SQL anziché utilizzare l'interfaccia utente o SMO. Sono stato sommariamente respinto .

Tuttavia, SQL Server 2012 lo rende molto semplice. Facciamo finta di avere una tabella con lo stesso schema su più database, ad esempio dbo.whatcha:

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

Il seguente script utilizza la nuova sys.dm_exec_describe_first_results_setfunzione di gestione dinamica per recuperare i tipi di dati corretti per ciascuna delle colonne (e ignorare la IDENTITYproprietà). Costruisce la tabella #tmp di cui hai bisogno, inserisce da ciascuno dei database nell'elenco e quindi seleziona da #tmp, il tutto all'interno di un singolo batch SQL dinamico e senza utilizzare un WHILEciclo (che non lo rende migliore, semplicemente più semplice da guarda e ti permette di ignorare del Database_Ref_Notutto :-)).

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;

L' PRINToutput risultante :

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;

Quando sei sicuro che stia facendo quello che ti aspetti, basta decommentare il EXEC.

(Questo ti fida che lo schema è lo stesso; non convalida che una o più tabelle siano state modificate e, di conseguenza, potrebbe non riuscire.)


Perché questo non genera le specifiche di identità?
FindOutIslamNow,

1
@Kilanny Hai letto l'intera risposta, come la parte in cui parlo del perché stiamo ignorando la proprietà dell'identità?
Aaron Bertrand

Ho bisogno della definizione di tabella COMPLETA (compresi identità, indici, vincoli, ..). Per fortuna ho trovato questo fantastico script stormrage.com/SQLStuff/sp_GetDDLa_Latest.txt Grazie comunque
FindOutIslamNow

@Kilanny Great. Per essere chiari, il tuo requisito non corrisponde al requisito in questa domanda. Avevano bisogno di una copia della tabella senza identità perché la stavano usando per copiare i dati esistenti, non generando nuove righe.
Aaron Bertrand

Aaron, questa è una soluzione elegante in un pizzico in cui non hai bisogno di chiavi, ecc ...
GWR

5

In T-SQL non è possibile generare uno script di creazione completo di una tabella. Almeno non c'è modo di costruire. potresti sempre scrivere il tuo "generatore" attraverso le informazioni sys.columns.

Ma nel tuo caso non è necessario ottenere lo script di creazione completo. Tutto ciò che serve è impedire la SELECT INTOcopia della proprietà identità. Il modo più semplice per farlo è aggiungere un calcolo a quella colonna. Quindi invece di

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

devi scrivere

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

Per generare questa affermazione è possibile utilizzare nuovamente sys.columns come in questo SQL Fiddle

Installazione schema MS SQL Server 2008 :

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

Le due colonne di cui abbiamo bisogno sono namee is_identity: Query 1 :

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

Risultati :

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

Con ciò possiamo usare CASEun'istruzione per generare ogni colonna per l'elenco delle colonne:

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

Risultati :

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

Con un po 'di trucco XML possiamo concatenare tutto questo insieme per ottenere l'elenco completo delle colonne:

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

Risultati :

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

Tieni presente che non puoi creare una tabella #temp utilizzando SQL dinamico e utilizzarla al di fuori di tale istruzione poiché la tabella #temp esce dall'ambito una volta terminata l'istruzione sql dinamica. Quindi devi comprimere tutto il codice nella stessa stringa SQL dinamica o utilizzare una tabella reale. Se devi essere in grado di eseguire più di questi script / procedure contemporaneamente, devi fornirci un nome di tabella casuale, altrimenti si calpesteranno l'un l'altro. Qualcosa del genere QUOTENAME(N'temp_'+CAST(NEWID() AS NVARCHAR(40))dovrebbe fare un nome abbastanza buono.


Invece di copiare tutti i dati in giro, potresti anche usare una tecnica simile per generare automaticamente una vista per ogni tabella che unisce tutte le incarnazioni di quella tabella in tutti i database. A seconda delle dimensioni della tabella, tuttavia, potrebbe essere più veloce o più lento, quindi dovresti testarlo. Se segui questa strada, inserirei quelle viste in un database separato.


1
È possibile creare una tabella #temp e quindi fare riferimento a esso da SQL dinamico bene. È solo se lo crei in quell'ambito che non è visibile dopo l'esecuzione dell'SQL dinamico.
Aaron Bertrand

3

C'è un buon script per raggiungere questo obiettivo nell'articolo SQLServerCentral:

L'ultima versione attuale dello script è disponibile anche come testo qui (stormrage.com).

Vorrei che ci fosse un modo per includere tutto lo script qui, perché funziona per me. Lo script è troppo lungo per essere incollato qui.

Avviso sul copyright:

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

È possibile generare un rough CREATE TABLEutilizzando SQL dinamico dai dati inINFORMATION_SCHEMA.COLUMNS .

Se è necessario aggiungere vincoli, ecc., Sarà necessario aggiungere informazioni da alcune delle altre INFORMATION_SCHEMAviste.

Documentazione Microsoft

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.