【问题标题】:Importing excel files having variable headers导入具有可变标题的 excel 文件
【发布时间】:2017-12-03 01:03:48
【问题描述】:

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

在极少数情况下,我们收到带有空格的 excel 文件列名(例如:列名是“ABC”但我们收到的是“ABC”),这会导致映射问题和SSIS 失败。

有没有可能在不打开excel的情况下修剪列名。

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

【问题讨论】:

  • 当您说注意:页面名称将是动态的并且列位置可能会改变(例如:列“ABC可能存在于第一行或第二行或..”)您的意思 注意:页面名称将是动态的,并且列位置可能会改变(例如:列“ABC 可能存在于第一个 column 或第二个 column 或 ..”) ?
  • @VigneshKumar 如果您考虑将 BHouseDrHouseofSQL 作为第一步,那么您可以结合使用 Hadi 解决方案.然后你就有了一个 100% 有效的解决方案。我认为您不会收到更好的解决方案,因为您要求的是一个复杂的情况问题

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


【解决方案1】:

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

第一步:

excel连接第一行排除列名,使用sql命令作为数据访问方式

第 2 步: 输出列中的别名列名与您的目标匹配,

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

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

【讨论】:

  • 您好,感谢您的想法,但复杂的是我们使用的是动态页面名称
  • 你的意思是名字是excel表的变化?
  • 是的。文件名和 Excel 工作表名都是动态的
  • 您是否尝试过使用变量作为源路径的相同步骤
  • 没有...有没有办法通过脚本任务打开excel并更新列?
【解决方案2】:

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

问题

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

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

解决方案概述

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

解决方案详情

  1. 首先创建一个字符串类型的 SSIS 变量(即@[User::strQuery])
  2. 添加另一个包含 Excel 文件路径的变量(即@[User::ExcelFilePath])
  3. 添加一个脚本任务,选择@[User::strQuery]作为读写变量,@[User::ExcelFilePath]作为只读变量(在脚本任务窗口中)
  4. 将脚本语言设置为 VB.Net,并在脚本编辑器窗口中编写以下脚本:

注意:你必须导入System.Data.OleDb

在下面的代码中,我们在excel中搜索前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 建议的方式命名列

图片取自@BHouse 回答

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

更新 1:

来自 OP cmets: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. 添加布尔类型的 SSIS 变量 *(即@[User::ImportFile]
  2. @[User::ImportFile] 添加到脚本任务ReadWrite 变量中
  3. 在脚本任务中检查文件是否包含行
  4. 如果是则设置@[User::ImportFile] = True,否则设置@[User::ImportFile] = False
  5. 双击将脚本任务连接到数据流的箭头(优先级约束)
  6. 将其类型设置为约束和表达式
  7. 写出下面的表达式

    @[User::ImportFile] == True
    

注意:新的脚本任务代码为:

    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 cmets: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. 只需添加另一个数据流任务
  2. 使用另一个连接器和表达式@[User::ImportFile] == False 将此数据流与脚本任务连接(与第一个连接器的步骤相同)
  3. 在 DataFlow 任务中添加一个 SCript 组件作为源
  4. 创建要导入日志的输出列
  5. 创建一个包含您需要导入的信息的行
  6. 添加日志目标

或者您可以添加Execute SQL Task 来在日志表中插入一行,而不是添加另一个Data Flow Task

【讨论】:

  • 感谢您提供详细信息..它工作正常...除了一种情况..有时会出现带有空数据的excel。(即)我们只有标题行而不是数据...在这种情况下它会失败整个任务。
  • @VigneshKumar,你是对的,有一个简单的解决方法,在脚本任务和数据流任务之间的连接器中添加一个表达式。我更新了我的答案,看看
  • 是的,它正在工作@hadi,但是否有任何其他解决方法可用于处理数据流任务而不跳过所有数据流任务。
  • 其实其中一项任务会记录文件名和数据计数等等,这里是missssig
  • @VigneshKumar 只需添加另一个以脚本组件为源的数据流任务,并创建包含日志值的行。使用另一个连接器和表达式 @[User::ImportFile] == False 将此数据流与脚本任务连接
【解决方案3】:

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

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

谢谢

【讨论】:

  • 文件将自动创建。
【解决方案4】:

我是论坛的新手,所以如果您认为这很愚蠢,请持保留态度。

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

对于尾随或前导空格的问题,我多次使用以下方法:

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

还有 LTrim 和 RTrim '你可以猜出它们在字符串的左右两边做了什么

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

对于大写,你可以使用 UCase

myString = UCase(Trim(myString))

如果出现这样的情况,替换总是会派上用场,因为我经常处理有时用户可能会使用 # 字符而有时不使用的情况。

示例:“Patterson #288”或“PatTeRson 288” myString = UCase(Trim(Replace(myString,"#","") '去掉#号,去掉前导空格和尾随空格,同时将字母大写,以防用户也犯了错误

很方便运行这是循环导入和导出。

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

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

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

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

这类似于 V 对在工作簿上运行预处理脚本的评论。我就是这样处理它的。

干杯, 世界大会

【讨论】:

    猜你喜欢
    • 2020-08-20
    • 2012-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-15
    • 1970-01-01
    • 2017-09-17
    相关资源
    最近更新 更多