简易的迁移

    这里的迁移指的是从一个数据库迁移到另一个,比如组织库

       一个公司可能有多个系统,各个系统又有各自的组织架构

       比如 HR 系统的用户发生信息(手机号,住址)修改时,其它的库也要跟着发生改变

       如图,要从上面的系统中添加、修改、删除用户信息,还有类似的对部门数据,职位数据进行操作,方法一样:

  

 

数据在第一个系统(源表)中新增了用户,或者修改了用户信息,第二个系统也要做对应的业务操作,弯曲是两个系统关联的第三方ID。

 

以往迁移会使用步骤:

      1. 通过SQL查询拿到数据。

      2. 应用C#(Java 或者 Ruby、Python脚本)读取SQL结果集并对其作比较,检查源表是做了【插入】、【修改】还是【删除】了用户信息和组织信息。

      3. 比较后对目标表作相应的【插入】、【修改】、【删除】业务操作。

 

但这里我们采用:

       1. SQL查询并比较,直接分辨出源表中【插入】、【修改】、【删除】的数据。

       2. 根据上面比较的信息,使用SQL或者API接口把修过的数据更新到目标表。 

 

所有代码,这里一次性找出要添加,修改过和要删除的用户

 1 ;WITH SrcModified AS (
 2       SELECT  ID 
 3             , Mobile 
 4             , HousingAddr
 5       FROM      Raw.dbo.Raw_UserInfo   -- 源表
 6         EXCEPT
 7       SELECT  ID = RawID 
 8              , Mobile 
 9              , HousingAddr
10       FROM      Dest.dbo.Dest_UserInfo -- 目标表
11      ), modified AS (
12             SELECT ID FROM  srcModified
13      ), toAdd AS ( SELECT   ID FROM  modified AS m -- 修改过的用户,并且在目标表中没有,就代表添加的用户
14                       WHERE NOT EXISTS (
15                                SELECT * FROM Dest.dbo.Dest_UserInfo AS dest
16                                     WHERE  m.ID = dest.RawID -- 用第三方ID关联
17                            ) 
18      ) ,toModify AS ( SELECT ID
19                         FROM modified
20                          EXCEPT
21                        SELECT  ID
22                          FROM  toAdd
23      ) ,toDel AS ( SELECT  d.ID
24                   FROM     Dest.dbo.dest_UserInfo AS d               
25                     WHERE NOT EXISTS ( SELECT * FROM Raw.dbo.Raw_UserInfo src
26                                            WHERE  src.ID = d.RawID --目标表中保存有源表的ID
27                                       )
28      ) ,allModified AS(
29            SELECT  ID ,1 AS opType FROM  toAdd     --所有添加用户
30                 UNION ALL 
31            SELECT  ID ,2 AS opType FROM  toModify  --修改过信息的用户
32                 UNION ALL
33            SELECT  ID ,3 AS opType FROM  toDel     --从源表中删除的用户
34        )
35        SELECT m.opType
36                ,  m.ID       
37                ,  u.Mobile
38                ,  u.HousingAddr 
39                 FROM allModified as m
40            left join Raw.dbo.Raw_UserInfo as u on m.ID = u.ID

 

      优点:

         SQL批处理的即时结果的特性,直接可以看出插入、修改、删除了哪些数据,而不用中间语言(C#\Java\Python\Ruby)去比较信息,对用户调试,特别是对于几 千上万用户信息的情况尤其有利。

      缺点:

         下面标红的地方。

在这里其实,所有工作都已经完成了,如果有兴趣,可以往下看。

 

=====================================================   结束分隔线   =======================================================

 

实现过程: 

   步骤1:找出所有变更过信息(手机号、住址等)的和新增的用户,这一步只要找出用户ID就行了,关于【删除】用户在【步骤4】:    

;WITH SrcModified AS (
      SELECT  ID 
            , Mobile
            , HousingAddr
      FROM      Raw.dbo.Raw_UserInfo -- 源表
        EXCEPT
      SELECT  ID = RawID
             , Mobile
             , HousingAddr
      FROM      Dest.dbo.Dest_UserInfo -- 目标表
     ), modified AS (
            SELECT ID FROM  srcModified
     )
     SELECT  ID FROM modified

 

步骤2:找出新增的用户,这里也只要找出用户ID,黄色区为较前一步新增的逻辑,下同。

 1 ;WITH SrcModified AS (
 2       SELECT  ID 
 3             , Mobile 
 4             , HousingAddr
 5       FROM      Raw.dbo.Raw_UserInfo -- 源表
 6         EXCEPT
 7       SELECT  ID = RawID 
 8              , Mobile 
 9              , HousingAddr
10       FROM      Dest.dbo.Dest_UserInfo -- 目标表
11      ), modified AS (
12             SELECT ID FROM  srcModified
13      ), toAdd AS ( SELECT   ID FROM  modified AS m                       -- 修改过的用户,并且在目标表中没有,就代表添加的用户
14                        WHERE NOT EXISTS ( SELECT * FROM Dest.dbo.Dest_UserInfo AS dest
15                                                WHERE  m.ID = dest.RawID) -- 用第三方ID关联
16      ) SELECT ID FROM toAdd

 

步骤3:找出修改过信息(手机号、住址等),这里同样用到我们的朋友 Except ,计算公式为:【变更的】 - 【添加的】 = 修改的

 1 ;WITH SrcModified AS (
 2       SELECT  ID 
 3             , Mobile 
 4             , HousingAddr
 5       FROM      Raw.dbo.Raw_UserInfo -- 源表
 6         EXCEPT
 7       SELECT  ID = RawID 
 8              , Mobile 
 9              , HousingAddr
10       FROM      Dest.dbo.Dest_UserInfo -- 目标表
11      ), modified AS (
12             SELECT ID FROM  srcModified
13      ), toAdd AS ( SELECT   ID FROM  modified AS m          -- 修改过的用户,并且在目标表中没有,就代表添加的用户
14                       WHERE NOT EXISTS (
15                                SELECT * FROM Dest.dbo.Dest_UserInfo AS dest
16                                     WHERE  m.ID = dest.RawID -- 用第三方ID关联
17                            ) 
18      ) ,toModify AS ( SELECT ID
19                         FROM modified
20                          EXCEPT
21                        SELECT  ID
22                          FROM  toAdd
23              )
24        SELECT * FROM toModify

 

步骤4:指出从源表中删除的用户。目标表中保存有源表的ID,方式与上面计算方式不同。

 1 ;WITH SrcModified AS (
 2       SELECT  ID 
 3             , Mobile 
 4             , HousingAddr
 5       FROM      Raw.dbo.Raw_UserInfo -- 源表
 6         EXCEPT
 7       SELECT  ID = RawID 
 8              , Mobile 
 9              , HousingAddr
10       FROM      Dest.dbo.Dest_UserInfo -- 目标表
11      ), modified AS (
12             SELECT ID FROM  srcModified
13      ), toAdd AS ( SELECT   ID FROM  modified AS m           -- 修改过的用户,并且在目标表中没有,就代表添加的用户
14                       WHERE NOT EXISTS (
15                                SELECT * FROM Dest.dbo.Dest_UserInfo AS dest
16                                     WHERE  m.ID = dest.RawID -- 用第三方ID关联
17                            ) 
18      ) ,toModify AS ( SELECT ID
19                         FROM modified
20                          EXCEPT
21                        SELECT  ID
22                          FROM  toAdd
23      ) ,toDel AS ( SELECT  d.ID
24                   FROM     Dest.dbo.dest_UserInfo AS d               
25                     WHERE NOT EXISTS ( SELECT * FROM Raw.dbo.Raw_UserInfo src
26                                            WHERE  src.ID = d.RawID --目标表中保存有源表的ID
27                                       )
28        ) SELECT * FROM toDel

 

步骤5: 汇总查出所有添加,修改,删除的用户信息, 得到的执行结果 opType ( 1: 新增,2:修改,3:删除 )

 1 ;WITH SrcModified AS (
 2       SELECT  ID 
 3             , Mobile 
 4             , HousingAddr
 5       FROM      Raw.dbo.Raw_UserInfo -- 源表
 6         EXCEPT
 7       SELECT  ID = RawID 
 8              , Mobile 
 9              , HousingAddr
10       FROM      Dest.dbo.Dest_UserInfo -- 目标表
11      ), modified AS (
12             SELECT ID FROM  srcModified
13      ), toAdd AS ( SELECT   ID FROM  modified AS m -- 修改过的用户,并且在目标表中没有,就代表添加的用户
14                       WHERE NOT EXISTS (
15                                SELECT * FROM Dest.dbo.Dest_UserInfo AS dest
16                                     WHERE  m.ID = dest.RawID -- 用第三方ID关联
17                            ) 
18      ) ,toModify AS ( SELECT ID
19                         FROM modified
20                          EXCEPT
21                        SELECT  ID
22                          FROM  toAdd
23      ) ,toDel AS ( SELECT  d.ID
24                   FROM     Dest.dbo.dest_UserInfo AS d               
25                     WHERE NOT EXISTS ( SELECT * FROM Raw.dbo.Raw_UserInfo src
26                                            WHERE  src.ID = d.RawID --目标表中保存有源表的ID
27                                       )
28      ) ,allModified AS(
29            SELECT  ID ,1 AS opType FROM  toAdd     --所有添加用户
30                 UNION ALL 
31            SELECT  ID ,2 AS opType FROM  toModify  --修改过信息的用户
32                 UNION ALL
33            SELECT  ID ,3 AS opType FROM  toDel     --从源表中删除的用户
34        )
35        SELECT ID, opType FROM allModified

关键步骤在这里都已经结束了,这一步拿到的数据是用户ID及发生过的操作(添加、修改、删除)信息,下一步就是关联源表,并取出所有信息

 步骤6: 关联源表,取出所有信息,这时候几乎就可以为所欲为了,通过应用(使用SQL或者其它API)这些数据对目标表进行新增,删除。且更新(这里存在唯一的遗憾,因为分不出修改的哪个字段 <手机还是住址>,所以它会修改所有比较过(Except)的字段,不过可以通过控制这些比较的字段,并在最终更新时控制更新的范围,来减少不必要的更新,不过SQL比较擅长批处理,这种活交给C#或其它动态脚本做进一步处理也是可以的。在实际应用当中,经过处理后这种更新的东西每次性更新的数据是非常少的,所以带来的副作用可以忽略)。

    以下 SQL 是最终版,这里是用户信息,其它(部门、职位)信息,使用方式一样:

 1 ;WITH SrcModified AS (
 2       SELECT  ID 
 3             , Mobile 
 4             , HousingAddr
 5       FROM      Raw.dbo.Raw_UserInfo   -- 源表
 6         EXCEPT
 7       SELECT  ID = RawID 
 8              , Mobile 
 9              , HousingAddr
10       FROM      Dest.dbo.Dest_UserInfo -- 目标表
11      ), modified AS (
12             SELECT ID FROM  srcModified
13      ), toAdd AS ( SELECT   ID FROM  modified AS m -- 修改过的用户,并且在目标表中没有,就代表添加的用户
14                       WHERE NOT EXISTS (
15                                SELECT * FROM Dest.dbo.Dest_UserInfo AS dest
16                                     WHERE  m.ID = dest.RawID -- 用第三方ID关联
17                            ) 
18      ) ,toModify AS ( SELECT ID
19                         FROM modified
20                          EXCEPT
21                        SELECT  ID
22                          FROM  toAdd
23      ) ,toDel AS ( SELECT  d.ID
24                   FROM     Dest.dbo.dest_UserInfo AS d               
25                     WHERE NOT EXISTS ( SELECT * FROM Raw.dbo.Raw_UserInfo src
26                                            WHERE  src.ID = d.RawID --目标表中保存有源表的ID
27                                       )
28      ) ,allModified AS(
29            SELECT  ID ,1 AS opType FROM  toAdd     --所有添加用户
30                 UNION ALL 
31            SELECT  ID ,2 AS opType FROM  toModify  --修改过信息的用户
32                 UNION ALL
33            SELECT  ID ,3 AS opType FROM  toDel     --从源表中删除的用户
34        )
35        SELECT m.opType
36                ,  m.ID       
37                ,  u.Mobile
38                ,  u.HousingAddr 
39                 FROM allModified as m
40            left join Raw.dbo.Raw_UserInfo as u on m.ID = u.ID

 

可能遇到的问题:

1. 在做集合操作(减Except、交Intersect、并Union)时,如果两个数据库的编码不一样,一个是中文的,一个是英文,或者其它就会提示不能比较的错误,这个时候使用在语句      中使用 collate  把各个列统一成一样的,就可以操作了,例如以下两个列的手机号在两个不同编码的库里,做集合操作时可以统一成中文的

1 select s.Mobile collate Chinese_PRC_CS_AS_KS_WS from src as s
2    Except
3 select d.Mobile collate Chinese_PRC_CS_AS_KS_WS from dest as d

 2. 源表和目标表很可能会在不同服务器上,这个时候做集合操作也会非常慢,可以使用 select ... into 把数据放入临时表进行比较。  

 

 其它小技巧:

1.  一般我们为客户做迁移的时候,用户信息太多,而能拿到的测试的数据没有代表性,他们可能只会拿出某个部门,或者一小部分的数据给你做测试,但这样是不够的,如果客户只能提供一小部分,可以和客户商量使用 TableSample 查询部分信息,它可以随机抽取任意百分比的源信息,分部相对均匀,这样就可以保证一定的测试覆盖率。

2. 为了测试,我们不得考虑到各种情况,一个快速且小量而又不污染源表的方法,可以使用 + union all  select 这种方式添加临时数据,这样很方便于测试,例如我想直接添加一个额外的用户,就可以: 

select UserName from formal          -- 正式的结果
   union all
select '额外的用户名称' as UserName    -- 临时的测试用户

 

       总结:  单就查询速度来说,这种方式效率并不高,因为每一个公用表达式都会扫描一次表,但这种方式比用中间语言(非SQL)把数据抽取出来,并使用各种 if ... else... 比较发生过修改过的字段信息要强得多,因为它是即时的批量查询,可以直接看到修改过的信息。

 

  以上所有代码测试环境为MS SQL Server,Postgres应该是可以运行的,因为MySQL不支持公用表达式,可能要做相应的修改。

 

posted @ 2015-03-05 22:46  牛肉兄  阅读(1067)  评论(1编辑  收藏  举报