导入具有可变标头的excel文件

时间:2017-11-22 14:45:45

标签: sql-server excel ssis etl sql-server-data-tools

我有SSIS包,它会将excel文件加载到数据库中。我创建了Excel Source任务,将excel列名映射到Database表列名,并且工作正常。

在极少数情况下,我们收到的excel文件列名称带有一些空格(例如:列名是" ABC"但我们正在接收" ABC") 导致映射问题,SSIS失败。

是否可以在不打开Excel的情况下修剪列名。

注意:页面名称将是动态的,列位置可能会更改(例如:列" ABC可能存在于第一行或第二行或..")。

4 个答案:

答案 0 :(得分:4)

这已在MSDN中很好地记录,运行类似于@houseofsql提到的步骤

步骤1:

在excel连接的第一行中排除列名,使用sql命令作为数据访问模式 enter image description here

步骤2:输出列中的别名列名称与您的目的地匹配,

[Sheet1$A2:I]中选择*将从第二行中选择

enter image description here

最后将目的地添加为OLEDB目的地

enter image description here

答案 1 :(得分:4)

首先,我的解决方案基于@DrHouseofSQL和@Bhouse答案,所以你必须首先阅读@DrHouseofSQL答案然后@BHouse回答然后继续这个答案

问题

  

注意:页面名称将是动态的,列位置可能会更改(例如:列“ABC”可能存在于第一行或第二行或...

这种情况有点复杂,可以使用以下解决方法解决:

解决方案概述

  1. 在导入数据的数据流任务之前添加脚本任务
  2. 您必须使用脚本任务打开Excel文件并获取工作表名称和标题行
  3. 构建查询并将其存储在变量
  4. 在第二个数据流任务中,您必须使用上面存储的查询作为源(请注意,您必须将Delay Validation属性设置为true
  5. 解决方案详情

    1. 首先创建一个类型为string 的SSIS变量(即@ [User :: strQuery])
    2. 添加另一个包含Excel文件路径的变量(即@ [User :: ExcelFilePath])
    3. 添加脚本任务,选择@[User::strQuery]作为ReadWrite变量,@[User::ExcelFilePath]作为ReadOnly Variable (在脚本任务窗口中)
    4. 将脚本语言设置为VB.Net,并在脚本编辑器窗口中编写以下脚本:
    5. 注意:您必须导入System.Data.OleDb

      在下面的代码中,我们搜索ex​​cel前15行以查找标题,如果在15行之后可以找到标题,则可以增加数量。此外,我假设列范围从AI

          m_strExcelPath = Dts.Variables.Item("ExcelFilePath").Value.ToString
      
          Dim strSheetname As String = String.Empty
          Dim intFirstRow As Integer = 0
      
          m_strExcelConnectionString = Me.BuildConnectionString()
          Try
      
      
              Using OleDBCon As New OleDbConnection(m_strExcelConnectionString)
      
                  If OleDBCon.State <> ConnectionState.Open Then
                      OleDBCon.Open()
                  End If
      
                  'Get all WorkSheets
                  m_dtschemaTable = OleDBCon.GetOleDbSchemaTable(OleDbSchemaGuid.Tables,
                                                                     New Object() {Nothing, Nothing, Nothing, "TABLE"})
      
                  'Loop over work sheet to get the first one (the excel may contains temporary sheets or deleted ones
      
                  For Each schRow As DataRow In m_dtschemaTable.Rows
                      strSheetname = schRow("TABLE_NAME").ToString
      
                      If Not strSheetname.EndsWith("_") AndAlso strSheetname.EndsWith("$") Then
      
                          Using cmd As New OleDbCommand("SELECT * FROM [" & strSheetname & "A1:I15]", OleDBCon)
      
                              Dim dtTable As New DataTable("Table1")
      
      
                              cmd.CommandType = CommandType.Text
      
                              Using daGetDataFromSheet As New OleDbDataAdapter(cmd)
      
                                  daGetDataFromSheet.Fill(dtTable)
      
                                  For intCount As Integer = 0 To 15
      
                                      If Not String.IsNullOrEmpty(dtTable.Rows(intCount)(0).ToString) Then
      
                                          '+1 because datatable is zero based indexed, +1 because we want to start from the second row
                                          intFirstRow = intCount + 2
      
                                      End If
      
      
                                  Next
      
      
      
                              End Using
      
                              If intFirstRow = 0 Then Throw New Exception("header not found")
      
                          End Using
      
                          'when the first correct sheet is found there is no need to check others
                          Exit For
      
                      End If
                  Next
      
                  OleDBCon.Close()
      
              End Using
      
          Catch ex As Exception
              Throw New Exception(ex.Message, ex)
          End Try
      
      
          Dts.Variables.Item("strQuery").Value = "SELECT * FROM [" & strSheetname & "A" & intFirstRow.ToString & ":I]"
      
          Dts.TaskResult = ScriptResults.Success
      End Sub
      
      1. 然后您必须添加一个Excel连接管理器,并选择要导入的Excel文件(只选择一个样本以便仅首次定义元数据)
      2. 将默认值Select * from [Sheet1$A2:I]分配给变量@[User::strQuery]
      3. 在数据流任务中添加Excel源,从变量中选择SQL命令,然后选择@[User::strQuery]
      4. 转到“列”标签,并按照与@BHouse建议相同的方式命名列
      5. Image 图片来自@BHouse回答

        1. 将DataFlow任务Delay Validation属性设置为True
        2. 将其他组件添加到DataFlow任务
        3. 更新1:

          来自OP评论:sometimes excel with empty data will come.(i.e) we have only header row not not data... in that case it fails entire task

          <强>解决方案:

          如果您的Excel文件不包含任何数据(仅限标题),则必须执行以下步骤:

          1. 添加boolean *类型的SSIS变量(即@[User::ImportFile]
          2. @[User::ImportFile]添加到脚本任务ReadWrite variables
          3. 在“脚本任务”中检查文件是否包含行
          4. 如果是,则设置@[User::ImportFile] = True,否则@[User::ImportFile] = False
          5. 双击将脚本任务连接到DataFlow
          6. 的箭头(优先约束)
          7. 将其类型设置为Constraint and Expression
          8. 编写以下表达式

            @[User::ImportFile] == True
            
          9. 注意:新的脚本任务代码为:

                m_strExcelPath = Dts.Variables.Item("ExcelFilePath").Value.ToString
            
                Dim strSheetname As String = String.Empty
                Dim intFirstRow As Integer = 0
            
                m_strExcelConnectionString = Me.BuildConnectionString()
                Try
            
            
                    Using OleDBCon As New OleDbConnection(m_strExcelConnectionString)
            
                        If OleDBCon.State <> ConnectionState.Open Then
                            OleDBCon.Open()
                        End If
            
                        'Get all WorkSheets
                        m_dtschemaTable = OleDBCon.GetOleDbSchemaTable(OleDbSchemaGuid.Tables,
                                                                           New Object() {Nothing, Nothing, Nothing, "TABLE"})
            
                        'Loop over work sheet to get the first one (the excel may contains temporary sheets or deleted ones
            
                        For Each schRow As DataRow In m_dtschemaTable.Rows
                            strSheetname = schRow("TABLE_NAME").ToString
            
                            If Not strSheetname.EndsWith("_") AndAlso strSheetname.EndsWith("$") Then
            
                                Using cmd As New OleDbCommand("SELECT * FROM [" & strSheetname & "A1:I15]", OleDBCon)
            
                                    Dim dtTable As New DataTable("Table1")
            
            
                                    cmd.CommandType = CommandType.Text
            
                                    Using daGetDataFromSheet As New OleDbDataAdapter(cmd)
            
                                        daGetDataFromSheet.Fill(dtTable)
            
                                        For intCount As Integer = 0 To 15
            
                                            If Not String.IsNullOrEmpty(dtTable.Rows(intCount)(0).ToString) Then
            
                                                '+1 because datatable is zero based indexed, +1 because we want to start from the second row
                                                intFirstRow = intCount + 2
            
                                            End If
            
            
                                        Next
            
            
            
                                    End Using
            
            
            
            
            
                                End Using
            
                                'when the first correct sheet is found there is no need to check others
                                Exit For
            
                            End If
                        Next
            
                        OleDBCon.Close()
            
                    End Using
            
                Catch ex As Exception
                    Throw New Exception(ex.Message, ex)
                End Try
            
                            If intFirstRow = 0 OrElse _
                               intFirstRow > dtTable.Rows.Count Then
            
                                Dts.Variables.Item("ImportFile").Value = False
            
                            Else
            
                                Dts.Variables.Item("ImportFile").Value = True
            
                            End If                    
            
                Dts.Variables.Item("strQuery").Value = "SELECT * FROM [" & strSheetname & "A" & intFirstRow.ToString & ":I]"
            
                Dts.TaskResult = ScriptResults.Success
            End Sub
            

            更新2:

            来自OP评论:is there any other work around available to process the data flow task without skipping all data flow task,Actually one of the task will log the filename and data count and all, which are missing here

            <强>解决方案:

            1. 只需添加另一个DATA FLOW任务
            2. 使用其他连接器并使用表达式@[User::ImportFile] == False (第一个连接器的相同步骤)将此数据流与脚本任务连接
            3. 在DataFlow任务中添加SCript组件作为源
            4. 创建要导入日志的输出列
            5. 创建包含您需要导入的信息的行
            6. 添加日志目的地
            7. 或者您可以添加Data Flow Task以在日志表中插入行

              ,而不是添加其他Execute SQL Task

答案 2 :(得分:2)

是手动还是自动创建文件? 在任何一种情况下,您都可以从Excel文件中删除标题行(以编程方式或告诉人们在保存文件之前将其删除)。 完成后,进入Excel连接管理器,找到指示“第一行有列名”的框。如果您可以清除该框,则将列再次映射到应解决问题的目标位置。您永远不必担心列名称中拼写错误(或多余的空格)。

我认为SSIS中还有一个选项可以完全跳过第一行,但我不记得那个选项在哪里。如果您能找到,那么只需跳过Excel文件的第一行。同样的映射仍然存在。

谢谢

答案 3 :(得分:1)

我对这个论坛很新,所以如果你认为这很愚蠢,那就把它当作一粒盐。

MS Access具有与Excel相同的VBA功能,或者您可以编写一个新的存根Excel工作簿,该工作簿在SQL导入之前进行解析和格式化,然后导入(如果您愿意,则为中间件)。

关于尾随或领先空间的问题,我在很多场合使用了以下内容:

myString = trim(msytring)'这将删除所有前导和尾随空格,但不会乱用字符之间的任何空格。因此,在导入时,您可以在导入它们时对列标题运行修剪。

还有LTrim和RTrim'你可以猜到那些字符串的左右两边

https://support.office.com/en-us/article/LTrim-RTrim-and-Trim-Functions-e340ced1-67df-435f-b078-1527a4eddea2

对于大写,您可以使用UCase

myString = UCase(Trim(myString))

如果存在我经常处理的情况,那么替换总是派上用场,有时候用户可能会使用#char,有时候不会。

示例:“Patterson#288”或“PatTeRson 288” myString = UCase(Trim(Replace(myString,"#","")'消除#符号并删除前导和尾随空格,并在用户也犯错误的情况下将字母大写

非常方便地运行它是循环导入和导出。

现在,如果文件名正在更改(这是工作簿名称)或者工作表名称正在更改,您也可以让“中间件”始终将工作簿命名为相同的名称(包含您的工作簿内容)要导入)与工作表相同,或者你可以计算工作表数并记录名称(再次有机会在“中间件”中标准化和重命名)

我认为这不是SQL的答案,但是因为我对SQL不太好,我会准备数据,在这种情况下首先是excel工作簿并将其标准化以便导入,这样代码就不会在数据库方面中断(服务器端)。

我使用excel作为Access with SQL查询脚本的前端,它可以直接链接到SQL但是要困难得多。像PostGre SQL这样的.CSV友好数据库在这方面有所帮助。

我希望这会有所帮助。如果在导入之前需要帮助格式化工作簿,请复制并应用所有更改(命名,字段名称约定//列标题),请告诉我。我可能会帮忙。

这类似于V在工作簿上运行预处理脚本的注释。这就是我接近它的方式。

干杯, WWC