使用SSIS加载多个表以保持外键关系

时间:2010-02-06 03:04:27

标签: sql-server ssis

我正在尝试使用SSIS将数据从单个文件(带有百万条+记录)加载到SQL Server上的多个表中,同时保持文件中定义的关系。

为了更好地举例说明,我们假设我正在尝试加载一个包含员工姓名的文件,他们过去占用的办公室以及用标签分隔的职位名称历史记录。

文件:

EmployeeName<tab>OfficeHistory<tab>JobLevelHistory
John Smith<tab>501<tab>Engineer
John Smith<tab>601<tab>Senior Engineer
John Smith<tab>701<tab>Manager
Alex Button<tab>601<tab>Senior Assistant
Alex Button<tab>454<tab>Manager

如果我的Office数据库架构具有以下表格:

Employee (nId, name)
Office (nId, number)
JobTitle (nId, titleName)
Employee2Office (nEmpID, nOfficeId)
Employee2JobTitle (nEmpId, nJobTitleID)

如何使用SSIS将文件加载到自动生成员工,Office和JobTitle的ID以及维护员工和办公室之间以及员工和职位标题之间的关系模式中?

所以在这种情况下。表格应如下所示:

Employee
1 John Smith
2 Alex Button

Office
1 501
2 601
3 701
4 454

JobTitle
1 Engineer
2 Senior Engineer
3 Manager
4 Senior Assistant

Employee2Office
1 1
1 2
1 3
2 2
2 4

Employee2JobTitle
1 1
1 2
1 3
2 4
2 3

我是SSIS的新手,并且在执行数据流任务时没有使用自动生成ID和建立外键关系。任何指针都会受到赞赏。

谢谢!

4 个答案:

答案 0 :(得分:2)

一个有趣的问题。这是我将如何做到这一点(Sql Server 2005)。 (我假设这是一个月工作而不只是一次,所以我添加了重复性代码。)

  1. 为Employee,JobTitle和Office表创建三个变量(type = Object)
  2. 使用三个sql任务将这三个表的行选择到相应的变量中。
  3. 添加数据流任务。
  4. 使用平面文件目的地从平面文件中选择。
  5. 输出进入脚本组件,平面文件中的三列作为输入,三个表变量导入脚本,脚本组件中的五个输出,每个输出具有相同的排除组编号,输入标记为对于该输出同步,添加到输出的七个新列(每个输出3个用于emp一个,2个用于作业,2个用于office),并使用以下代码(对System.xml.dll的引用必须添加使这一切都有效。):

    Imports System
    Imports System.Data
    Imports System.Math
    Imports Microsoft.SqlServer.Dts.Pipeline.Wrapper
    Imports Microsoft.SqlServer.Dts.Runtime.Wrapper
    Imports System.Collections
    Imports System.Data.OleDb
    
    Public Class ScriptMain
        Inherits UserComponent
    
        Private da As New OleDbDataAdapter
        Private emp As New DataTable
        Private emph As New Hashtable()
        Private job As New DataTable
        Private jobh As New Hashtable()
        Private off As New DataTable
        Private offh As New Hashtable()
        Private maxempid As Integer
        Private maxjobid As Integer
        Private maxoffid As Integer
    
        Public Overrides Sub PreExecute()
            maxempid = 0
            maxjobid = 0
            maxoffid = 0
            da.Fill(emp, Me.Variables.EmpTab)
            For Each dr As DataRow In emp.Rows
                emph.Add(dr.Item("Name"), dr.Item("nID"))
                If (CInt(dr.Item("nID").ToString) > maxempid) Then
                    maxempid = CInt(dr.Item("nID").ToString)
                End If
            Next
            da.Fill(job, Me.Variables.JobTab)
            For Each dr As DataRow In job.Rows
                jobh.Add(dr.Item("titleName"), dr.Item("nID"))
                If (CInt(dr.Item("nID").ToString) > maxempid) Then
                    maxjobid = CInt(dr.Item("nID").ToString)
                End If
            Next
            da.Fill(off, Me.Variables.OffTab)
            For Each dr As DataRow In off.Rows
                offh.Add(dr.Item("number"), dr.Item("nID"))
                If (CInt(dr.Item("nID").ToString) > maxempid) Then
                    maxoffid = CInt(dr.Item("nID").ToString)
                End If
            Next
            emp.Dispose()
            job.Dispose()
            off.Dispose()
            da.Dispose()
            MyBase.PreExecute()
        End Sub
    
    Public Overrides Sub Input0_ProcessInputRow(ByVal Row As Input0Buffer)
    
        If Not emph.ContainsKey(Row.EmployeeName) Then
            maxempid += 1
            emph.Add(Row.EmployeeName, maxempid)
            Row.EmpId = maxempid
            Row.Emp2Id = maxempid
            Row.Emp3Id = maxempid
            Row.DirectRowToEmployee()
        Else
            Row.EmpId = CInt(emph.Item(Row.EmployeeName).ToString)
            Row.Emp2Id = CInt(emph.Item(Row.EmployeeName).ToString)
            Row.Emp3Id = CInt(emph.Item(Row.EmployeeName).ToString)
        End If
        If Not jobh.ContainsKey(Row.JobLevelHistory) Then
            maxjobid += 1
            jobh.Add(Row.JobLevelHistory, maxjobid)
            Row.JobId = maxjobid
            Row.Job2Id = maxjobid
            Row.DirectRowToJobTitle()
        Else
            Row.JobId = CInt(jobh.Item(Row.JobLevelHistory).ToString)
            Row.Job2Id = CInt(jobh.Item(Row.JobLevelHistory).ToString)
        End If
        If Not offh.ContainsKey(Row.OfficeHistory) Then
            maxoffid += 1
            offh.Add(Row.OfficeHistory, maxoffid)
            Row.OffId = maxoffid
            Row.Off2Id = maxoffid
            Row.DirectRowToOfficeNumber()
        Else
            Row.OffId = CInt(offh.Item(Row.OfficeHistory).ToString)
            Row.Off2Id = CInt(offh.Item(Row.OfficeHistory).ToString)
        End If
        Row.DirectRowToEmp2Job()
        Row.DirectRowToEmp2Off()
    End Sub        
    End Class
    
  6. 此脚本的结果(该脚本为输入数据中的新值生成id。它通过将现有表加载到脚本的预执行部分中的哈希表中,然后检查是否存在名称,并基于此增加maxid并将其添加到哈希,如果它添加到哈希,它还将行添加到适当的(emp,job或off)输出,或从每行的哈希中检索maxid 。)无论上述状态如何,所有行都将写入剩余的两个输出(emp2job和emp2off)。

  7. 接下来,将数据流发送到查找(以检查目标表中的现有行,然后检查目标oledb连接器(emp,job和off,选中标识插入框,emp2job和emp2off取消选中检查约束)。 / LI>

答案 1 :(得分:1)

如果您确定要加载的数据的参照完整性正常,则可以在脚本任务中禁用外键约束,然后使用并行数据加载执行数据流,并在数据加载完成后启用约束再次。如果数据不正确,操作将失败。您必须设计回滚或清理策略。

另一种选择只是以串行方式加载数据,从主表开始并在子表上完成。我认为这是“更安全”的选项,因为它不会将您的数据完整性暴露给在ETL加载时可能正在使用这些表的其他用户。我更喜欢这个选项。

答案 2 :(得分:1)

这是怎么回事 - 在文本中解释起来有点困难,但我会试一试:

使用标识列定义数据库中的员工,办公室和职位名称表,以便自动生成ID。

定义多对多表(不需要身份或有用身份)

在您的SSIS数据流中,您必须在几次传递中执行此操作,以便首先在数据库中建立ID,然后返回并插入多对多行。

制作一个数据流:

  1. 放入数据源以读入文件
  2. 使用Multi-Cast将其拆分为三份。一个是员工,一个办公室,一个职称。
  3. 对于每一个,放一个Sort(这通常是禁止的,但由于源是文本而不是数据库,我们必须去那里)。将排序设置为仅传递三个字段中的一个,并检查排序中的选项以删除重复项。这为每个基表创建了一个唯一的列表(如select distinct)。
  4. 为三个连接到每个表的每一个放置一个目的地。
  5. 在第一个数据流之后,添加第二个数据流。这个将填充多对多关系行

    1. 使用数据源读取文件
    2. 在数据库中添加查找“员工姓名”的查找,并返回“员工ID”。这将获得上面生成的员工ID。 (这通常称为按业务查询或代理键的自然键)
    3. 添加在数据库中查找Title的查找并返回标题ID
    4. 添加在数据库中查找Office的查找并返回Office ID
    5. 再次,将结果分为两个副本,一个用于员工办公室,另一个用于员工职称
    6. 根据您需要的逻辑,也许再次使用Sort来对它们进行重复数据删除(取决于您从输入中规范化的细节)
    7. 将结果放入具有两个目的地的多对多表中。

答案 3 :(得分:0)

您可以编写一个存储过程,首先在基本表(employee,office和JobTitle)中插入数据,然后逐行读取您的文件。之后,您应该搜索基本表以获取标识并将数据插入关系表。