根据来自另一个数据库的查询结果查询数据库

Query a database based on result of query from another database

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

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

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

常规设置

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

连接:

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

匹配 ID 列:

  1. 单击 OK 关闭 LookUp。那么你需要select LookUp Match Output.

匹配输出:

有 3 种方法可以实现:

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

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

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

  • 至少你必须 select 查找中的一列 table

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

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

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

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

ISNULL([col1]) == False
  1. 或者你可以设置Error handling为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. Select 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

我会先创建一个字符串变量,例如SQL_Select,在包的范围内。然后我会使用 Execute SQL Task 对第一个数据库分配一个值。 General 页面上的 ResultSet 属性 应设置为 Single row。在 Result Set 选项卡中添加一个条目以将其分配给您的变量。

使用的 SQL 语句需要设计成 return 第二个数据库所需的 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用你的专栏和 table 名字。

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

"best" 答案取决于所涉及的数据量和源系统。

许多其他答案建议在 SQL 服务器中基于巧妙的串联构建一个值列表。如果引用的系统是 Oracle、MySQL、DB2、Informix、PostGres 等,则效果不佳。可能 是等效概念,但可能 不是是。

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

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

但是,您说源数据库是一些自定义解决方案,您无法在其中创建 tables。您可以使用临时 tables 查看上述方法,在 SSIS 中,您只需需要将连接标记为 singleton/persisted(TODO:查找)。我不太关心 SSIS 的临时 tables,因为调试它们是一场噩梦,我不希望我的死敌。

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

现在我们只能使用纯 SSIS 解决方案。要获得最佳性能,请不要 select 下拉列表中的 table 名称 - 除非您绝对需要每一列。另外,请注意您的数据类型。将 LOB (XML, text, image (n)varchar(max), varbinary(max)) 拉入数据流会导致性能不佳。

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

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

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

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

您可以在两个服务器之间添加一个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'