數據庫表結構比對工具

一、概述

       在軟件實施過程中,我遇到了這麼一個問題,我在客戶那邊安裝了整個ERP程序並且向數據庫中導入了客戶提供給我的基礎資料,程序進入試運行階段,在試運行期間,客戶反映出程序的各種問題,需要提交給公司去糾正這些問題,在糾正問題的時候伴隨的問題的解決,程序開發人員向數據庫中添加了數據庫表或者向已有表中添加了字段,這個時候開發員沒有一個良好的習慣用SQL語法來操作這些添加,而是直接在數據庫設計器中添加,在實施人員給客戶更新程序的時候需要重新還原數據庫,導致之前已經導入的基礎資料需要重新做處理,做了重複工作量,我作爲一個實施人員爲了讓自己工作更輕鬆,編寫了這麼一個小工具,網上搜索了一下,很多這種的小工具但是並不適合我,自己動手豐衣足食,接下來我們探討一下這個小工具的實現。

二、小工具設計思路

      需要兩個數據庫進行比較,那麼我們需要兩個不同的數據庫甚至連接兩個不同的數據庫服務器,針對不同的數據庫服務器中的兩個數據庫進行表結構比對(大家可以考慮存儲過程,函數等的比對,比對過程都不會難),想到這些我們就可以開始着手開始設計程序了,設計界面如下圖所示:

三、程序設計

獲取表結構的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/ 此處安裝使用,使用過程中有什麼問題可以回帖提出,最好的是自己手動編寫一個滿足自己需求的。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章