供应商

时间:2017-12-18 18:44:56

标签: sql-server csv ssis etl

我有一些SSIS软件包可以获取来自供应商的CSV文件并将它们放入我们的本地数据库中。我遇到的问题是有时供应商添加或删除列,我们没有时间在下次运行之前更新我们的包,这会导致SSIS包异常终止。我想以某种方式阻止这种情况发生。

我尝试逐行读取CSV文件,删除新列,然后使用insert语句将更改的行放入表中,但这比我们当前的进程要长得多(CSV文件可以有成千上万的记录。

我开始考虑使用ADO连接,但我的本地机器既没有ACE也没有JET提供程序,我认为部署的服务器也缺少这些提供程序(我怀疑我可以在部署中安装它们服务器)。

我不知道我能做些什么来加载表并能够忽略新添加或删除的列(尽管如果CSV文件缺少表的列,那不是什么大不了的事)这是快速和可靠的。有什么想法吗?

4 个答案:

答案 0 :(得分:1)

我采用了不同的方法,这似乎有效(在我解决了一些问题之后)。我所做的是获取CSV文件行并将它们放入临时数据表中。完成后,我从数据表到我的数据库进行了批量复制。为了处理缺失或新列,我确定了CSV和表共有哪些列,并且只处理了这些常用列(在日志文件中记录了新列,以便稍后添加它们)。这是我的BulkCopy模块:

    Private Sub BulkCopy(csvFile As String)
    Dim i As Integer
    Dim rowCount As Int32 = 0
    Dim colCount As Int32 = 0
    Dim writeThis As ArrayList = New ArrayList

    tempTable = New DataTable()
    Try
        '1) Set up the columns in the temporary data table, using commonColumns

        For i = 0 To commonColumns.Count - 1
            tempTable.Columns.Add(New DataColumn(commonColumns(i).ToString))
            tempTable.Columns(i).DataType = GetDataType(commonColumns(i).ToString)
        Next

        '2) Start adding data from the csv file to the temporary data table

        While Not csvReader.EndOfData
            currentRow = csvReader.ReadFields() 'Read the next row of the csv file
            rowCount += 1
            writeThis.Clear()

            For index = 0 To UBound(currentRow)
                If commonColumns.Contains(csvColumns(index)) Then
                    Dim location As Integer = tableColumns.IndexOf(csvColumns(index))
                    Dim columnType As String = tableColumnTypes(location).ToString

                    If currentRow(index).Length = 0 Then
                        writeThis.Add(DBNull.Value)
                    Else
                        writeThis.Add(currentRow(index))
                    End If
                    'End Select
                End If
            Next

            Dim row As DataRow = tempTable.NewRow()
            row.ItemArray = writeThis.ToArray
            tempTable.Rows.Add(row)
        End While
        csvReader.Close()

        '3) Bulk copy the temporary data table to the database table.

        Using copy As New SqlBulkCopy(dbConnection)
            '3.1) Set up the column mappings
            For i = 0 To commonColumns.Count - 1
                copy.ColumnMappings.Add(commonColumns(i).ToString, commonColumns(i).ToString)
            Next

            '3.2) Set the destination table name
            copy.DestinationTableName = tableName

            '3.3) Copy the temporary data table to the database table
            copy.WriteToServer(tempTable)

        End Using
    Catch ex As Exception
        message = "*****ERROR*****" + vbNewLine
        message += "BulkCopy: Encountered an exception of type " + ex.GetType.ToString()
        message += ": " + ex.Message + vbNewLine + "***************" + vbNewLine
        LogThis(message)
    End Try
End Sub

可能会有一些更优雅的东西,但到目前为止似乎有效。

答案 1 :(得分:0)

了解BiML,它在运行时根据元数据动态构建和执行您的SSIS包。

答案 2 :(得分:0)

基于此评论:

  

我尝试逐行读取CSV文件,删除新内容   列,然后使用insert语句放置更改的行   进入表格,但这比我们当前的过程要长得多   (CSV文件可能有数千或数十万个   记录)。

而且:

  

我用csvreader来读取文件。插入是通过sqlcommand   对象

乍一看,瓶颈不在平面文件源中,而是在目标中。 OLEDB命令以行方式执行,每个输入行一个语句。通过将其更改为OLEDB目标,它会将进程转换为批量插入操作。要测试它,只需使用平面文件源并将其连接到派生列。运行它并检查速度。如果速度更快,请更改为oledb目的地并重试。它也有助于插入堆(没有聚簇索引或非聚簇索引)并使用tablock。

但是,这并不能解决您的各种文件问题。我不知道如果你在设计时最初配置它的方式缩短了一列或更多列,那么平面文件源会起什么作用。它可能会失败,或者它可能以某种锯齿形式导入行,其中下一行的一部分被分配给当前行的最后一列。这可能是个烂摊子。

但是,我确实知道当平面文件源获得额外列时会发生什么。我为它添加了这个连接项目,但遗憾地被拒绝了:https://connect.microsoft.com/SQLServer/feedback/details/963631/ssis-parses-flat-files-incorrectly-when-the-source-file-contains-unexpected-extra-columns

更多的列会连接到最后一列。如果您计划它,可以使最后一列变大,然后从登台表中解析SQL。此外,您可以将整行插入SQL并从那里解析每一列。这有点笨拙但是因为你会有很多CHARINDEX()检查所有位置的值的位置。

更简单的选择可能是在脚本任务中使用split()的一些组合在.Net中解析它以获取所有值并检查数组中的值的数量以了解您拥有的列数。这也允许您根据找到的内容将行指向不同的缓冲区。

最后,您可以要求供应商提交格式。固定数量的列或使用处理XML等变体的格式。

答案 3 :(得分:0)

对于源脚本组件,我有一个C#解决方案(我还没有检查过,但我认为它有效)。

它会使用split将数据读入数组。

然后对于每个数据行使用相同的split函数并使用标头值检查列并使用rowval设置输出。

您需要将所有输出列放入输出区域。

所有不存在的列在退出时都会显示空值。

public override void CreateNewOutputRows()
    {


        using (System.IO.StreamReader sr = new System.IO.StreamReader(@"[filepath and name]"))
        {
            while (!sr.EndOfStream)
            {
                string FullText = sr.ReadToEnd().ToString();
                string[] rows = FullText.Split('\n');

                //Get header values
                string[] header = rows[0].Split(',');


                for (int i = 1; i < rows.Length - 1; i++)
                {
                    string[] rowVals = rows[i].Split(',');
                    for (int j = 0; j < rowVals.Length - 1; j++)
                    {

                        Output0Buffer.AddRow();
                        //Deal with each known header name
                        switch (header[j])
                        {
                            case "Field 1 Name": //this is where you use known column names
                                Output0Buffer.FieldOneName = rowVals[j]; //Cast if not string
                                break;
                            case "Field 2 Name":
                                Output0Buffer.FieldTwoName = rowVals[j]; //Cast if not string
                                break;
                            //continue this pattern for all column names
                        }
                    }

                }
            }
        }


    }