Quantcast
Channel: CodeSection,代码区,数据库(综合) - CodeSec
Viewing all articles
Browse latest Browse all 6262

SQL应用之跨数据库服务器数据库结构同步实现(MSSQL)

$
0
0
一、前言

什么是“跨数据库服务器数据库结构同步”?

跨数据库服务器数据库结构同步,这里包含以下几个方面的问题

跨数据库服务器:两个数据库分别位于不同的数据库服务器 数据库结构:由数据库对象(表、视图、存储过程、函数等)组成的数据库结构 同步:要求将一个数据库结构的修改更新到另外一个数据库中,这里的修改可以是数据库对象的创建、删除、数据类型的修改等等
“跨数据库服务器的数据库结构同步”到底有什么用呢?
在发布系统时,经常要将开发库的数据库结构(表结构、视图、存储过程等)同步到生产库中。尽管也有相应的文档和规范,要求数据库修改必须要有记录,但是根据实践发现,总会有一些”漏网之鱼”。这样就急需一个自动发现数据库结构修改的工具——数据库结构差异发现。

开发环境与生产环境是分离开的,因此还要处理数据库异地的问题。异地数据库结构的同步,还会涉及执行权限的问题。

二、远程数据库结构同步实现方案

这个实现是本人几年前所作,其设计与实现思路请详见下文中的备注。备注中记录了实现过程中遇到的一些问题。

使用说明:
- @sserver:表示远程服务器所在IP,如两数据库都在本地,置空即可
- @sdbname:表示源数据库名称
- @tserver:表示目标数据库所在服务器的IP
- @tdbname:表示目标数据库名称

注意:如涉及远程服务器,请先创建“链接服务器”,详情请查看 SQL总结之跨数据库服务器之间的数据访问

警告:本实现方案只经过了本人简单测试,分享出来仅供学习探讨。在生产环境中请谨慎使用!

-- ==========================================================
-- /******************* 数据库结构同步 *******************/
-- 1、同步的范围:视图、存储过程、函数、触发器,也包括数据库表结构
-- 2、思路:
-- (1)计算出视图、存储过程、函数、触发器、表(索引、主键、外键、默认值约束、唯一约束、Check约束)等对象的依赖关系
-- (2)获取脚本对象(视图、存储过程、函数、触发器)的创建脚本、表内部对象(索引、主键、外键、默认值约束、唯一约束、Check约束)创建所必须的相关参数
-- (3)表字段等元数据信息
-- (4)比较对象,需要执行的操作(add、modify)
-- (5)对于modify的(视图、存储过程、函数、触发器)对象,更新前将脚本备份到txt中,然后再执行更新脚本操作
-- (6)对于add操作的字段,如果所属表不存在,不作操作
-- (7)更新失败后,可以通过txt恢复结构
-- (8)表与字段的备注信息也要更新
-- (9)预防措施:<1>数据库备份;<2>将需要更新的对象脚本保存到txt;<3>错误信息日志;
-- 3、注意
-- (1)视图、存储过程、函数、触发器修改名称时,请即时右键修改脚本重新执行一次(对象修改名称不触发创建脚本中对象名称的修改)
-- (2)默认schema_name为dbo,不支持自定义的其它schema_name
-- (3)不支持自定义数据类型type的更新
-- (4)暂不支持Synonyms的更新(目前用不着)
-- /*********************************************/
-- /************ 将数据库清空重置 *****************/
-- 1、功能范围:(1)删除数据 (2)将表的自增ID重置到0
-- 2、问题:数据库表相互依赖,不易清空数据
-- 3、预防措施:清空数据前数据库备份
/************************************************/
-- ==========================================================
SET NOCOUNT ON; -- 不显示影响行数
SET ANSI_WARNINGS OFF; -- 不显示警告信息(聚合函数在遇到字段NULL值情况会出现警告)
declare @sserver varchar(50) /*= '[172.16.8.95]'*/,@sdbname varchar(50) = 'HRDB',
@tserver varchar(50),@tdbname varchar(50) = 'HRDBDev',
@scollation varchar(50) = cast((select SERVERPROPERTY(N'collation')) as varchar(50)),@tcollation varchar(50)
/*
1、处理字符串:
(1)动态SQL不支持点位符,输入变量作用有限,不可作表名用;
(2)Replace字符串函数可以处理超长字符:注意,输入类型与返回类型保持一致,返回类型超出输入类型的最大长度时将被截取,因此最好输入类型设置max
(3)print字符数量有限:varchar(max) 和 nvarchar(max) 数据类型均被截断为不大于 varchar(8000) 和 nvarchar(4000) 的数据类型
set @execsql = REPLACE(@sql,'#tb','#t')
set @execsql = REPLACE(@execsql,'[@dbname]',@source)
2、用openquery行集函数取代动态服务器链接的访问方式(4秒--47秒)
(1)动态链接访问方式(select * from [172.16.8.95].dbname.dbo.table)不要对远程对象使用元数据函数(object_name),必须通过远程对象获取相应信息
(2)动态服务器链接耗时47秒,而openquery用时4秒,可能的原因是动态服务器链接是将每个对象数据集从远程取出然后在本地处理,而不是在远程将数据集处
理好后返回最终数据集
(3)openquery行集函数中,第二个参数是查询sql,只能用常量且字符不超8000,需要使用动态SQL将变量变成常量再执行
*/
declare @sql varchar(max),@colsql varchar(max),@execsql nvarchar(max),@execDepSql nvarchar(max)
set @sql = 'select t.type,t.object_id,t.name,t.parent_object_id,t2.name as pname,
case when t.type in(''P'',''V'',''TR'',''FN'',''TF'') then m.definition
when t.type in(''PK'',''UQ'') then (case when t.type in(''PK'') then ''PRIMARY KEY'' else ''UNIQUE'' end) + '' ('' +
STUFF((select '','' + col.name from [@dbname].sys.index_columns indcol
left join [@dbname].sys.columns col on col.object_id = t.parent_object_id and col.column_id = indcol.column_id
where t.type in(''PK'',''UQ'') and indcol.object_id = ind.object_id and indcol.index_id = ind.index_id
for xml path('''')),1,1,'''') + '')''
when t.type in(''F'') then (''FOREIGN KEY ('' +
STUFF((select '','' + col.name from [@dbname].sys.foreign_key_columns kcol
left join [@dbname].sys.columns col on col.object_id = kcol.parent_object_id and col.column_id = kcol.parent_column_id
where t.type in(''F'') and kcol.constraint_object_id = k.object_id
for xml path('''')),1,1,'''') + '') REFERENCES ''
+ t2.name + '' (''
+ STUFF((select '','' + rcol.name from [@dbname].sys.foreign_key_columns kcol
left join [@dbname].sys.columns rcol on rcol.object_id = kcol.referenced_object_id and rcol.column_id = kcol.referenced_column_id
where t.type in(''F'') and kcol.constraint_object_id = k.object_id
for xml path('''')),1,1,'''') + '') ''
+ (case when k.delete_referential_action = 0 then '''' else '' ON DELETE '' + k.delete_referential_action_desc collate Chinese_PRC_CI_AS end)
+ (case when k.update_referential_action = 0 then '''' else '' ON UPDATE '' + k.update_referential_action_desc collate Chinese_PRC_CI_AS end)
+ (case when k.is_not_for_replication = 1 then '' NOT FOR REPLICATION'' else '''' end))
when t.type in(''D'') then ''DEFAULT '' + dcons.definition + '' FOR '' + dcons.colname
when t.type in(''C'') then ''CHECK '' + (case when ckcons.is_not_for_replication = 1 then '' NOT FOR REPLICATION'' else '''' end)
+ ckcons.definition
when t.type in(''U'') then ''CREATE TABLE '' + QUOTENAME(t.name) + '' ('' +
REPLACE(STUFF((select '','' + CHAR(13) + CHAR(10) + CHAR(9) + col.name + '' '' +
(case when tp.name in (''decimal'',''numeric'') then tp.name + ''('' + cast(col.precision as varchar) + '','' + cast(col.scale as varchar) + '')''
when tp.name in (''float'') and col.precision <> tp.precision then tp.name + ''('' + cast(col.precision as varchar) + '')''
when tp.name in (''binary'',''char'') and col.max_length <> 1 then tp.name + ''('' + cast(col.max_length as varchar) + '')''
when tp.name in (''varbinary'',''varchar'') and col.max_length <> 1 then tp.name + ''('' + (case when col.max_length = -1 then ''max'' else cast(col.max_length as varchar) end) + '')''
when tp.name in (''nchar'',''nvarchar'') and col.max_length <> 1 then tp.name + ''('' + (case when col.max_length = -1 then ''max'' else cast(col.max_length/2 as varchar) end) + '')''
else tp.name end) +
isnull('' '' +col.collation_name,'''') +
(case when col.is_nullable = 1 then '''' else '' NOT NULL'' end) +
(case when col.is_identity = 1 then '' IDENTITY'' + (case when seed_value = 1 and increment_value = 1 then '''' else '' '' + cast(seed_value as varchar) + '','' + cast(increment_value as varchar) + '')'' end) else '''' end) +
(case when col.default_object_id <> 0 then '' DEFAULT '' + df.definition else '''' end) +
(case when col.is_sparse = 1 then '' SPARSE'' else '''' end +
(case when col.is_column_set = 1 then '' XML COLUMN_SET FOR ALL_SPARSE_COLUMNS'' else '''' end) +
(case when col.is_computed = 1 then '' AS '' + cmp.definition else '''' end))
from [@dbname].sys.columns col
left join [@dbname].sys.types tp on tp.user_type_id = col.user_type_id
left join [@dbname].sys.identity_columns iden on iden.object_id = col.object_id and iden.column_id = col.column_id
left join [@dbname].sys.computed_columns cmp on cmp.object_id = col.object_id and cmp.column_id = col.column_id
left join [@dbname].sys.default_constraints df on df.object_id = col.default_object_id
where col.object_id = t.object_id for xml path('''')),1,1,''''),''
'','''') +
ISNULL(REPLACE((select '','' + CHAR(13) + CHAR(10) + CHAR(9) + ''CONSTRAINT '' + ind.name + '' '' +
(case when o.type in(''PK'') then ''PRIMARY KEY'' else ''UNIQUE'' end) + '' ('' +
STUFF((select '','' + col.name from [@dbname].sys.index_columns indcol
left join [@dbname].sys.columns col on col.object_id = ind.object_id and col.column_id = indcol.column_id
where indcol.object_id = ind.object_id and indcol.index_id = ind.index_id
for xml path('''')),1,1,'''') + '')''
from [@dbname].sys.indexes ind
left join [@dbname].sys.objects o on o.name = ind.name
where o.type in(''PK'',''UQ'') and ind.object_id = t.object_id for xml path('''')),''
'',''''),'''') +
ISNULL(REPLACE((select '','' + CHAR(13) + CHAR(10) + CHAR(9) + ''CONSTRAINT '' + k.name + '' FOREIGN KEY ('' +
STUFF((select '','' + col.name from [@dbname].sys.foreign_key_columns kcol
left join [@dbname].sys.columns col on col.object_id = kcol.parent_object_id and col.column_id = kcol.parent_column_id
where t.type in(''F'') and kcol.constraint_object_id = k.object_id
for xml path('''')),1,1,'''') + '') REFERENCES ''
+ t.name + '' (''
+ STUFF((select '','' + rcol.name from [@dbname].sys.foreign_key_columns kcol
left join [@dbname].sys.columns rcol on rcol.object_id = kcol.referenced_object_id and rcol.column_id = kcol.referenced_column_id
where t.type in(''F'') and kcol.constraint_object_id = k.object_id
for xml path('''')),1,1,'''') + '') ''
+ (case when k.delete_referential_action = 0 then '''' else '' ON DELETE '' + k.delete_referential_action_desc collate Chinese_PRC_CI_AS end)
+ (case when k.update_referential_action = 0 then '''' else '' ON UPDATE '' + k.update_referential_action_desc collate Chinese_PRC_CI_AS end)
from [@dbname].sys.foreign_keys k
where k.parent_object_id = t.object_id for xml path('''')),''
'',''''),'''') +
ISNULL(REPLACE((select '','' + CHAR(13) + CHAR(10) + CHAR(9) + ''CONSTRAINT '' + ckcons.name + '' CHECK ''
+ (case when ckcons.is_not_for_replication = 1 then '' NOT FOR REPLICATION'' else '''' end)
+ ckcons.definition
from [@dbname].sys.check_constraints ckcons
where ckcons.parent_object_id = t.object_id for xml path('''')),''
'',''''),'''') +
+ CHAR(13) + CHAR(10) + '')''
else t.name end as Expression,
case when t.type in(''U'') then isnull(cast(pro.value as varchar),'''') else '''' end as remark
from [@dbname].sys.objects t
left join [@dbname].sys.objects t2 on t2.object_id = t.parent_object_id
left join [@dbname].sys.sql_modules m on m.object_id = t.object_id
left join [@dbname].sys.indexes ind on ind.name = t.name and ind.object_id = t.parent_object_id
left join [@dbname].sys.foreign_keys k on k.object_id = t.object_id
left join (select cons.object_id,cons.definition,col.name as colname
from [@dbname].sys.default_constraints cons
left join [@dbname].sys.columns col on col.object_id = cons.parent_object_id
and col.column_id = cons.parent_column_id) dcons on dcons.object_id = t.object_id
left join (select cons.object_id,definition,cons.is_not_for_replication
from [@dbname].sys.check_constraints cons) ckcons on ckcons.object_id = t.object_id
left join [@dbname].sys.extended_properties pro on pro.major_id = t.object_id and pro.minor_id = 0 and class = 1 and t.type = ''U''
where t.type in(''V'',''P'',''FN'',''TF'',''AF'',''TR'',''U'',''PK'',''F'',''UQ'',''D'',''C'')'
set @colsql = 'select ''A'' as Type,colid,colname,PObjectId,pName,''COLUMN '' + colname + '' '' + datatype + collation + nullable,remark
from(select o.object_id as PObjectId,o.name as pName,col.column_id as colid,col.name as colname,
case when tp.name in (''decimal'',''numeric'') then tp.name + ''('' + cast(col.precision as varchar) + '','' + cast(col.scale as varchar) + '')''
when tp.name in (''float'') and col.precision <> tp.precision then tp.name + ''('' + cast(col.precision as varchar) + '')''
when tp.name in (''binary'',''char'') and col.max_length <> 1 then tp.name + ''('' + cast(col.max_length as varchar) + '')''
when tp.name in (''varbinary'',''varchar'') and col.max_length <> 1 then tp.name + ''('' + (case when col.max_length = -1 then ''max'' else cast(col.max_length as varchar) end) + '')''
when tp.name in (''nchar'',''nvarchar'') and col.max_length <> 1 then tp.name + ''('' + (case when col.max_length = -1 then ''max'' else cast(col.max_length/2 as varchar) end) + '')''
else tp.name end as datatype,
isnull('' COLLATE '' + col.collation_name,'''') as collation,
case when col.is_nullable = 1 then '''' else '' NOT NULL'' end nullable,
case when col.is_identity = 1 then '' IDENTITY'' + (case when seed_value = 1 and increment_value = 1 then '''' else '' '' + cast(seed_value as varchar) + '','' + cast(increment_value as varchar) + '')'' end) else '''' end iden,
case when col.default_object_id <> 0 then '' DEFAULT '' + df.definition else '''' end as defaultexp,
case when col.is_sparse = 1 then '' SPARSE'' else '''' end as sparse,
case when col.is_computed = 1 then ''AS '' + cmp.definition else '''' end as computeexp,
(case when col.is_column_set = 1 then '' XML COLUMN_SET FOR ALL_SPARSE_COLUMNS'' else '''' end) +
isnull(cast(pro.value as varchar),'''') as remark
from [@dbname].sys.columns col
left join [@dbname].sys.types tp on tp.user_type_id = col.user_type_id
left join [@dbname].sys.identity_columns iden on iden.object_id = col.object_id and iden.column_id = col.column_id
left join [@dbname].sys.computed_columns cmp on cmp.object_id = col.object_id and cmp.column_id = col.column_id
left join [@dbname].sys.default_constraints df on df.object_id = col.default_object_id
left join [@dbname].sys.objects o on o.object_id = col.object_id
left join [@dbname].sys.extended_properties pro on pro.major_id = col.object_id and pro.minor_id = col.column_id and pro.class = 1
where o.type in(''U'')
) t'
set @execDepSql = 'select *
from( -- P、V、TF、FN、U之间的关系
select distinct referencing_id as ReferId,referenced_id as ReferedId from [@dbname].sys.sql_expression_dependencies where referencing_minor_id = 0 and referenced_minor_id = 0 and referenced_id is not null
and referencing_id <> referenced_id
union -- U与U的关系(外键)
select distinct parent_object_id,referenced_object_id from [@dbname].sys.foreign_keys where parent_object_id <> referenced_object_id
union -- 表内部对象(TR,PK,F,UQ,D,C)与表之间的关系
select object_id,parent_object_id from [@dbname].sys.objects where type in(''TR'',''PK'',''F'',''UQ'',''D'',''C'')) t'
if(exists(select name from tempdb..sysobjects where name like'%dependency%' and type='U'))
begin
drop table #dependency
end
if(exists(select name from tempdb..sysobjects where name like'%t%' and type='U'))
begin
drop table #t
end
if(exists(select name from tempdb..sysobjects where name like'%s%' and type='U'))
begin
drop table #s
end
if(exists(select name from tempdb..sysobjects where name like'%result%' and type='U'))
begin
drop table #result
end
create table #dependency(ReferId int,ReferedId int)
create table #t (Type char(2),ObjectId int,Name varchar(128),PObjectId int,PName varchar(128),Expression varchar(max),Remark varchar(8000),LogicName varchar(1000),RefLevel int)
select * into #s from #t
-- 最新数据源处理
if @sserver is not null
begin
set @execsql = REPLACE(@sql,'''','''''') -- 再次转义字符串
set @execsql = N'select * from openquery(' + @sserver + ',''' + @execsql + ''')'
set @execsql += N' union select * from openquery(' + @sserver + ',''' + replace(@colsql,'''','''''') + ''')'
set @execDepSql = N'select * from openquery(' + @sserver + ',''' + replace(@execDepSql,'''','''''') + ''')'
end else begin
set @execsql = @sql + ' union ' + @colsql
end
set @execsql = case when @scollation = 'Chinese_PRC_CI_AS' or @scollation is null then @execsql else REPLACE(@execsql,'Chinese_PRC_CI_AS',@scollation) end
set @execsql = REPLACE(@execsql,'[@dbname]',@sdbname)
set @execsql = N'insert into #s(Type,ObjectId,Name,PObjectId,PName,Expression,Remark) ' + @execsql
set @execDepSql = N'insert into #dependency ' + REPLACE(@execDepSql,'[@dbname]',@sdbname)
--select LEN(@execsql),DATALENGTH(@execsql)
--print substring(@execsql,1,4000)
--print substring(@execsql,4001,4000)
--print substring(@execsql,8001,4000)
exec sp_executesql @execsql
exec sp_executesql @execDepSql
-- 目标数据源处理
if @tserver is not null
begin
set @execsql = REPLACE(@sql,'''','''''') -- 再次转义字符串
set @execsql = N'select * from openquery(' + @tserver + ',''' + @execsql + ''')'
end else begin
set @execsql = @sql + ' union ' + @colsql
end
set @execsql = case when @tcollation = 'Chinese_PRC_CI_AS' or @tcollation is null then @execsql else REPLACE(@execsql,'Chinese_PRC_CI_AS',@scollation) end
set @execsql = REPLACE(@execsql,'[@dbname]',@tdbname)
set @execsql = N'insert into #t(Type,ObjectId,Name,PObjectId,PName,Expression,Remark) ' + @execsql
--select LEN(@execsql),DATALENGTH(@execsql)
--print substring(@execsql,1,4000)
--print substring(@execsql,4001,4000)
--print substring(@execsql,8001,4000)
exec sp_executesql @execsql
-- (1)对象之间的依赖关系:外键可能依赖于待增加表,视图、存储过程等脚本创建对象可能依赖于待增加列
-- (2)由此总结:要首先表增加、列增加(列所属表必须存在),然后再处理各种约束,最后根据对象间的依赖关系依次处理;
-- (3)对象增加不一定必须在对象修改之前:例如要create一个视图,它依赖于另一外需要modify的视图,而此待modify视图企图通过修改来增加列colname
-- 而colname又是此待创建视图中需要用到,因此必须先modify视图,再create另一个视图。
-- (4)规定:(1)列的顺序为0。它依赖于表,因表是必定存在的(否则将直接在创建表时创建列),相当于无依赖;
-- 它作为外键时,将由外键处理对外的依赖关系,而外键依赖于表,知道表与表之间的依赖关系即可
-- (2)无依赖关系的非表内部对象空,顺序为0
-- (3)约束对象(表内部对象)依赖于所属表
-- (4)非表内部对象(表、视图、存储过程、函数)根据依赖关系(要排除自向引用关系)计算顺序;
-- (5)根据顺序依次处理add(create)和modify操作
update #s set RefLevel = NULL -- 清空
update #s set RefLevel = 0 where type = 'A' -- 列无依赖性,因为表是一定存在的(否则只需要创建表,无需要处理列)
update #s set RefLevel = 0 where ObjectId not in(select ReferId from #dependency) -- 对于无依赖关系对象层级设置为0
declare @dependency table(objectid int,maxleavel int)
while 1=1
begin
-- 清空表变量
delete @dependency
-- 获取待处理数据
insert into @dependency
select ObjectId,d.MaxLevel
from #s as n
left join (select ReferId,COUNT(*) as RefCount,MAX(RefLevel) as MaxLevel,
SUM(case when n.RefLevel is not null then 1 else 0 end) as CmpCount
from #dependency d
left join #s as n on n.ObjectId = d.ReferedId
group by ReferId) d on d.ReferId = n.ObjectId
where n.RefLevel is null and d.RefCount = d.CmpCount
-- 循环终止条件
if (select COUNT(*) from @dependency) > 0
begin
update #s set RefLevel = d.maxleavel + 1 from @dependency d where d.objectid = #s.ObjectId
end else begin
break
end
end
-- 设置逻辑主键,让数据有效联系起来
declare @useLogicName varchar(20) = 'UQ,D,C'/* 'PK,F,UQ,D,C' */
update #s set LogicName = case when type in ('PK','F','UQ','D','C') and CHARINDEX(',' + rtrim(type) + ',',',' + @useLogicName + ',') <> 0 then PName + '->' + Expression when type in('A') then PName + '->' + Name else Name end
update #t set LogicName = case when type in ('PK','F','UQ','D','C') and CHARINDEX(',' + rtrim(type) + ',',',' + @useLogicName + ',') <> 0 then PName + '->' + Expression when type in('A') then PName + '->' + Name else Name end
select ROW_NUMBER() over(order by RefLevel,t.Operation,t.[Type]) rownum,t.* into #result from(
select n.*,t.Expression as oldExpression,t.PName as oPname,t.Type as otype,
case when n.Type = 'U' then 0 -- 表对象特殊,不存在修改
when n.Type in('V','P','FN','TF','AF','TR') then (case when t.Name is null then 1 when t.Expression = n.Expression then 0 else 2 end)
when not exists(select 1 from #t where Name = n.PName) or t.Expression = n.Expression then 0
when t.Expression is null then 1 else 2 end as Operation,
case when n.Type in('V','P','FN','TF','AF','TR') and CHARINDEX(n.Name,n.Expression) = 0 then 1 else 0 end as renamed
from #s as n left join #t as t on t.LogicName = n.LogicName and t.Type = n.Type) t
where t.Operation <> 0
select * from #result
--select * from #s
print '-- ======================================================================================================'
declare @i int = 1,@j int,@len int,@max int = (select COUNT(*) from #result)
declare @nExpression nvarchar(max),@oExpression nvarchar(max),@name varchar(128),@pname varchar(128),@type char(2),@renamed int,@operation int
while @i <= @max
begin
select @name = Name,@pname = PName,@type = [Type],@operation = Operation,@renamed = renamed,
@nExpression = Expression,
@oExpression = oldExpression
from #result where rownum = @i
print '/*************************** ' + @name + ' **************************/'
if @renamed = 1 and @type in('V','P','FN','TF','AF','TR')
begin
print '脚本对象' + @name + '已经重命名,但脚本未更新!'
set @i += 1;
continue;
end else begin
if @type in('A')
begin
print 'ALTER TABLE ' + @PName +
(case when @operation = 1 then ' ADD ' + STUFF(@nExpression,1,7,'')
else ' ALTER ' + @nExpression end)
end else if @type in('PK','FK','UQ','D','C') begin
if @operation = 2
begin
print 'ALTER TABLE ' + @PName + ' DROP ' + @name
end
print 'ALTER TABLE ' + @PName + ' ADD CONSTRAINT ' + @name + ' ' + @nExpression
end else begin
if @operation = 2
begin
print 'DROP ' + (case when @type = 'V' then 'VIEW '
when @type = 'P' then 'PROCEDURE '
when @type = 'TR' then 'TRIGGER '
else 'FUNCTION ' end) + @name
end
--print len(@nExpression)
set @j = 1
while @j < LEN(@nExpression)
begin
-- 解决缺陷:每次打印都会换行会切断部分信息
set @len = 4000 + 1 - CHARINDEX(char(13) + char(10),Reverse(SUBSTRING(@nExpression,@j,@j + 4000 - 1)),1)
print SUBSTRING(@nExpression,@j,@len-2)
set @j += @len
end
end
end
set @i += 1
end
---- 【为什么下面的处理速度这么慢?5s】
--select * from(
--select n.*,--t.Expression as oldExpression,t.PName as oPname,t.Type as otype,
-- case when n.Type in('U','V','P','FN','TF','AF') then (case when t.Name is null then 1 when t.Expression = n.Expression then 0 else 2 end)
-- else (case when t.Expression = n.Expression or (t.ObjectId is null and n.Type <> 'TR') then 0 when t.Expression is null then 1 else 2 end) end as Operation,
-- case when n.Type in('U','V','P','FN','TF','AF','TR') and CHARINDEX(n.Name,n.Expression) = 0 then 1 else 0 end as renamed
--from (select *,case when type in ('PK','F','UQ','D','C') and CHARINDEX(',' + rtrim(type) + ',',',' + @useLogicName + ',') > 0 then Expression else Name end as LogicName from #s) as n
-- left join (select *,case when type in ('PK','F','UQ','D','C') and CHARINDEX(',' + rtrim(type) + ',',',' + @useLogicName + ',') > 0 then Expression else Name end as LogicName from #t) as t on t.LogicName = n.LogicName
--) t
--where t.Operation <> 0
--order by t.Operation,t.Type
/* 导出txt文档的脚本 */
--EXEC sp_configure 'show advanced options', 1;
--RECONFIGURE;
--EXEC sp_configure 'xp_cmdshell', 1;
--RECONFIGURE;
--select definition + NCHAR(13) + NCHAR(10) + NCHAR(13) + NCHAR(10) as SQLStr into temp from HRDBDev.sys.sql_modules
--EXEC master..xp_cmdshell 'bcp "Select SQLStr from HRDBDev.dbo.temp" queryout c:\temp.txt -c -w -U sa -P admin@123'
--drop table temp
--EXEC sp_configure 'xp_cmdshell', 0;
--RECONFIGURE;
--EXEC sp_configure 'show advanced options', 0;
--RECONFIGURE;
exec sp_executesql
N'EXEC sp_configure ''show advanced options'', 1;
RECONFIGURE;
EXEC sp_configure ''xp_cmdshell'', 1;
RECONFIGURE;
select definition + NCHAR(13) + NCHAR(10) + NCHAR(13) + NCHAR(10) as SQLStr into temp from HRDBDev.sys.sql_modules
EXEC master..xp_cmdshell ''bcp "Select SQLStr from HRDBDev.dbo.temp" queryout c:\temp.txt -c -w -U sa -P admin@123''
drop table temp
EXEC sp_configure ''xp_cmdshell'', 0;
RECONFIGURE;
EXEC sp_configure ''show advanced options'', 0;
RECONFIGURE;'
三、两数据库结构差异发现方案

上面的脚本虽然费了好劲实现的,但是要数据库结构同步到生产库中,为保证安全起见,还是要谨慎考虑的。毕竟数据安全比任何事情都重要!

于是,将上述方案中最重要的部分——数据库结构差异发现——抽提出来。是否要执行同步,哪些修改要同步,还是由自己一句句来执行。

这样就达到了我的目的:最大限度地简化数据结构的同步,同时保证数据安全!

使用方法:下面的“use ”后面的分别是两个数据库的名称,修改后执行,即可查询出两个库的结构差异。

-- 最新数据库
USE HRDBDev
select case when t.[type] = 'D' then o.name + ';' + col.name else t.name end as name,
t.[object_id] as objectId,t.[type],t.type_desc,
case when t.[type] = 'D' then df.[definition]
when t.parent_object_id <> 0 then ISNULL(o.name,'')
else m.[definition] end
as parentNameOrSql
into #newObjects
from sys.objects t
left join sys.objects o on o.[object_id] = t.parent_object_id
left join sys.sql_modules m on m.[object_id] = t.[object_id]
left join sys.default_constraints df on df.[object_id] = t.[object_id] and t.[type] = 'D'
left join sys.columns col on col.default_object_id = df.[object_id]
order by t.type
select c.[object_id] as objectId,o.name as tablename,c.name as colname,c.column_id as colId,
c.system_type_id as typeId,TYPE_NAME(c.system_type_id) as typename,
c.max_length as maxlength,c.[precision],c.scale,c.is_nullable,c.is_identity
into #newColumns
from sys.columns c
left join sys.objects o on o.[object_id] = c.[object_id]
where o.type = 'U'
order by c.[object_id]
-- 要更新的目标数据库
USE HRDBRelease
select case when t.[type] = 'D' then o.name + ';' + col.name else t.name end as name,
t.[object_id] as objectId,t.[type],t.type_desc,
case when t.[type] = 'D' then df.[definition]
when t.parent_object_id <> 0 then ISNULL(o.name,'')
else m.[definition] end
as parentNameOrSql
into #targetObjects
from sys.objects t
left join sys.objects o on o.[object_id] = t.parent_object_id
left join sys.sql_modules m on m.[object_id] = t.[object_id]
left join sys.default_constraints df on df.[object_id] = t.[object_id] and t.[type] = 'D'
left join sys.columns col on col.default_object_id = df.[object_id]
order by t.type
select c.[object_id] as objectId,o.name as tablename,c.name as colname,c.column_id as colId,
c.system_type_id as typeId,TYPE_NAME(c.system_type_id) as typename,
c.max_length as maxlength,c.[precision],c.scale,c.is_nullable,c.is_identity
into #targetColumns
from sys.columns c
left join sys.objects o on o.[object_id] = c.[object_id]
where o.type = 'U'
order by c.[object_id]
select * into #resultObjects
from(
select case when t.objectId is null then 'add'
--when n.parentNameOrSql <> t.parentNameOrSql then 'modified'
when RTRIM(LTRIM(replace(n.parentNameOrSql,CHAR(13)+CHAR(10),' '))) <>
RTRIM(LTRIM(replace(t.parentNameOrSql,CHAR(13)+CHAR(10),' '))) then 'modified'
else null end as operateion,n.*
from #newObjects n
left join #targetObjects t on n.name = t.name and n.[type] = t.[type]
) t
where t.operateion is not null
order by operateion
select * into #resultColumns
from(
select n.tablename,case when t.colId is null then 'add'
when n.typename <> t.typename or n.maxlength <> t.maxlength
or n.[precision] <> t.[precision] or n.scale <> t.scale
or n.is_nullable <> t.is_nullable or n.is_identity <> t.is_identity
then 'modify'
else null end as operateion,
case when t.colId is null then ''
when n.typename <> t.typename then 'type: ' + t.typename + ' -> ' + n.typename
when n.maxlength <> t.maxlength then 'length:' + str(t.maxlength) + ' -> ' + str(n.maxlength)
when n.[precision] <> t.[precision] then 'precision: ' + str(t.[precision]) + ' -> ' + str(n.[precision])
when n.scale <> t.scale then 'scale: ' + str(t.scale) + ',' + str(n.scale)
when n.is_nullable <> t.is_nullable then 'isnull: ' + str(t.is_nullable) + ' -> ' + str(n.is_nullable)
when n.is_identity <> t.is_identity then 'identity: ' + str(t.is_identity) + ' -> ' + str(n.is_identity)
else null end as modification,
n.colname + ' (' + n.typename +
(case when n.typename in('decimal','numeric')
then ' ('+ LTRIM(str(n.[precision])) + ', ' + LTRIM(str(n.[scale])) + ')'
when n.typename in('binary','varbinary','char','varchar')
then ' ('+ LTRIM(str(n.maxlength)) + ')'
-- Unicode每个字符用两个字节存储
when n.typename in('nchar','nvarchar')
then ' ('+ LTRIM(str(n.maxlength/2)) + ')'
-- datetime,datetime2,datetimeoffset,date,smalldatetime,time,timestamp,
-- real,money,smallmoney,float,bit,int,bigint,smallint,tinyint
-- image,text,ntext,sysname
else '' end) + ','
+ (case when n.is_nullable = 1 then 'null' else 'not null' end) + ')' as newColumn,
t.colname + ' (' + t.typename +
(case when t.typename in('decimal','numeric')
then ' ('+ LTRIM(str(t.[precision])) + ', ' + LTRIM(str(t.[scale])) + ')'
when t.typename in('binary','varbinary','char','varchar')
then ' ('+ LTRIM(str(t.maxlength)) + ')'
when t.typename in('nchar','nvarchar')
then ' ('+ LTRIM(str(t.maxlength/2)) + ')'
else '' end) + ','
+ (case when t.is_nullable = 1 then 'null' else 'not null' end) + ')' as oldColumn
from #newColumns n
left join #targetColumns t on t.tablename = n.tablename and t.colname = n.colname
) t
where t.operateion is not null
order by operateion
select * from #resultObjects t order by operateion,t.[type],t.name
select c.* from #resultColumns c left join #resultObjects t on t.name = c.tablename where t.operateion is null or t.operateion <> 'add' order by c.operateion,tablename
drop table #newObjects
drop table #newColumns
drop table #targetObjects
drop table #targetColumns
drop table #resultObjects
drop table #resultColumns
--select t.tablename,t.colname,
-- t.colname + ' (' + t.typename +
-- (case when t.typename in('decimal','numeric')
-- then ' ('+ LTRIM(str(t.[precision])) + ', ' + LTRIM(str(t.[scale])) + ')'
-- when t.typename in(/*'float',*/'binary','varbinary','char','varchar')
-- then ' ('+ LTRIM(str(t.maxlength)) + ')'
-- when t.typename in('nchar','nvarchar')
-- then ' ('+ LTRIM(str(t.maxlength/2)) + ')'
-- else '' end) + ','
-- + (case when t.is_nullable = 1 then 'null' else 'not null' end) + ')'
--from (select OBJECT_NAME(object_id) as tablename,c.name as colname,tp.name as typename,
-- c.max_length as maxlength,c.precision,c.scale,c.is_nullable
-- from sys.columns c
-- left join sys.types tp on tp.system_type_id = c.system_type_id) t
--where t.typename = 'float'

Viewing all articles
Browse latest Browse all 6262

Trending Articles