【问题标题】:Query a database based on result of query from another database根据来自另一个数据库的查询结果查询一个数据库
【发布时间】:2017-09-30 11:18:44
【问题描述】:

我在 VS 2013 中使用 SSIS。 我需要从 1 个数据库中获取 ID 列表,并使用该 ID 列表查询另一个数据库,即SELECT ... from MySecondDB WHERE ID IN ({list of IDs from MyFirstDB})

【问题讨论】:

  • 数据库是否在同一台服务器上?
  • 不,它们在 2 个不同的服务器上。
  • Google sp_addlinkedserver

标签: sql sql-server ssis etl ssis-2012


【解决方案1】:

有3种方法可以实现:

第一种方法 - 使用查找转换

首先你必须添加一个Lookup Transformation 就像@TheEsisia 回答但还有更多要求:

  • 在查找中,您必须编写包含 ID 列表的查询(例如:SELECT ID From MyFirstDB WHERE ...

  • 至少您必须从查找表中选择一列

  • 这些不会过滤行,但这会添加第二个表中的值

要过滤行WHERE ID IN ({list of IDs from MyFirstDB}),您必须在查找错误输出Error case 中做一些工作,有两种方法:

  1. 将错误处理设置为Ignore Row,因此添加的列(来自查找)的值为 null ,因此您必须添加一个Conditional split 来过滤值等于NULL 的行。

假设您已选择 col1 作为查找列,因此您必须使用类似的表达式

ISNULL([col1]) == False
  1. 或者你可以将错误处理设置为Redirect Row,这样所有的行都会被发送到错误输出行,可能不会被使用,所以数据会被过滤

这种方法的缺点是所有数据都是在执行过程中加载和过滤的。

此外,如果在加载所有数据后在本地计算机上进行网络过滤(服务器上的第二种方法)是内存。

第二种方法 - 使用脚本任务

为避免加载所有数据,您可以采取一种解决方法,您可以使用脚本任务来实现:(在 VB.NET 中编写的答案)

假设连接管理器名称为TestAdo"Select [ID] FROM dbo.MyTable" 是获取id 列表的查询,User::MyVariableList 是要存储id 列表的变量

注意:此代码将从连接管理器读取连接

    Public Sub Main()

        Dim lst As New Collections.Generic.List(Of String)


        Dim myADONETConnection As SqlClient.SqlConnection  
    myADONETConnection = _  
        DirectCast(Dts.Connections("TestAdo").AcquireConnection(Dts.Transaction), _  
        SqlClient.SqlConnection)

        If myADONETConnection.State = ConnectionState.Closed Then
        myADONETConnection.Open()
        End If

        Dim myADONETCommand As New SqlClient.SqlCommand("Select [ID] FROM dbo.MyTable", myADONETConnection)

        Dim dr As SqlClient.SqlDataReader

        dr = myADONETCommand.ExecuteReader

        While dr.Read

            lst.Add(dr(0).ToString)

        End While


        Dts.Variables.Item("User::MyVariableList").Value = "SELECT ... FROM ... WHERE ID IN(" &  String.Join(",", lst) & ")"

        Dts.TaskResult = ScriptResults.Success
    End Sub

并且User::MyVariableList应该用作源(变量中的Sql命令)

第三种方法 - 使用执行 Sql 任务

类似于第二种方法,但这将使用Execute SQL Task 构建IN 子句,然后将整个查询用作OLEDB Source

  1. 只需在 DataFlow 任务之前添加一个执行 SQL 任务
  2. ResultSet 属性设置为single
  3. 选择User::MyVariableList作为结果集
  4. 使用以下 SQL 命令

    DECLARE @str AS VARCHAR(4000)
    
    SET @str = ''
    
    SELECT @str = @str + CAST([ID] AS VARCHAR(255)) + ','
    FROM dbo.MyTable 
    
    SET @str = 'SELECT * FROM  MySecondDB WHERE ID IN (' + SUBSTRING(@str,1,LEN(@str) - 1) + ')'
    
    SELECT @str
    

如果该列是字符串数据类型,您应该在值之前和之后添加引号,如下所示:

SELECT @str = @str + '''' + CAST([ID] AS VARCHAR(255)) + ''','
    FROM dbo.MyTable

确保您已将 DataFlow Task Delay Validation 属性设置为 True

【讨论】:

  • 很好的解释!
  • 选项 3 适用于您可能拥有超过 10 万个 ID 的情况吗?它似乎在 where ID in (select... 语句中选择了一个单独的 ID
  • @ColinMac 在处理大量 ID 时,这种方法不是首选
【解决方案2】:

这是使用LookUp Transformation 的经典案例。首先,使用OLE DB Source 从第一个数据库中获取数据。然后,使用LookUp Transformation 根据来自第二个数据集的ID 值过滤此数据集。以下是使用LookUp Transformation 的步骤:

  1. General选项卡中,选择Full CashOLE DB Connection ManagerRedirect rows to no match output,如下图所示。请注意,使用 Full Cash 可为您的包提供出色的性能。

常规设置

  1. Connection 选项卡中,使用OLE DB Connection Manager 连接到您的第二个服务器。然后,您可以直接选择具有ID 值的数据集,也可以(如下图所示)使用 SQL 代码从过滤数据集中选择 ID。

连接:

  1. 转到Columns 选项卡并从两个数据集中选择ID 列。对于第一个数据集中的每条记录,它将检查其ID 是否在Available LookUp Column 中。如果是,它将转到Matching 输出,否则转到No Matching 输出。

匹配 ID 列:

  1. 点击OK关闭LookUp。然后你需要选择LookUp Match Output

匹配输出:

【讨论】:

  • 谢谢。 @TheEsisia:myFirstDB 中的 ID 是 int,而 mySecondDB 中的 ID 是 varchar(20)。因此,我创建了一个带有表达式 (DT_STR,20,1252)id 的派生列。在查找中,我将可用输入列中的“Derived Column.ID”连接到可用查找列中的 ID。我还从可用查找列中选择列列表以显示在结果中。当我运行包时,我收到错误“在查找过程中行产生不匹配。”
  • 在查找转换的连接选项卡中,选择“使用 SQL 查询的结果”。写一个select语句来查询表,但是对于id comulm,使用CAST语句将VARCHAR(20)转换成int。
  • 性能方面,在这种情况下不建议查找,最好在 OLEDB 源中过滤数据
  • @Yahfoufi 首先,Look Ups 使用兑现,因此具有出色的性能。其次,当两个数据集位于两个不同的服务器中时,您将如何过滤 OLE DB Source 中的数据?
  • @TheEsisia 使用执行 sql 任务来获取 ID 列表并将其存储在变量中(例如:1,2,3),将 sql 命令构建为另一个变量 "SELECT * FROM .... WHERE ID IN(" + @[user::variable] + ")" 中的表达式并使用它变量作为 OLEDB SOURCE 中的源
【解决方案3】:

“最佳”答案取决于所涉及的数据量和源系统。

许多其他答案建议基于 SQL Server 中的巧妙连接构建一个值列表。如果引用的系统是 Oracle、MySQL、DB2、Informix、PostGres 等,那效果就不太好了。可能有一个等价的概念,但可能没有

为了获得最佳性能,您需要在任何这些行到达数据流之前过滤第二个数据库。这意味着向您的源查询添加过滤条件,正如其他人所建议的那样。这种方法的挑战是您的查询将受到一些我不记得的实际范围的限制。 where 子句中的十、一百、一千个值可能没问题。十万,一百万 - 可能不是那么多。

如果您有大量值要针对源表进行过滤,则可以在该服务器上创建一个表并截断并重新加载该表(执行 sql 任务 + 数据流)。这允许您将所有数据都放在本地,然后您可以索引过滤表并让数据库引擎做它真正擅长的事情。

但是,您说源数据库是一些您无法在其中创建表的自定义解决方案。您可以使用临时表查看上述方法,并且在 SSIS 中您只需将连接标记为单例/持久连接(TODO:看看这个)。我不太关心带有 SSIS 的临时表,因为调试它们是我不希望我的死敌做的噩梦。

如果您仍在阅读,我们已经确定了为什么源系统中的过滤可能不“可行”,即使它会提供最佳性能。

现在我们被纯粹的 SSIS 解决方案所困。要获得最佳性能,请不要在下拉列表中选择表名 - 除非您绝对需要每一列。另外,请注意您的数据类型。将 LOB(XML、文本、图像 (n)varchar(max)、varbinary(max))拉入数据流会导致性能下降。

默认建议是使用查找组件来过滤数据流中的数据。只要您的源系统支持和 OLE DB 提供程序(或者您可以将数据强制转换为Cache Connection Manager

如果由于某种原因您不能使用 Lookup 组件,那么您可以显式地对源系统中的数据进行排序,将源组件标记为此类,然后在数据流中使用 Inner Join 类型的 Merge Join只引入匹配的数据。

但是,请注意,源系统中的排序将根据本机规则进行排序。我遇到了一种情况,SQL Server 是基于默认的 ASCII 排序,而我的 DB2 实例在 zOS 上运行,提供了 EBCDIC 排序。当我的域只是整数时,这很棒,但当键变成字母数字时(AAA、A2B 和 AZZ 将基于此进行不同的排序),这真是太棒了。

最后,除了最后一段,以上假设你有整数。如果您正在执行字符串匹配,您会得到额外的丑陋程度,因为不同的组件可能会或可能不会执行区分大小写的匹配(使用区分大小写的系统进行排序也可能是一个因素)。

【讨论】:

    【解决方案4】:

    我会首先创建一个字符串变量,例如SQL_Select,在包的范围内。然后我会使用针对第一个数据库的执行 SQL 任务来分配一个值。 General 页面上的 ResultSet 属性应设置为 Single row。向 结果集 选项卡添加一个条目以将其分配给您的变量。

    使用的 SQL 语句需要设计为在单行文本中返回第二个数据库所需的 SELECT 语句。示例如下:

    SELECT 
        'SELECT * from MySecondDB WHERE ID IN ( ' 
        + STUFF ( (
            SELECT TOP 5
                ' , ''' + [name] + ''''
            FROM  dbo.spt_values
            FOR XML PATH(''), TYPE).value('(./text())[1]', 'VARCHAR(4000)'                
            ) , 1 , 3, '' ) 
        + ' ) '
        AS SQL_Select
    

    删除 TOP 5 并将 [name]dbo.spt_values 替换为您的列名和表名。

    然后您可以在下游任务中使用变量 SQL_Select,例如针对数据库的 OLE DB 源 2. OLE DB 源和 OLE DB 命令任务都允许您将变量指定为 SQL 语句源。

    【讨论】:

    • 谢谢大家。我使用 Hadi 的第二种解决方案
    【解决方案5】:

    您可以在两台服务器之间添加 LinkedServer。 SQL 命令是这样的:

    EXEC sp_addlinkedserver @server='SRV' --or any name you want
    EXEC sp_addlinkedsrvlogin 'SRV', 'false', null, 'username', 'password'
    
    SELECT * FROM SRV.CatalogNameInSecondDB.dbo.SecondDBTableName s
    INNER JOIN FirstDBTableName f on s.ID = f.ID
    WHERE f.ID IN (list of values)
    
    EXEC sp_dropserver 'SRV', 'droplogins'
    

    【讨论】:

      猜你喜欢
      • 2018-02-02
      • 2021-03-01
      • 2016-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-14
      • 1970-01-01
      • 2018-09-12
      相关资源
      最近更新 更多