一、创建过程
USE [TEMPDB]
GO
-- =============================================
-- Author: <LE>
-- Create date: <2017-06-28>
-- Description: <比较两个实例之间的库结构差异>
-- 本地缺失:不处理
-- 本地比远程多:记录下来差异,并插入临时表(包括库及表)
-- 本地与远程类型不一致:记录下来差异,并插入临时表
-- 2018-04-19更新内容
-- 1. 修正表结构差异结果列名显示成表名的问题
-- 2. 增加字段类型代码转换,以明文显示差异列类型
-- =============================================
CREATE PROCEDURE [dbo].[PR_DB_STRUCT_COMPARE]
(
@DBNAME_PREFIX VARCHAR(300),--数据库前缀
@TARGET_LINKED_SERVER_NAME VARCHAR(300)--目标连接服务器名
)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
--定义变量
DECLARE
@SSQL VARCHAR(1500),
@DBNAME VARCHAR(300)
--定义临时表
--库差异
CREATE TABLE #TMP_DB_DIFF(
C_DBNAME VARCHAR(300) --库名
)
--表差异
CREATE TABLE #TMP_TABLE_DIFF(
C_DBNAME VARCHAR(300), --库名
C_TBNAME VARCHAR(300) --表名
)
--字段差异
CREATE TABLE #TMP_COLUMN_DIFF(
N_DBFLAG INT, --库标记 1源库 2目标库
C_DBNAME VARCHAR(300), --库名
C_TBNAME VARCHAR(300), --表名
C_COLNAME VARCHAR(300), --字段名
N_TYPENAME VARCHAR(300), --类型名称
N_COLLENGTH INT --字段长度
)
BEGIN TRY
--插入本地多出来的库,不处理,后续自动处理
--因为要拼接参数,所以不能直接执行,用动态SQL处理
SET @SSQL = ' INSERT INTO #TMP_DB_DIFF '
+ ' SELECT S.NAME FROM MASTER..SYSDATABASES S '
+ ' LEFT JOIN '
+ @TARGET_LINKED_SERVER_NAME
+ '.MASTER.DBO.SYSDATABASES T ON S.NAME = T.NAME '
+ ' WHERE T.NAME IS NULL '
EXECUTE(@SSQL)
--1. 定义游标 查询出本地及远程实例都有的数据库,后续逐个处理
DECLARE DBS CURSOR FOR SELECT NAME FROM MASTER..SYSDATABASES
WHERE NAME LIKE @DBNAME_PREFIX + '%' AND NAME NOT IN (SELECT C_DBNAME FROM #TMP_DB_DIFF)
--2. 打开游标
OPEN DBS
--3. 获取记录
FETCH DBS INTO @DBNAME
--4. 循环处理每个库
WHILE @@FETCH_STATUS = 0
BEGIN
--4.1 开始处理每个库
--4.1.1 处理差异表 得到本地有,但远程实例没有的表名,并把结果集插入临时表
SET @SSQL = 'WITH S AS (SELECT NAME FROM ' + @DBNAME + '..SYSOBJECTS '
+ ' WHERE XTYPE = ''U''), '
+ ' T AS (SELECT NAME FROM ' + @TARGET_LINKED_SERVER_NAME + '.' + @DBNAME
+ '.DBO.SYSOBJECTS WHERE XTYPE = ''U'') '
+ ' INSERT INTO #TMP_TABLE_DIFF '
+ ' SELECT ''' + @DBNAME + ''', S.NAME FROM S LEFT JOIN T ON S.NAME = T.NAME WHERE T.NAME IS NULL '
--PRINT @SSQL
--PRINT '------------'
EXECUTE(@SSQL)
--4.1.2 处理差异字段 得到本地有,但远程实例没有的列名,并把结果集插入临时表
SET @SSQL = 'WITH S AS ( '
+ ' SELECT OBJS.name TBNAME, COLS.name COLNAME, '
+ ' CASE WHEN TYPS.name IS NULL THEN ''未知类型'' ELSE TYPS.name END COLTYPE, COLS.length COLLEN FROM '
+ @DBNAME + '..SYSOBJECTS OBJS '
+ ' INNER JOIN ' + @DBNAME + '..SYSCOLUMNS COLS ON OBJS.ID = COLS.ID '
+ ' LEFT JOIN ' + @DBNAME + '..SYSTYPES TYPS ON COLS.XTYPE = TYPS.XTYPE '
+ ' WHERE OBJS.XTYPE = ''U''), '
+ ' T AS ( '
+ ' SELECT OBJS.name TBNAME, COLS.name COLNAME, '
+ ' CASE WHEN TYPS.name IS NULL THEN ''未知类型'' ELSE TYPS.name END COLTYPE, COLS.length COLLEN FROM '
+ @TARGET_LINKED_SERVER_NAME + '.' + @DBNAME + '.DBO.SYSOBJECTS OBJS '
+ ' INNER JOIN ' + @TARGET_LINKED_SERVER_NAME + '.' + @DBNAME
+ '.DBO.SYSCOLUMNS COLS ON OBJS.ID = COLS.ID '
+ ' LEFT JOIN ' + @TARGET_LINKED_SERVER_NAME + '.' + @DBNAME + '.DBO.SYSTYPES TYPS ON COLS.XTYPE = TYPS.XTYPE '
+ ' WHERE OBJS.XTYPE = ''U'') '
+ ' INSERT INTO #TMP_COLUMN_DIFF '
+ ' SELECT CASE WHEN S.TBNAME IS NULL THEN 1 ELSE 2 END N_DBFLAG, ''' + @DBNAME
+ ''' , CASE WHEN S.TBNAME IS NULL THEN T.TBNAME ELSE S.TBNAME END TBNAME, '
+ ' CASE WHEN S.COLNAME IS NULL THEN T.COLNAME ELSE S.COLNAME END COLNAME, '
+ ' CASE WHEN S.COLTYPE IS NULL THEN T.COLTYPE ELSE S.COLTYPE END COLTYPE, '
+ ' CASE WHEN S.COLLEN IS NULL THEN T.COLLEN ELSE S.COLLEN END COLLEN '
+ ' FROM S '
+ ' FULL JOIN T ON S.TBNAME = T.TBNAME AND S.COLNAME = T.COLNAME AND S.COLTYPE = T.COLTYPE AND S.COLLEN = T.COLLEN '
+ ' WHERE S.COLNAME IS NULL OR T.COLNAME IS NULL '
--4.1.3 执行动态脚本
--PRINT @SSQL
--PRINT '================'
EXECUTE(@SSQL)
--4.2 处理下一条
FETCH DBS INTO @DBNAME
END
--5. 查询结果集(可以直接在这里生成同步脚本)
SELECT * FROM #TMP_COLUMN_DIFF
SELECT * FROM #TMP_TABLE_DIFF
SELECT * FROM #TMP_DB_DIFF
--6. 删除临时表
DROP TABLE #TMP_COLUMN_DIFF
DROP TABLE #TMP_TABLE_DIFF
DROP TABLE #TMP_DB_DIFF
--7. 关闭游标
CLOSE DBS
--8. 销毁游标
DEALLOCATE DBS
END TRY
BEGIN CATCH
--关闭游标
CLOSE DBS
--销毁游标
DEALLOCATE DBS
--打印错误信息
PRINT ERROR_NUMBER()
PRINT ERROR_MESSAGE()
END CATCH
END
二、执行过程
EXEC PR_DB_STRUCT_COMPARE 'DB_', 'dev3310Link' -- 过程的参数请参阅脚本中的说明
三、查看结果
执行完毕后返回的结果集就是我们想要的结果。在需要时也可以修改存储过程中的临时表为实体表,根据需求调整即可