• 【译】第四篇 Integration Services:增量加载-Updating Rows


    本篇文章是Integration Services系列的第四篇,详细内容请参考原文


    回顾增量加载
    记住,在SSIS增量加载有三个使用案例:
    1、New rows-add rows to the destination that have been added to the source since the previous load.
    2、Updated rows-update rows in the destination that have been updated in the source since the previous load.
    3、Deleted rows-remove rows from the destination that have been deleted from the source.
    在这篇文章中,我们将关注SSIS增量加载功能中的更新行——自从加载到目标之后在源中有变更的行。
    添加更新代码
    在进行检测更改和更新行之前,我们需要配置另一个测试。使用之前相同的方法更改目标表中的行(dbo.Person)。打开SSMS并连接到你的SQL Server实例。打开一个新的查询窗口,输入下面的T-SQL语句:

    use [AdventureWorks2012]
    go
    update dbo.Person set MiddleName='Ray'
     where MiddleName Is Null

    按F5执行语句,目标表中将有8499行记录被更新。我们将MiddleName为NULL的值更新为Ray.
    打开BIDS然后打开My_First_SSIS_Project解决方案。点击数据流页签,我们需要对上篇文章中加入的查找组件做些更改。双击查找打开查找转换编辑器。
    点击列页。在页的右上部分有一些表格形式的表。左边的一个标记为可用输入列。这包含查找转换的输入列列表(记住,查找转换是连接到OLE DB源适配器的输出,这些列就是从那来的)。另一个网格标记可用查找列。这些是连接页上配置的表、视图或查询的列。
    在上一篇中,我们将可用输入列的EntityID列映射到可用查找列的EntityID列。我将查找比作是Join,
    EntityID列之间的线表示Join子句的ON。这个"ON"定义的匹配准则强迫查找起作用。
    在上一篇我们没有选择可用查找列的复选框。可用查找列旁边有复选框,在网格的顶部有一个选择所有的复选框。如果查找转换是一个Join,这些复选框就是用于从连接表中添加列到SELECT子句。点击可用查找列的选择所有复选框。
    在可用输入列和可用查找列的下面有一个网格。输出别名用于修改可用查找列返回的列名称。继续Join类比,输出别名类似于查询语句中使用AS来给列设置别名。我喜欢使用LkUp_或Dest_来识别从查找操作返回的行。这让我很容易区分从OLE DB源进来的列和从查找转换返回的列。另外,如果列名相同,SSIS会自动在列名后面加上(1)。我们这里添加LkUp_前缀来区分:

    图4.1 查找转换编辑器-勾选可用查找列
    查找转换中流入到数据流管道中记录来自OLE DB源适配器,是从Person.Person表加载到数据流中。目标表是dbo.Person,我们通过查找转换编辑器中的连接页配置访问。查找转换打开目标表,尝试匹配存在于数据流管道与目标表中的记录。当发现没有匹配到时,无匹配项的行将被发送到无匹配输出。
    我们修改了查找转换配置,安查找转换在源和目标表中的EntityID列找到匹配时,会将目标表中的EntityID、FirstName、LastName和MiddleName列返回出来。在它们流经查找转换时,这些列会添加到数据流记录中。
    接下来我们在数据流画布添加条件性拆分和OLE DB命令组件。点击查找转换,将绿箭头拖放到条件性拆分:

    图4.2 添加条件性拆分和OLE DB命令
    查找转换唯一可用的绿色数据流路径是查找匹配输出,因此,这次没有提示我们选择输出,默认就是查找匹配输出。除了它们所包含的数据,查找转换的无匹配输出和匹配输出还有其他的不同,最大的不同是返回的列。
    我将会向你展示查找转换输出会把查找列添加到源行。首先让我们查看输入列。右击OLE DB源与查找转换之间的数据流路径,选择编辑:

    图4.3 编辑OLE DB源与查找之间的数据流路径
    当打开数据流路径编辑器后点击元数据页,路径元数据如图4.4所示:

    图4.4 OLE DB源与查找之间数据流路径的元数据
    查找转换的列来自OLE DB源。关闭这个数据流路径编辑器,右击查找转换和OLE DB目标之间叫做查找无匹配输出的数据流路径,选择编辑打开数据流路径编辑器,点击元数据页:

    图4.5 查找与OLE DB目标之间数据流路径的元数据
    上一篇我们看过这个元数据。查找转换的无匹配输出的元数据和查找转换输入的元数据完全相同。这是本身设计的,查找只能传递这些行,通过转换它们不能找到匹配。
    因为我们配置了查找转换,在找到匹配记录时会有不同的情况:目标中的列会额外返回。要查看数据流的这些更改,右击查找转换和条件性拆分之间叫做查找匹配输出的数据流路径:

    图4.6 查找与条件性拆分之间数据流路径的元数据
    在图4.6中很容易发现数据流路径中添加的额外列,因我我们在查找转换匹配操作时增加了别名。关闭数据流路径编辑器。
    在上一篇,我们配置查找无匹配输出,没有配置从查找表中返回列。因为在增量加载新行不需要它们,但现在我们需要它们来比较源和目标表中的值。
    Change Detection
    查找匹配输出中的"匹配"是什么意思?它表示源表(Person.Person-通过OLE DB源适配器加载到数据流任务)中的EntityID列与目标表(dbo.Person-通过查找转换中的连接页访问)中的EntityID列有相同的值。我们知道EntityID列值匹配,但我们不确定是否其他列也匹配,我们需要知道这些。
    如果源和目标的所有列值都匹配,源中的记录就没有做任何变更,我们可以在进一步考虑中排除它。如果源中有变更,我们想要捕获那些变更并且应用到目标。这就是增量加载中更新的目标。首先我们要检测不同,其次应用更新。
    SSIS中应用Change Detection
    打开条件性拆分,当条件性拆分转换编辑器打开,它展示三部分。左上部分包含两个虚拟文件夹:变量和列:

    图4.7 变量与列虚拟文件夹
    我们主要操作列,因此展开图4.7中的列虚拟文件夹。为了检测源和目标中列的不同,我们将对比FirstName、LastName、MiddleName列。为什么我们不对比EntityID列?这些行的EntityID列已经匹配上,在查找中我们就是用源和目标表的EntityID列作为Join中的on联接条件。
    首先让我们对比FirstName,点击列列表中的FirstName并拖动到条件性拆分转换编辑器下方的条件列:

    图4.8 将FirstName拖放到条件
    当你在条件列放开FirstName,然后点击其他地方就会发生验证。文本会变红因为验证失败。这里为什么会失败?条件必须返回布尔值——真或假。FirstName是一个字符串,此时如果你点击确定试图关闭转换编辑器,你将得到一个错误信息。

    图4.9 错误信息
    点击确定,我们还没结束。在条件性拆分转换编辑器的右上部分包含有SSIS表达式语言。
    我们想检测FirstName和LkUp_FirstName不同。在SSIS表达式语言,展开运算符虚拟文件夹,选择不等于操作:

    图4.10 运算符虚拟文件夹下的不等于
    点击不等于操作并拖动到条件表达式下FirstName列的右边,然后从列虚拟文件来下拖动LkUp_FirstName放到条件表达式下不等于的右边:

    图4.11 条件中添加不等于及LkUp_FirstName
    因为表达式现在可以得出一个布尔值,它通过验证并且字体变黑。如何检查的呢?源和目标中EntityID列匹配的记录中检测FirstName与LkUp_FirstName是否匹配。如果FirstName不同,源和目标就存在差异。因为目标通常是滞后的源,它们假设源有更新、更好、更准确的数据。如果是这样的话,我们需要把数据输入到目标。
    因为在测试查询中没有对FirstName列做任何更改,这个条件返回总是假:因为他们总是相等。我们需要添加条件表达式获取所有变更。让我们在变化检测条件表达式中分离这部分,用括号包住这段表达式:

    图4.12 用括号包住表达式
    接下来我们要添加MiddleName。我们停下思考下我们需要什么,我们要检测源和目标中任何列差异。只要一个改变,就足以触发目标上的更新。因此我们想要检查一列或另一列不相等。"Or"操作符合需求。
    下拉SSIS表达式语言列表,将逻辑或拖放到表达式的右边:

    图4.13 运算符虚拟文件夹下的逻辑或
    在逻辑或的后面添加括号如图4.14所示:

    图4.14 添加逻辑或及括号
    从转换编号器左上部分的列虚拟文件夹将MiddleName拖放到括号中,在MiddleName列的右边拖放一个不等于操作,然后拖入LkUp_MiddleName。
    继续按照之前的步骤将LastName加入到条件表达式,最终的条件表达式如图4.15所示:

    图4.15 最终的条件表达式
    在我们关闭转换编辑器前,我们重命名输出名称为Updated Rows。
    你可能会被表达的不必要的括号所困扰,在图4.16中,我清理掉表达式中不必要的括号和空格:

    图4.16 总体表达式
    我们完成了什么?查找转换匹配输出传送到条件性拆分转换。这意味着这些行的EntityID列和LkUp_EntityID列有相同的值。EntityID和LkUp_EntityID列被用于查找转换执行匹配操作。我们在条件性拆分转换中配置了一个叫做Updated Rows的条件。Updated Rows条件分离FirstName、MiddleName、LastName列有差异的行。
    注意:条件性拆分转换将行定向到不同的输出。当我们定义了一个条件,我们还创建了一个新的输出,一个新的路径,数据可以从条件性拆分转换中流出。
    源中和目标中列相同的行到哪去了呢?指定将输入行定向到特定的输出时所要遵循的条件。如果输入行不符合任何条件,则该行被定向到条件性拆分默认输出。

    图4.17 配置后的条件性拆分转换编辑器
    点击确定关闭条件性拆分转换编辑器。点击条件性拆分然后拖动绿色箭头到OLE DB命令,在选择输入输出中选择Updated Rows:

    图4.18 选择输入输出
    此时你的数据流任务应该如图4.19所示:

    图4.19 数据流画布
    我们将用OLE DB命令转换执行更新目标表中的行。
    我们检测源与目标存在差异的行,通过Updated Rows输出将更新的行发送给OLE DB命令转换。是时候配置OLE DB命令的更新功能了。右击OLE DB命令转换打开OLE DB命令的高级编辑器。
    在OLE DB命令的高级编辑器"连接管理器"页签,设置OLE DB连接管理器到127.0.0.1,5377.AdventureWorks2012.sa。点击"组件属性"页签,下拉到自定义属性,点击SqlCommand的属性值文本框中的省略号打开"字符串值编辑器"。在字符串值键入下面T-SQL语句:

    update dbo.Person
       set FirstName = ?
          ,MiddleName = ?
          ,LastName = ?
     where EntityID = ?


    图4.20 OLE DB命令组件属性
    在OLE DB,用问号(?)代表参数占位符。我们将在下一个标签中映射这些参数。单击确定以关闭"字符串值编辑器"。单击"列映射"页签:

    图4.21 OLE DB命令列映射
    The question marks form a zero-based array.这意味着param_0代表第一个问号,param_1代表第二个问号,等等。我们通过拖动可用输入列中的列,将它们释放到可用目标列中的映射参数上。从源和目标表中的列存在于可用输入列。记住,前缀为LkUp_的列实际上是查找转换返回的目标数据。所以我们只要映射源表的列(因为我们假设源包含最新的数据)。因为第一个?映射FirstName,我们将FirstName映射Param_0,MiddleName映射Param_1,LastName映射Param_2,EntityID映射Param_3:

    图4.22 OLE DB命令映射列
    你已经完成参数映射和OLE DB命令配置。点击确定按钮关闭OLE DB命令的高级编辑器。你的数据流任务看起来应该如4.23所示:

    图4.23 数据流画布
    让我们测试一下,按F5启动调试器:

    图4.24 执行过程中的数据流任务
    …它几乎可以工作。让我们在进度标签检查错误信息。我发现了错误,但很难阅读:

    图4.25 进度标签错误信息
    但是可以右击错误信息选择复制消息文本:

    [条件性拆分 [127]] 错误: “输出“Updated Rows”(165)”的表达式“(FirstName != LkUp_FirstName) || (MiddleName != LkUp_MiddleName) || (LastName != LkUp_LastName)”的计算结果为 NULL,但“组件“条件性拆分”(127)”要求布尔值结果。请修改输出中的错误行处理设置,以将此结果视为 False (忽略失败),或者将该行重定向到错误输出(重定向行)。表达式结果必须是用于“条件性拆分”的布尔值。NULL 表达式结果不对。

    问题出现在我的表达式上面。某些表达式的计算为NULL。当你验证等于/不等于,并且其中有一个为NULL时,这个结果不会是真/假,而是NULL。因为NULL不是一个布尔值,所以条件表达式失败。
    我将从源表:Person.Person,执行三个查询搜索NULL:

    use [AdventureWorks2012]
    go
    select * from Person.Person
     where FirstName Is Null
    
    select * from Person.Person
     where MiddleName Is Null
    
    select * from Person.Person
     where LastName Is Null

    执行这些查询告诉我源表中8499行的MiddleName列是NULL。记得在开始我们更新过目标表中MiddleName列为NULL的记录,正好是8499行。
    为了解决这个问题,停止BIDS调试器然后打开条件性拆分转换编辑器。我们需要把NULL值分离到一种方法中以允许我们比较MiddleName列的值而不出错。我们使用SSIS表达式语言中的IsNull()函数。这个函数并不是T-SQL中我们熟知的IsNull()。T-SQL函数使用另一个值代替NULL。SSIS表达式语言的IsNull()函数是一个NULL值的检测,它返回一个布尔结果(真/假)。我们组合IsNull()和条件操作来检查NULL并返回它们是否存在。更新Updated Rows条件语句如下形式:

    (FirstName != LkUp_FirstName) || ((ISNULL(MiddleName) ? "Humperdinck" : MiddleName) != (ISNULL(LkUp_MiddleName) ? "Humperdinck" : LkUp_MiddleName)) || (LastName != LkUp_LastName)


    图4.26 更新的的Updated Rows条件语句
    我们是如何处理NULL?首先我们用ISNULL(MiddleName)检测它们。ISNULL()函数返回真/假,并且是条件操作的"条件"。条件操作函数格式是<Condition>?<True>:<False>. 如果MiddleName是NULL,条件操作中True部分(本例Humperdinck)将会应用。如果MiddleName不是NULL,条件操作中False部分(本例MiddleName)将会应用。因此如果MiddleName是NULL,我们用Humperdinck代替作对比。如果MiddleName不是NULL,我们直接用MiddleName的值做对比。
    LkUp_MiddleName列使用相同的逻辑,当LkUp_MiddleName是NULL,我们用Humperdinck代替,如果不是NULL,我们用非空的值做对比。这样我们就避免了NULL != NULL这种表达式。
    这里需要注意的是:Humperdinck不能是一个可用的MiddleName的值。考虑加载的名称数据中没有MiddleName会出现什么情况。比如John Smith。后来,他在网上或通过客服代表更新自己的姓名信息。如果John的MiddleName是Humperdinck那会发生什么呢?存储在目标表中的MiddleName值是空的。源表已更新为Humperdinck。当条件性拆分Updated Rows条件应用于这一行,对不等式的MiddleName侧不为空,因此使用MiddleName(Humperdinck)比较。不平等LkUp_MiddleName侧为空,因此替换为Humperdinck。不平等按照设计进行对比,但未能确定这些值的不同。
    点击确定关闭条件性拆分转换编辑器,按F5重新执行SSIS包:

    图4.27 重新执行SSIS包
    我注意这个执行一些趣事。首先,它成功执行;其次,它花费了一段时间。在我的电脑上,它花费了近2分多钟。

    图4.28 SSIS包执行时间
    为什么执行花费那么长时间?大部分的时间花费在OLE DB命令中的更新语句上。OLE DB命令每次只处理一行。等效于SSIS游标,并且SQL Server不喜欢基于行的操作,他们执行太慢。
    基于集合的更新
    是否有方法避免基于行的操作?当然存在。让我们看下如何使用。如果你的SSIS包还在运行,把它停止。数据流任务中点击OLE DB命令然后删除它,在它的位置从工具箱拖一个OLE DB目标,并将条件性拆分的输出Updated Rows连接到OLE DB目标:

    图4.29 添加OLE DB目标
    将OLE DB目标重命名为StageUpdates。右击OLE DB目标打开编辑器。连接管理器下选择127.0.0.1,5377.AdventureWorks2012.sa,接受默认的数据访问模式"表或视图-快速加载"。在表或视图名称的旁边点击新建:

    图4.30 OLE DB目标编辑器
    在我们点击确定之前我们做些调整,表名来自OLE DB的名称-这就是开始我们重命名它的原因。数据定义语句的列,是从数据流路径的元数据读取的。它包括将SSIS数据类型转换成SQL Server数据类型。
    在我们执行前,将LkUp_前缀的列移除。我们只需保存源中的数据来更新。最终语句修改为:

    CREATE TABLE [StageUpdates] (
        [EntityID] int,
        [FirstName] nvarchar(50),
        [LastName] nvarchar(50),
        [MiddleName] nvarchar(50)
    )

    点击确定,在AdventureWorks2012库下就会创建StageUpdates表。注意OLE DB目标编辑器的确定按钮是灰色的,并且在底部有一个警告信息。点击映射页:

    图4.31 OLE DB目标编辑器映射页
    列自动匹配因为列名和数据类型相同。类型相匹是因为可用目标列是从可用输入列的元数据构建的。点击确定按钮。你的数据流画布如图4.32所示:

    图4.32 数据流画布
    在执行前我们需要处理StageUpdates中的行。现在需要更新的数据保存在StageUpdates表。我们需要这些数据应用到dbo.Person表。点击控制流页签然后画布添加一个执行SQL任务。点击数据流任务将绿箭头拖动到执行SQL任务:

    图4.33 添加执行SQL任务
    我们将在执行SQL任务中执行一个基于集合的更新。不像每次只loop一行记录,基于集合的几乎可以一次把所有更新应用到目标表。
    右击执行SQL任务打开执行SQL任务编辑器。在常规页,修改名称属性为"Apply Staged Updates",修改连接属性为127.0.0.1,5377.AdventureWorks2012.sa。点击SQLStatement属性的省略号打开"输入SQL查询",并键入下面T-SQL语句:

    update dest
       set dest.FirstName = stage.FirstName
          , dest.MiddleName = stage.MiddleName
          , dest.LastName = stage.LastName
      from dbo.Person dest
       join dbo.StageUpdates stage
         on stage.EntityID = dest.EntityID

    这个语句将StageUpdates和dbo.Person联接,并将更新从Stage应用到目标。在我们执行这个包之前,让我们重置目标表中的数据,执行下面T-SQL语句:

    use [AdventureWorks2012]
    go
    update dbo.Person set MiddleName='Ray'
     where MiddleName Is NULL

    返回到BIDS,按F5执行SSIS包。我的控制流如下所示:

    图4.34 控制流画布
    我的数据流任务如下所示:

    图4.35 数据流任务
    在BIDS的进度页签显示了改进后的执行时间:

    图4.36 改进后的执行时间
    2.792秒远好于2分钟!
    我们仍然需要对StageUpdates表做更多处理。当前的配置会继续往StageUpdates表中保存记录然后应用于更新。我们可以在更新应用到dbo.Person目标表之后,删除StageUpdates表中的记录。但如果在执行过程中发生一些"坏"的情况,执行过程中保留这些记录可以让我有更多的数据点来检查线索。因此,我们在数据流任务加载前清空StageUpdates表,让StageUpdates表中的记录在执行期间一直保留。
    为了达到这个目的,首先停止BIDS调试器。然后在控制流画布添加另一个执行SQL任务,连接执行SQL任务到数据流任务:

    图4.37 添加执行SQL任务
    双击打开执行SQL任务编辑器。配置常规页如图4.38所示:

    图4.38 配置常规页面
    下面我们准备执行,点击F5执行SSIS包:

    图4.39 控制流画布
    数据流任务执行一个增量加载:

    图4.40 数据流画布
    总结
    在这篇文章中,我们从一个存在于目标的数据不会有变更的SSIS增量加载开始。这种很适用于给定日期的高温数据,或每天货币兑换率的案例。有些数据是随着时间变化的。为了捕捉这些变化,我们需要检测源和目标之间的差异,并将更新应用到目标。本篇文章覆盖了几种更新的方法,越往后面效率越高。

  • 相关阅读:
    Python之面向对象知识整理
    python2与python3的区别
    Gitlab 删除仓库文件夹
    Git撤销本地commit(未push)
    js库
    HTML | 打开网址后3秒跳转外链
    Vue CLI | 安装
    npm | npm淘宝镜像和查看镜像设置
    swiper | 过渡效果 effect: 'fade' 导致文字重叠
    CSS改变背景 | pattern.css
  • 原文地址:https://www.cnblogs.com/Uest/p/4756110.html
Copyright © 2020-2023  润新知