¿Hay alguna manera de generar un script de creación de tabla en TSQL?


22

¿Hay alguna manera de generar un script de creación a partir de una tabla existente únicamente en T-SQL (es decir, sin usar SMO, ya que T-SQL no tiene acceso a SMO)? Digamos un procedimiento almacenado que recibe un nombre de tabla y devuelve una cadena que contiene el script de creación para la tabla dada.

Ahora permítanme describir la situación que estoy enfrentando, ya que puede haber una forma diferente de abordar esto. Tengo una instancia con varias docenas de bases de datos. Todas estas bases de datos tienen el mismo esquema, todas las mismas tablas, índices, etc. Fueron creados como parte de una instalación de software de terceros. Necesito tener una manera de trabajar con ellos para poder agregar datos de ellos de manera ad-hoc. Gente amable en dba.se ya me ha ayudado aquí ¿Cómo crear un activador en una base de datos diferente?

Actualmente necesito encontrar una manera de hacer una selección de una tabla en todas las bases de datos. Grabé todos los nombres de las bases de datos en una tabla llamada Databaseesy escribí el siguiente script para ejecutar una instrucción select en todos ellos:

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

Sin embargo, el script anterior falla con el siguiente mensaje:

Un valor explícito para la columna de identidad en la tabla '#tmp' solo se puede especificar cuando se utiliza una lista de columnas y IDENTITY_INSERT está ACTIVADO.

Agregando esto:

SET IDENTITY_INSERT #tmp ON

no ayuda, ya que no puedo especificar la lista de columnas y mantenerla genérica.

En SQL no hay forma de desactivar la identidad en una tabla determinada. Solo puede soltar una columna y agregar una columna que, obviamente, cambia el orden de las columnas. Y si el orden de las columnas cambia, usted, nuevamente, necesita especificar la lista de columnas, eso sería diferente dependiendo de la tabla que consulte.

Entonces pensé que si podía obtener el script de creación de tabla en mi código T-SQL, podría manipularlo con expresiones de manipulación de cadenas para eliminar la columna de identidad y también agregar una columna para el nombre de la Base de datos al conjunto de resultados.

¿Alguien puede pensar en una forma relativamente fácil de lograr lo que quiero?

Respuestas:


28

En 2007, pedí una manera fácil de generar un CREATE TABLEscript a través de T-SQL en lugar de usar la interfaz de usuario o SMO. Fui sumariamente rechazado .

Sin embargo, SQL Server 2012 hace esto muy fácil. Supongamos que tenemos una tabla con el mismo esquema en varias bases de datos, por ejemplo dbo.whatcha:

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

El siguiente script utiliza la nueva sys.dm_exec_describe_first_results_setfunción de administración dinámica para recuperar los tipos de datos adecuados para cada una de las columnas (e ignorando la IDENTITYpropiedad). Construye la tabla #tmp que necesita, se inserta desde cada una de las bases de datos en su lista y luego selecciona desde #tmp, todo dentro de un solo lote dinámico de SQL y sin usar un WHILEbucle (eso no lo hace mejor, simplemente es más fácil mira y te permite ignorar por Database_Ref_Nocompleto :-)).

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;

El resultado resultante 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;

Cuando esté seguro de que está haciendo lo que espera, simplemente descomente el EXEC.

(Esto confía en que el esquema es el mismo; no valida que una o más de las tablas hayan sido modificadas desde entonces y, como resultado, puede fallar).


¿Por qué eso no genera las especificaciones de identidad?
FindOutIslamNow

1
@Kilanny ¿Leíste la respuesta completa, como la parte en la que hablo de por qué ignoramos la propiedad de identidad?
Aaron Bertrand

Necesito la definición de tabla COMPLETA (incluyendo identidad, índices, restricciones, ...). Afortunadamente encontré este gran script stormrage.com/SQLStuff/sp_GetDDLa_Latest.txt Gracias de todos modos
FindOutIslamNow

@Kilanny Genial. Para ser claros, su requisito no coincide con el requisito en esta pregunta. Necesitaban una copia de la tabla sin identidad porque la estaban usando para copiar datos existentes, sin generar nuevas filas.
Aaron Bertrand

Aaron, esta es una solución elegante en un apuro donde no necesitas llaves, etc ...
GWR

5

No es posible que int T-SQL genere un script de creación completo de una tabla. Al menos no hay forma de construir. siempre puedes escribir tu propio "generador" revisando la información sys.columns.

Pero en su caso no necesita obtener el script de creación completo. Todo lo que necesita es evitar que SELECT INTOcopie la propiedad de identidad. La forma más fácil de hacerlo es agregar un cálculo a esa columna. Entonces en lugar de

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

necesitas escribir

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

Para generar esta declaración, puede usar nuevamente sys.columns como en este Fiddle de SQL

Configuración del esquema de MS SQL Server 2008 :

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

Las dos columnas que necesitamos son namey is_identity: Consulta 1 :

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

Resultados :

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

Con eso podemos usar una CASEdeclaración para generar cada columna para la lista de columnas:

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

Resultados :

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

Con un pequeño truco XML podemos concatenar todo esto para obtener la lista completa de columnas:

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

Resultados :

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

Tenga en cuenta que no puede crear una tabla #temp usando SQL dinámico y usarla fuera de esa declaración ya que la tabla #temp queda fuera de alcance una vez que finalice su declaración sql dinámica. Por lo tanto, debe exprimir todo su código en la misma cadena dinámica de SQL o usar una tabla real. Si necesita poder ejecutar múltiples de estos scripts / procedimientos al mismo tiempo, necesita un nombre de tabla aleatorio, de lo contrario, se pisarán entre sí. Algo así QUOTENAME(N'temp_'+CAST(NEWID() AS NVARCHAR(40))debería ser un nombre lo suficientemente bueno.


En lugar de copiar todos los datos, también puede usar una técnica similar para generar automáticamente una vista para cada tabla que une todas las encarnaciones de esa tabla en todas las bases de datos. Sin embargo, según el tamaño de la tabla, puede ser más rápido o más lento, por lo que debe probarlo. Si sigue esta ruta, pondría esas vistas en una base de datos separada.


1
Puede crear una tabla #temp y luego hacer referencia a ella desde SQL dinámico perfectamente. Solo si lo crea en ese ámbito, no será visible después de que se haya ejecutado el SQL dinámico.
Aaron Bertrand

3

Hay un buen script para lograr esto en el artículo SQLServerCentral:

La última versión actual del script también está disponible como texto aquí (stormrage.com).

Desearía que hubiera una manera de incluir todo el guión aquí, porque funciona para mí. El guión es demasiado largo para pegarlo aquí.

Aviso de 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

Puede generar un bruto CREATE TABLEutilizando SQL dinámico a partir de los datos en INFORMATION_SCHEMA.COLUMNS.

Si necesita agregar restricciones, etc., deberá agregar información de algunas de las otras INFORMATION_SCHEMAvistas.

Documentación de Microsoft

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.