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

时间:2017-05-02 19:59:29

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

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

5 个答案:

答案 0 :(得分:11)

有3种方法可以实现这一目标:

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

首先你必须添加Lookup Transformation来回答@TheEsisia,但还有更多要求:

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

  • 至少你必须从查询表中选择一列

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

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

enter image description here

  1. 将错误处理设置为Ignore Row,以便添加的列(来自查找)值为空,因此您必须添加Conditional split来过滤具有等于NULL的值的行。
  2. 假设您已选择col1作为查阅列,因此您必须使用类似的表达式

    ISNULL([col1]) == False
    
    1. 或者您可以将错误处理设置为Redirect Row,因此所有行都将被发送到错误输出行,这些行可能不会被使用,因此数据将被过滤
    2. 此方法的缺点是在执行期间加载和过滤所有数据。

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

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

      为了避免加载所有数据,您可以执行一种解决方法,您可以使用脚本任务来实现此目的:(在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任务
      2. 之前添加执行SQL任务
      3. ResultSet属性设置为single
      4. 选择User::MyVariableList作为结果集
      5. 使用以下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
        
      6. enter image description here

        enter image description here

        确保您已将DataFlow Task Delay Validation媒体资源设为True

答案 1 :(得分:5)

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

的步骤
  1. General标签中,选择Full CashOLE DB Connection ManagerRedirect rows to no match output,如下图所示。请注意,使用Full Cash可以为您的软件包提供出色的性能。
  2. 常规设置

    enter image description here

    1. Connection标签中,使用OLE DB Connection Manager连接到第二台服务器。然后,您可以直接选择具有ID值的数据集,或者(如下图所示)您可以使用SQL代码从过滤数据集中选择ID。
    2. <强>连接:

      enter image description here

      1. 转到Columns标签,然后从两个数据集中选择ID列。对于第一个数据集中的每条记录,它会检查其ID是否在Available LookUp Column中。如果是,则会转到Matching输出,否则转到No Matching输出。
      2. 匹配ID列:

        enter image description here

        1. 点击OK关闭LookUp。然后,您需要选择LookUp Match Output
        2. 匹配输出:

          enter image description here

答案 2 :(得分:1)

我首先要创建一个String变量,例如SQL_Select,在包的范围内。然后我将使用执行SQL任务为第一个数据库分配一个值。 常规页面上的 ResultSet 属性应设置为单行。在结果集标签中添加一个条目,将其分配给您的变量。

使用的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语句源。

答案 3 :(得分:1)

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

许多其他答案建议根据SQL Server中的智能连接构建值列表。如果引用的系统是Oracle,MySQL,DB2,Informix,PostGres等,那么效果不会很好。可能是一个等效的概念,但可能

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

如果您有大量值要对源表进行筛选,则可以在该服务器上创建表并截断并重新加载该表(执行sql task + data flow)。这允许您将所有数据都置于本地,然后您可以索引过滤器表并让数据库引擎执行它真正擅长的操作。

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

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

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

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

如果由于某种原因无法使用Lookup组件,那么您可以在源系统中显式排序数据,标记源组件,然后在数据流中使用内部连接类型的合并连接只引入匹配的数据。

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

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

答案 4 :(得分:0)

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