为数据库中的所有表/列生成DDL / DML脚本

时间:2016-05-13 14:58:07

标签: sql sql-server sql-server-2008

这主要是我学习一些SQL Server概念的实验。假设以下情形:

  • 我有一个生产数据库,一个开发数据库和一个测试数据库;
  • 开发数据库比测试数据库更新,包含最近开发的几个新表和列;
  • 我也想更新测试数据库(使用这些新表和列),但不要删除并重新创建该数据库(它包含有用的测试数据)

我在下面编写的脚本是针对“开发”数据库执行的,因此它将生成一个脚本,其中包含数据库的每一列的条件。然后应该使用该脚本来对其他数据库进行更新,并且条件应该添加测试数据库尚未具有的任何列或表:

DECLARE @CURRENT_COLUMN nvarchar(100)
DECLARE @COLUMN_LITERAL nvarchar(100)
DECLARE @CURRENT_DEFAULT nvarchar(20)
DECLARE @CURRENT_DATATYPE nvarchar(100)
DECLARE @CURRENT_SCHEMA nvarchar(100)
DECLARE @SQLA nvarchar(max)
DECLARE @SQLB nvarchar(max)
DECLARE @CURRENT_TABLE nvarchar(100)
DECLARE @COMPUTED smallint
SET @COMPUTED = 0
PRINT '
DECLARE @SQL nvarchar(max)
'
DECLARE CUR_SCHEMA CURSOR FOR
SELECT TABLE_SCHEMA from INFORMATION_SCHEMA.TABLES

OPEN CUR_SCHEMA

    FETCH NEXT FROM CUR_SCHEMA
    INTO @CURRENT_SCHEMA

WHILE @@FETCH_STATUS = 0
BEGIN   


DECLARE CUR_TAB CURSOR FOR
SELECT ist.TABLE_NAME from INFORMATION_SCHEMA.TABLES ist 
WHERE ist.TABLE_SCHEMA = @CURRENT_SCHEMA
AND EXISTS (
        SELECT TOP 1 name
        FROM sys.tables
        where name = ist.TABLE_NAME)
ORDER BY ist.TABLE_NAME

OPEN CUR_TAB

    FETCH NEXT FROM CUR_TAB
    INTO @CURRENT_TABLE


WHILE @@FETCH_STATUS = 0
BEGIN

      PRINT '
      IF OBJECT_ID('''+@CURRENT_TABLE+''') IS NULL
      BEGIN
      SET @SQL = ''
      CREATE TABLE [' + @CURRENT_TABLE +'] (placeholder bit)''
      EXEC sp_executesql @SQL
      END 
      '

DECLARE CUR CURSOR FOR 
SELECT COLUMN_NAME, DATA_TYPE 
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @CURRENT_TABLE
AND TABLE_SCHEMA = @CURRENT_SCHEMA
ORDER BY ORDINAL_POSITION asc

OPEN CUR

    FETCH NEXT FROM CUR 
    INTO @CURRENT_COLUMN, @CURRENT_DATATYPE
    SET @COLUMN_LITERAL = '[' + @CURRENT_COLUMN + ']'

WHILE @@FETCH_STATUS = 0
BEGIN

    SET @SQLB = ''
    SET @COMPUTED = 0

     /* Check if column is computed */

      IF (SELECT is_computed FROM sys.columns 
      WHERE object_id = OBJECT_ID(@CURRENT_TABLE)
      AND name = @CURRENT_COLUMN) = 1
      BEGIN
      SET @SQLB = @SQLB + 'IF NOT EXISTS(SELECT TOP 1 sc.name FROM sys.columns sc
        INNER JOIN sys.tables st ON st.object_id = sc.object_id
        INNER JOIN sys.schemas ss ON ss.schema_id = st.schema_id
        WHERE sc.Name = ''' + @CURRENT_COLUMN + '''
        AND st.Object_ID = OBJECT_ID('''+ @CURRENT_TABLE+ ''')
        AND ss.name = ''' + @CURRENT_SCHEMA + ''')
BEGIN
ALTER TABLE ' + @CURRENT_SCHEMA + '.[' + @CURRENT_TABLE + ']
       ADD ' + @CURRENT_COLUMN + ' AS ' +
      (SELECT definition FROM sys.computed_columns
      WHERE object_id = OBJECT_ID(@CURRENT_TABLE)
      AND name = @CURRENT_COLUMN)

      SET @COMPUTED = 1

      END

      /* Check for identity */

      IF (SELECT is_identity FROM sys.columns WHERE object_id = OBJECT_ID(@CURRENT_TABLE)
      AND name = @CURRENT_COLUMN) = 1
      BEGIN
      SET @SQLB = @SQLB + ' IDENTITY (' + 
      CAST((SELECT IDENT_SEED(@CURRENT_SCHEMA + '.[' + @CURRENT_TABLE + ']')) AS VARCHAR(4)) + ',' +
      CAST((SELECT IDENT_INCR(@CURRENT_SCHEMA + '.[' + @CURRENT_TABLE + ']')) AS VARCHAR(4)) + ')'
      END

      /* Check if NULL is allowed */

      IF (SELECT sc.is_nullable from sys.columns sc
        INNER JOIN sys.tables st ON st.object_id = sc.object_id
        INNER JOIN sys.schemas ss ON ss.schema_id = st.schema_id
        INNER JOIN sys.types sp ON sp.system_type_id = sc.system_type_id
        WHERE st.name = @CURRENT_TABLE
        AND sc.name = @CURRENT_COLUMN
        AND ss.name = @CURRENT_SCHEMA
        AND sp.name = @CURRENT_DATATYPE
      ) = 0
      BEGIN
      SET @SQLB = @SQLB + ' NOT NULL'
      END
      ELSE SET @SQLB = @SQLB + ' NULL' 

      /*  Check for defaults  */

      IF (SELECT COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS 
      WHERE COLUMN_NAME = @CURRENT_COLUMN
      AND TABLE_SCHEMA = @CURRENT_SCHEMA
      AND TABLE_NAME = @CURRENT_TABLE) IS NOT NULL
       BEGIN
      SET @CURRENT_DEFAULT = ' DEFAULT ' + (SELECT COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS 
      WHERE COLUMN_NAME = @CURRENT_COLUMN
      AND TABLE_SCHEMA = @CURRENT_SCHEMA
      AND TABLE_NAME = @CURRENT_TABLE)

      END
ELSE SET @CURRENT_DEFAULT = ''


 IF @CURRENT_DATATYPE in ('date','datetime2','datetime','time',
      'smalldatetime','datetimeoffset','text','ntext',
      'varchar','char','nchar','nvarchar')

  BEGIN

        /*  Check for date related data types  */

   IF @CURRENT_DATATYPE in ('date','datetime2','datetime','time',
      'smalldatetime','datetimeoffset')
      BEGIN
      SET @SQLA = '
    IF NOT EXISTS(SELECT TOP 1 sc.name FROM sys.columns sc
    INNER JOIN sys.tables st ON st.object_id = sc.object_id
    INNER JOIN sys.schemas ss ON ss.schema_id = st.schema_id
    WHERE sc.Name = ''' + @CURRENT_COLUMN + '''
    AND st.Object_ID = OBJECT_ID('''+ @CURRENT_TABLE+ ''')
    AND ss.name = ''' + @CURRENT_SCHEMA + ''')
        BEGIN
        ALTER TABLE ' + @CURRENT_SCHEMA + '.['+ @CURRENT_TABLE + ']
        ADD '+@COLUMN_LITERAL+'' + ' ' + ''+@CURRENT_DATATYPE+' '+@CURRENT_DEFAULT

      END

      /*  Check for MAX column length  */

  IF (SELECT sc.max_length FROM sys.columns sc
INNER JOIN sys.tables st ON st.object_id = sc.object_id
INNER JOIN sys.schemas ss ON ss.schema_id = st.schema_id
INNER JOIN sys.types sp ON sp.system_type_id = sc.system_type_id
WHERE st.name = @CURRENT_TABLE
AND sc.name = @CURRENT_COLUMN
AND ss.name = @CURRENT_SCHEMA
AND sp.name = @CURRENT_DATATYPE) = -1

BEGIN

SET @SQLA = '
    IF NOT EXISTS(SELECT TOP 1 sc.name FROM sys.columns sc
    INNER JOIN sys.tables st ON st.object_id = sc.object_id
    INNER JOIN sys.schemas ss ON ss.schema_id = st.schema_id
    WHERE sc.Name = ''' + @CURRENT_COLUMN + '''
    AND st.Object_ID = OBJECT_ID('''+ @CURRENT_TABLE+ ''')
    AND ss.name = ''' + @CURRENT_SCHEMA + ''')
        BEGIN
        ALTER TABLE ' + @CURRENT_SCHEMA + '.['+ @CURRENT_TABLE + ']
        ADD '+@COLUMN_LITERAL+'' + ' ' + ''+@CURRENT_DATATYPE+'(MAX)'+' ' + @CURRENT_DEFAULT
END

      /*  Check for string data types  */

ELSE IF @CURRENT_DATATYPE in ('varchar','char','nchar','nvarchar')
BEGIN
SET @SQLA = '
    IF NOT EXISTS(SELECT TOP 1 sc.name FROM sys.columns sc
    INNER JOIN sys.tables st ON st.object_id = sc.object_id
    INNER JOIN sys.schemas ss ON ss.schema_id = st.schema_id
    WHERE sc.Name = ''' + @CURRENT_COLUMN + '''
    AND st.Object_ID = OBJECT_ID('''+ @CURRENT_TABLE+ ''')
    AND ss.name = ''' + @CURRENT_SCHEMA + ''')
        BEGIN
        ALTER TABLE ' + @CURRENT_SCHEMA + '.[' + @CURRENT_TABLE + ']
        ADD '+@COLUMN_LITERAL+'' + ' ' + ''+@CURRENT_DATATYPE+''
        + '(' + 
        CAST( 
         ( SELECT 
         CASE WHEN @CURRENT_DATATYPE IN ('nchar', 'nvarchar') THEN MAX(sc.max_length)/2
         ELSE MAX(sc.max_length) END AS 'max_length' FROM sys.columns sc
        INNER JOIN sys.tables st ON st.object_id = sc.object_id
        INNER JOIN sys.schemas ss ON ss.schema_id = st.schema_id
        INNER JOIN sys.types sp ON sp.system_type_id = sc.system_type_id
        WHERE st.name = @CURRENT_TABLE
        AND sc.name = @CURRENT_COLUMN
        AND ss.name = @CURRENT_SCHEMA
        AND sp.name = @CURRENT_DATATYPE
        ) 
        AS VARCHAR(10)) +')'+@CURRENT_DEFAULT

END

     /*  Check for text and ntext types (no column width)  */

ELSE IF @CURRENT_DATATYPE in ('text','ntext')
BEGIN
SET @SQLA = '
    IF NOT EXISTS(SELECT TOP 1 sc.name FROM sys.columns sc
    INNER JOIN sys.tables st ON st.object_id = sc.object_id
    INNER JOIN sys.schemas ss ON ss.schema_id = st.schema_id
    WHERE sc.Name = ''' + @CURRENT_COLUMN + '''
    AND st.Object_ID = OBJECT_ID('''+ @CURRENT_TABLE+ ''')
    AND ss.name = ''' + @CURRENT_SCHEMA + ''')
        BEGIN
        ALTER TABLE ' + @CURRENT_SCHEMA + '.[' + @CURRENT_TABLE + ']
        ADD '+@COLUMN_LITERAL+'' + ' ' + ''+@CURRENT_DATATYPE+' '+@CURRENT_DEFAULT

END


  END
ELSE

        /*  Check for decimal and numeric types  */

IF @CURRENT_DATATYPE in ('decimal','numeric')
  BEGIN


SET @SQLA = '
    IF NOT EXISTS(SELECT TOP 1 sc.name FROM sys.columns sc
    INNER JOIN sys.tables st ON st.object_id = sc.object_id
    INNER JOIN sys.schemas ss ON ss.schema_id = st.schema_id
    WHERE sc.Name = ''' + @CURRENT_COLUMN + '''
    AND st.Object_ID = OBJECT_ID('''+ @CURRENT_TABLE+ ''')
    AND ss.name = ''' + @CURRENT_SCHEMA + ''')
        BEGIN
        ALTER TABLE '  + @CURRENT_SCHEMA + '.[' + @CURRENT_TABLE + ']
         ADD '+@COLUMN_LITERAL+'' + ' ' + ''+@CURRENT_DATATYPE+''+'(' + CAST( (SELECT MIN(NUMERIC_PRECISION) FROM INFORMATION_SCHEMA.COLUMNS
        WHERE TABLE_NAME = @CURRENT_TABLE
        AND COLUMN_NAME = @CURRENT_COLUMN
        AND TABLE_SCHEMA = @CURRENT_SCHEMA
        AND DATA_TYPE = @CURRENT_DATATYPE
        ) AS VARCHAR(10)) + ',' + 

        CAST( (SELECT MIN(NUMERIC_SCALE) FROM INFORMATION_SCHEMA.COLUMNS
        WHERE TABLE_NAME = @CURRENT_TABLE
        AND COLUMN_NAME = @CURRENT_COLUMN
        AND DATA_TYPE = @CURRENT_DATATYPE
        ) AS VARCHAR(10)) + ')'+ @CURRENT_DEFAULT

  END
ELSE  
  BEGIN
SET @SQLA = '
    IF NOT EXISTS(SELECT TOP 1 sc.name FROM sys.columns sc
    INNER JOIN sys.tables st ON st.object_id = sc.object_id
    INNER JOIN sys.schemas ss ON ss.schema_id = st.schema_id
    WHERE sc.Name = ''' + @CURRENT_COLUMN + '''
    AND st.Object_ID = OBJECT_ID('''+ @CURRENT_TABLE+ ''')
    AND ss.name = ''' + @CURRENT_SCHEMA + ''')
        BEGIN
        ALTER TABLE ' + @CURRENT_SCHEMA + '.[' + @CURRENT_TABLE + ']
        ADD '+@COLUMN_LITERAL+'' + ' ' + ''+@CURRENT_DATATYPE+''+@CURRENT_DEFAULT


END

IF @COMPUTED = 0
BEGIN

    PRINT @SQLA + @SQLB + '
    END
    '


END

    FETCH NEXT FROM CUR 
    INTO @CURRENT_COLUMN, @CURRENT_DATATYPE
    SET @COLUMN_LITERAL = '[' + @CURRENT_COLUMN + ']'

END 
CLOSE CUR;
DEALLOCATE CUR;

 PRINT '
    IF EXISTS
    (SELECT TOP 1 sc.name FROM sys.columns sc
    INNER JOIN sys.tables st ON st.object_id = sc.object_id
    INNER JOIN sys.schemas ss ON ss.schema_id = st.schema_id
    WHERE sc.Name = ''placeholder''
    AND st.Object_ID = OBJECT_ID('''+ @CURRENT_TABLE+ ''')
    AND ss.name = ''' + @CURRENT_SCHEMA + ''')
            BEGIN
            ALTER TABLE '+@CURRENT_SCHEMA+'.['+@CURRENT_TABLE+'] DROP COLUMN [placeholder]
            END

            '

   FETCH NEXT FROM CUR_TAB
   INTO @CURRENT_TABLE

END
CLOSE CUR_TAB
DEALLOCATE CUR_TAB

END
CLOSE CUR_SCHEMA
DEALLOCATE CUR_SCHEMA

问题:

  • 我可以避免使用“占位符”列吗? (我添加了它,因为我不能创建空列,以防它们不存在)。
  • 是否接受使用三个游标?我相信这可以简化,可能使用临时表或表变量。
  • 我的方法是捕捉异常格式化案例(如数字数据类型定义或最大列字符长度)的内聚力?
  • 脚本完全正确吗?我针对真实数据库的副本对其进行了密集测试,并测试了它针对空白数据库生成的脚本,并且它似乎产生了预期的结果。
  • 我使用的变量数量是否过多?我的变数是否无关紧要?
  • 是否可以使用 INFORMATION_SCHEMA 系统表? (我曾多次使用 INFORMATION_SCHEMA 来避免过多的表格加入。)
  • 我正确使用游标吗?
  • 您是否会对我的某些部分建议采用不同的方法?

谢谢,很抱歉提出这么多问题。如果您不想回答所有问题,请回答一个或几个!

** 注意 **

  • 我为SQL Server 2008数据库写了这个,但你可以指出新版本的替代品来帮助提高我的知识
  • 我知道这个脚本不会复制存储过程,触发器和其他东西,但之后可以使用SSMS自动编写脚本,因此我只在脚本中包含列属性。

1 个答案:

答案 0 :(得分:1)

所以我建议使用Sql Server数据工具和数据库项目。它允许您导入现有数据库的模式或创建新的数据库项目。您可以将它链接到TFS或Git,您对源代码管理的偏好。在这种情况下,您不必使用单独的SQL脚本来生成架构。如果您决定从SSDT转移到架构开发,那么代码库将始终具有最新的已知架构。然后,您可以生成用于部署新代码库和SSDT的脚本,或者Visual Studio会将SQL计算出来,我强烈建议在部署到prod之前检查这些脚本。 也可以从此工具发布对目标数据库的更改。

SSDT