数据库表结构比对工具

一、概述

       在软件实施过程中,我遇到了这么一个问题,我在客户那边安装了整个ERP程序并且向数据库中导入了客户提供给我的基础资料,程序进入试运行阶段,在试运行期间,客户反映出程序的各种问题,需要提交给公司去纠正这些问题,在纠正问题的时候伴随的问题的解决,程序开发人员向数据库中添加了数据库表或者向已有表中添加了字段,这个时候开发员没有一个良好的习惯用SQL语法来操作这些添加,而是直接在数据库设计器中添加,在实施人员给客户更新程序的时候需要重新还原数据库,导致之前已经导入的基础资料需要重新做处理,做了重复工作量,我作为一个实施人员为了让自己工作更轻松,编写了这么一个小工具,网上搜索了一下,很多这种的小工具但是并不适合我,自己动手丰衣足食,接下来我们探讨一下这个小工具的实现。

二、小工具设计思路

      需要两个数据库进行比较,那么我们需要两个不同的数据库甚至连接两个不同的数据库服务器,针对不同的数据库服务器中的两个数据库进行表结构比对(大家可以考虑存储过程,函数等的比对,比对过程都不会难),想到这些我们就可以开始着手开始设计程序了,设计界面如下图所示:

数据库表结构比对工具_第1张图片

三、程序设计

获取表结构的SQL语法:

USE DBName--换成你自己的数据库名称

SELECT distinct 表名 = OBJECT_NAME(c.object_id),
                    表描述 = (SELECT top 1 a.[value] FROM sys.extended_properties a left JOIN  sysobjects b ON a.major_id=b.id WHERE b.name=OBJECT_NAME(c.object_id) and a.minor_id=0 ),
                    字段名 = c.name, 字段描述 = ex.value, 字段类型 = t.name, 字段长度 = c.max_length, 位数 = c.precision, 小数位 = c.scale
                    FROM sys.columns c LEFT OUTER JOIN sys.extended_properties ex
                    ON ex.major_id = c.object_id AND ex.minor_id = c.column_id AND ex.name = 'MS_Description'
                    left outer join systypes t on c.system_type_id = t.xtype WHERE
                    OBJECTPROPERTY(c.object_id, 'IsMsShipped') = 0 AND
                    t.name != 'sysname'

编写一个方法放入上述SQL 返回一个datatable。

获取数据库中表的创建语法:

USE DBName--此处替换为你自己的数据库名称

SET ARITHABORT ON
SET CONCAT_NULL_YIELDS_NULL ON
SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
SET NUMERIC_ROUNDABORT OFF

declare @crlf char(2)
SET @crlf=char(13)+char(10)

;WITH ColumnDefs as
(
    select TableObj=c.[object_id]
        ,ColSeq=c.column_id
        ,ColumnDef=quotename(c.Name)+' '
                    +case
                        when c.is_computed=1 then 'as '+coalesce(k.[definition],'')
                            +case when k.is_persisted=1 then ' PERSISTED'+case when k.is_nullable=0 then ' NOT NULL' else '' end else '' end
                        else DataType
                            +case
                            when DataType in ('decimal','numeric') then '('+cast(c.precision as varchar(10))+case when c.scale<>0 then ','+cast(c.scale as varchar(10)) else '' end +')'
                            when DataType in ('char','varchar','nchar','nvarchar','binary','varbinary') then '('+case when c.max_length=-1 then 'max' else case when DataType in ('nchar','nvarchar') then cast(c.max_length/2 as varchar(10)) else cast(c.max_length as varchar(10)) end end +')'
                            when DataType='float' and c.precision<>53 then '('+cast(c.precision as varchar(10))+')'
                            when DataType in ('time','datetime2','datetimeoffset') and c.scale<>7 then '('+cast(c.scale as varchar(10))+')'
                            else ''
                            end
                    end
                    +case when c.is_identity=1 then ' IDENTITY('+cast(IDENT_SEED(quotename(object_schema_name(c.[object_id]))+'.'+quotename(object_name(c.[object_id]))) as varchar(30))+','+cast(ident_incr(quotename(object_schema_name(c.[object_id]))+'.'+quotename(object_name(c.[object_id]))) as varchar(30))+')' else '' end
                    +case when c.is_rowguidcol=1 then ' ROWGUIDCOL' else '' end
                    +case when c.xml_collection_id>0 THEN ' (CONTENT '+QUOTENAME(SCHEMA_NAME(x.SCHEMA_ID))+'.'+ QUOTENAME(x.name)+')' ELSE '' end
                    +case
                        when c.is_computed=0 and UserDefinedFlag=0
                        then case
                            when c.collation_name<>cast(databasepropertyex(db_name() ,'collation') as nvarchar(128))
                            then ' COLLATE '+c.collation_name
                            else ''
                            end
                        else ''
                    end
                    +case when c.is_computed=0 then case when c.is_nullable=0 then ' NOT' else '' end+' NULL' else '' end
                    +case
                        when c.default_object_id>0
                        then ' CONSTRAINT '+quotename(d.name)+' DEFAULT '+coalesce(d.[definition],'')
                        else ''
                    end
                
    from sys.columns c
    cross apply ( 
    select DataType=type_name(c.user_type_id)
            ,UserDefinedFlag=case
                                when c.system_type_id=c.user_type_id
                                then 0
                                else 1
                                end) F1
    left join sys.default_constraints d ON c.default_object_id=d.[object_id]
    left join sys.computed_columns k ON c.[object_id]=k.[object_id]
                                    and c.column_id=k.column_id
    left join  sys.xml_schema_collections x ON c.xml_collection_id = x.xml_collection_id                           
)
,IndexDefs as
(
    select TableObj=i.[object_id]
        ,IxName=quotename(i.name)
        ,IxPKFlag=i.is_primary_key
        ,IxType=case when i.is_primary_key=1 then 'PRIMARY KEY ' when i.is_unique=1 then 'UNIQUE ' else '' end
                +lower(type_desc)
        ,IxDef='('+IxColList+')'
                +coalesce(' INCLUDE ('+IxInclList+')','')
        ,IxOpts=IxOptList       
    from sys.indexes i
    left join sys.stats s ON i.index_id=s.stats_id and i.[object_id]=s.[object_id]
    cross apply ( 
    select stuff((select case when i.is_padded=1 then ', PAD_INDEX=ON' else '' end
                        +case when i.fill_factor<>0 then ', FILLFACTOR='+cast(i.fill_factor as varchar(10)) else '' end
                        +case when i.ignore_dup_key=1 then ', IGNORE_DUP_KEY=ON' else '' end
                        +case when s.no_recompute=1 then ', STATISTICS_RECOMPUTE=ON' else '' end
                        +case when i.allow_row_locks=0 then ', ALLOW_ROW_LOCKS=OFF' else '' end
                        +case when i.allow_page_locks=0 then ', ALLOW_PAGE_LOCKS=OFF' else '' end)
                    ,1,2,'')) F_IxOpts(IxOptList)
    cross apply ( 
    select stuff((select ','+quotename(c.name)
                        +case
                            when ic.is_descending_key=1 AND i.type<>3
                            then ' DESC'
                            WHEN ic.is_descending_key=0 AND i.type<>3
                            THEN ' ASC'
                            ELSE ''
                            end
                    from sys.index_columns ic
                    join sys.columns c ON ic.[object_id]=c.[object_id]
                                        and ic.column_id=c.column_id
                    where ic.[object_id]=i.[object_id]
                    and ic.index_id=i.index_id
                    and ic.is_included_column=0
                    order by ic.key_ordinal
                    FOR xml path(''),type).value('.','nvarchar(max)')
                ,1,1,'')) F_IxCols(IxColList)
    cross apply ( 
    select stuff((select ','+quotename(c.name)
                    from sys.index_columns ic
                    join sys.columns c ON ic.[object_id]=c.[object_id]
                                        and ic.column_id=c.column_id
                    where ic.[object_id]=i.[object_id]
                    and ic.index_id=i.index_id
                    and ic.is_included_column=1
                    order by ic.key_ordinal
                    FOR xml path(''),type).value('.','nvarchar(max)')
                ,1,1,'')) F_IxIncl(IxInclList)
    where i.type_desc<>'HEAP'
)
,FKDefs as
(
    select TableObj=f.parent_object_id
        ,FKName=quotename(f.name)
        ,FKRef=quotename(object_schema_name(f.referenced_object_id))+'.'
                +quotename(object_name(f.referenced_object_id))
        ,FKColList=ParentColList
        ,FKRefList=RefColList
        ,FKDelOpt=case f.delete_referential_action
                    when 1 then 'CASCADE'
                    when 2 then 'SET NULL'
                    when 3 then 'SET DEFAULT'
                    end
        ,FKUpdOpt=case f.update_referential_action
                    when 1 then 'CASCADE'
                    when 2 then 'SET NULL'
                    when 3 then 'SET DEFAULT'
                    end
        ,FKNoRepl=f.is_not_for_replication
    from sys.foreign_keys f
    cross apply ( 
    select stuff((select ','+quotename(c.name)
                    from sys.foreign_key_columns k
                    join sys.columns c ON k.parent_object_id=c.[object_id]
                                        and k.parent_column_id=c.column_id
                    where k.constraint_object_id=f.[object_id]
                    order by constraint_column_id
                    FOR xml path(''),type).value('.','nvarchar(max)')
                ,1,1,'')) F_Parent(ParentColList)
    cross apply (
    select stuff((select ','+quotename(c.name)
                    from sys.foreign_key_columns k
                    join sys.columns c ON k.referenced_object_id=c.[object_id]
                                        and k.referenced_column_id=c.column_id
                    where k.constraint_object_id=f.[object_id]
                    order by constraint_column_id
                    FOR xml path(''),type).value('.','nvarchar(max)')
                ,1,1,'')) F_Ref(RefColList)
)
select TableName
        ,[definition]
from sys.tables t
cross apply ( 
    select TableName=quotename(object_schema_name(t.[object_id]))+'.'
                    +quotename(object_name(t.[object_id]))) F_Name
cross apply (
    select stuff((select @crlf+'  ,'+ColumnDef
                from ColumnDefs
                where TableObj=t.[object_id]
                order by ColSeq
                FOR xml path(''),type).value('.','nvarchar(max)')
                ,1,5,'')) F_Cols(ColumnList)
cross apply (
    select stuff((select @crlf+'  ,CONSTRAINT '+quotename(name)+' CHECK '
                        +case when is_not_for_replication=1 then 'NOT FOR REPLICATION ' else '' end
                        +coalesce([definition],'')
                from sys.check_constraints
                where parent_object_id=t.[object_id]
                FOR xml path(''),type).value('.','nvarchar(max)')
                ,1,2,'')) F_Const(ChkConstList)
cross apply (
    select stuff((select @crlf+'  ,CONSTRAINT '+IxName+' '+IxType+' '+IxDef+coalesce(' WITH ('+IxOpts+')','')
                from IndexDefs
                where TableObj=t.[object_id]
                    and IxPKFlag=1
                FOR xml path(''),type).value('.','nvarchar(max)')
                ,1,2,'')) F_IxConst(IxConstList)
cross apply (
    select stuff((select @crlf+'  ,CONSTRAINT '+FKName+' FOREIGN KEY '+'('+FKColList+')'+' REFERENCES '+FKRef+' ('+FKRefList+')'
                        +case when FKDelOpt is NOT NULL then ' ON DELETE '+FKDelOpt else '' end
                        +case when FKUpdOpt is NOT NULL then ' ON UPDATE '+FKUpdOpt else '' end
                        +case when FKNoRepl=1 then ' NOT FOR REPLICATION' else '' end
                from FKDefs
                where TableObj=t.[object_id]
                FOR xml path(''),type).value('.','nvarchar(max)')
                ,1,2,'')) F_Keys(FKConstList)
cross apply (
    select stuff((select @crlf+'CREATE '+IxType+' INDEX '+IxName+' ON '+TableName+' '+IxDef+coalesce(' WITH ('+IxOpts+')','')
                from IndexDefs
                where TableObj=t.[object_id]
                    and IxPKFlag=0
                FOR xml path(''),type).value('.','nvarchar(max)')
                ,1,2,'')) F_Indexes(IndexList)
cross apply (
    select [definition]=(select 'CREATE TABLE '+TableName+@crlf+'('+@crlf+'   '+ColumnList+coalesce(@crlf+ChkConstList,'')+coalesce(@crlf+IxConstList,'')+coalesce(@crlf+FKConstList,'')+@crlf+')'+coalesce(@crlf+IndexList,'')+@crlf
    FOR xml path(''),type).value('.','nvarchar(max)')) F_Link

这个SQL语法是网上某位大神的,我收藏了很久了都忘记在哪里了,这里不能提供出处,望见谅。

在此处也一样的编写一个方法来执行上述SQL 返回一个datatable

做好上述准备以后我们就可以来实现 表结构比对方法了,此时我们需要考虑一下几点:

1.目标数据库中无来源数据库中表的情况下,获取创建表语法

2.目标数据库中存在来源数据库中表的情况下,进行表字段比较

3.综合第2点,目标数据表中无来源数据表中的字段,则在目标数据表中添加来源表的字段

满足上述这些要求我们就可以设计出一个比对方法了。接下来提供数据库表字段的添加SQL语法和字段描述的SQL语法如下:

--添加数据库字段的语法

strbSQLScript.Append("IF NOT EXISTS(select * from syscolumns where id=object_id('" + dr["表名"].ToString() + "') and name='" + dr["字段名"].ToString() + "') \r\n");
                            strbSQLScript.Append("Alter table " + dr["表名"].ToString() + " Add " + dr["字段名"].ToString() + " " + dr["字段类型"].ToString() + "(" + dr["字段长度"].ToString() + ") null \r\n");
                            strbSQLScript.Append("GO \r\n");

--添加数据库字段描述的语法
                            strbSQLScript.Append("IF NOT EXISTS (SELECT * FROM ::fn_listextendedproperty(N'MS_Description' , N'SCHEMA',N'dbo', N'TABLE',N'" + dr["表名"].ToString() + "', N'COLUMN',N'" + dr["字段名"].ToString() + "')) \r\n");
                            strbSQLScript.Append("EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'" + dr["字段描述"].ToString() + "' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'" + dr["表名"].ToString() + "', @level2type=N'COLUMN',@level2name=N'" + dr["字段名"].ToString() + "' \r\n");
                            strbSQLScript.Append("GO \r\n");

这两个语法我直接放代码上来了,我用的foreach循环。
这个工具可以从 http://www.egbt.net/MSSQLDatabaseTools/ 此处安装使用,使用过程中有什么问题可以回帖提出,最好的是自己手动编写一个满足自己需求的。



你可能感兴趣的:(工具及使用,C#)