【问题标题】:Parse MS Excel files dynamically with SSIS使用 SSIS 动态解析 MS Excel 文件
【发布时间】:2016-05-21 04:43:32
【问题描述】:

我有一个业务需求,希望能够将 Excel 文件放置在网络位置,并将这些文件中的数据上传到 SQL Server 数据库。这些文件将包含 1 个数据工作表。这些文件对应于在已知数据库中找到的表。这些文件可以并且将与多个表相关联,并且只有在打开文件时才会知道,即工作表的名称。我目前正在为这些文件中的每一个创建多个 SSIS 包,因为它们被上传到共享驱动器,但有时,我创建包的速度不够快。

我想我的问题是,这种类型的动态解析是 SSIS 可以通过 Foreach 容器中的脚本任务完成的吗?还是我应该考虑另一种选择?

到目前为止,我有以下内容...但是根据我的研究,我遇到过类似的帖子:Extracting excel files with SSIS 这让我对可行性有点担心...

public void Main()
{
        // TODO: Add your code here

        Dts.TaskResult = (int)ScriptResults.Success;

        string NetworkLocation;

        //Create database connection
        SqlConnection myADONETConnection = new SqlConnection();
        myADONETConnection = (SqlConnection)(Dts.Connections["db"].AcquireConnection(Dts.Transaction) as SqlConnection);

        //Obtain the location of the file(s)
        NetworkLocation = (string)Dts.Variables["User::NetworkLocation"].Value;

        string[] dirs = Directory.GetFiles(NetworkLocation, "*.csv");


    }

那么,有什么想法或想法或我应该研究的方向吗?

【问题讨论】:

  • 我很肯定我可以帮助你解决这个问题,因为几个月前我不得不写一些类似的东西,从一个文件夹中读取许多 Excel 文件,每个 Excel 文件包含一个或多个工作表未知名称,并且每个工作表包含一列或多列未知名称。 Foreach 循环容器是要走的路。我有几个问题。每个文件中的每个工作表是否包含相同的字段名称,或者它们是否根据已知数据库中的源表而有所不同?
  • 另外,所有的 Excel 文件都是 .xlsx 或 xls 类型的吗?它们是从同一个文件夹中读取的,还是从不同文件夹中读取的?
  • 文件将包含不同的字段名称。现在,我看到了 .csv 和 .xls 文件。所以,公平地假设我会看到 .xls、xlsx 和 csv。
  • 好的,我会给你我申请的,包括厨房水槽。但我试图适当地评论事情,以便您可以删除不需要的内容。至于不同的文件类型,我通过应用适当的驱动程序来处理 xls 和 xlsx 文件类型的方法。您可能需要为其他文件类型(如 CSV)应用单独的 ForEach 循环容器。尽管如此,所有这些都可以包含在单个 .dtsx SSIS 包中
  • 一个缩写:BIML。查一下。

标签: ssis


【解决方案1】:

几个月前,我编写了一个 SSIS 包,它完全符合您的要求,而且还有更多功能。在我的例子中,数百个包含一个或多个不同名称的工作表的 Excel 文件需要作为唯一的临时表导入到数据库中。此外,每个工作表中的列名和列数都是未知的。每个工作表都成为自己的表格,表格名称是原始 Excel 文件名和工作表名称 (FileName__WorksheetName) 的组合。如果文件和工作表名称包含下划线,我在文件名和工作表名称之间应用了两个下划线。这个过程有一些注意事项: 1)所有的 Excel 文件必须位于同一个文件夹中; 2)每个工作表中的列标题必须出现在第一行; 3) 工作表名称不得包含任何特殊字符(空格会自动替换为下划线)。

步骤:

1) 为每个循环容器创建一个。在集合下,应用“Foreach 文件枚举器”,在枚举器配置下,列出文件夹位置和文件。对于文件,您可以列出 . 甚至 *.xlsx 或 *.xls 以过滤到特定文件。申请完全合格。对于变量映射,应用索引为 0 的字符串用户变量,例如“ExcelFile”。

2) 在 For Each 循环容器中添加一个脚本任务。您将向其发送 ReadOnlyVariable“ExcelFile”,它将写入 ReadWriteVariables 下的两个新字符串变量“TableName”和“Worksheets”。应用以下 C# 脚本。请注意,由于以下脚本将更新您的 Excel 文件,因此您应该应用原件的副本。

#region Namespaces
using System;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;
using System.IO;
using Excel = Microsoft.Office.Interop.Excel;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
using System.Data.OleDb;
using System.Xml.Serialization;

#endregion

namespace xxxxxxxxx
{

[Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
{

    public void Main()
    {

    // Includes full path, filename and extension... C:\Documents\ThisExcel.xlsx
    string xlFile = Dts.Variables["User::ExcelFile"].Value.ToString();

    // Remove path changing value to "ThisExcel.xlsx"
    string NoPath = Path.GetFileName(xlFile);

    // Remove extension changing value to "ThisExcel".
    // This is applied because filename will become part of the name for new database tables
    string tableName = Path.GetFileNameWithoutExtension(NoPath);

    // Replace any spaces with underscores in tableName (FileName without path and extension)
    tableName = tableName.Replace(" ", "_");
    Dts.Variables["User::TableName"].Value = tableName;

    Excel.Application app = new Excel.Application();
    Excel.Workbook excelWorkbook;


        try
        {
            excelWorkbook = app.Workbooks.Open(xlFile);

            string tempsheet = " ";
            int CountWorksheets = excelWorkbook.Sheets.Count;
            //Dts.Variables["User::WorksheetCount"].Value = CountWorksheets;

            string[] Excelworksheets;
            Excelworksheets = new string[CountWorksheets];
            int x = 0;

            // Rename worksheets replace empty space with an underscore needed for an SSIS import and 
            // to avoid empty spaces in final table names.
            foreach (Excel.Worksheet sheet in excelWorkbook.Worksheets)
            {
                tempsheet = sheet.Name;
                tempsheet = tempsheet.Replace(" ", "_");
                Excelworksheets[x++] = tempsheet.ToString();
                sheet.Name = tempsheet;
            }

            Dts.Variables["User::Worksheets"].Value = Excelworksheets;

            excelWorkbook.Save();
            excelWorkbook.Close();
        }
        catch (Exception ex)
        {
            MessageBox.Show("Excel sheet rename failed for file " + xlFile + " based on " + ex.Message);
        }
        finally
        {
            app.Quit();
            app = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

        Dts.TaskResult = (int)ScriptResults.Success;
    }

    #region ScriptResults declaration
    enum ScriptResults
    {
        Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
        Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
    };
    #endregion

}
 }

3) 保存并构建上述C#脚本任务后,在刚才创建的脚本任务下方的前面的For Each Loop容器中添加一个For Each Loop Container。这将遍历每个 Excel 文件中的每个工作表。如果你只有一个工作表,那很好。它将应用“Foreach From Variable Enumerator”的枚举器,这将是在前面提到的脚本任务中填充的“Worksheets”字符串变量。它将写入索引为 0 的名为“Worksheet”的新用户字符串变量。

4) 在这个新的嵌套 For Each 循环容器中,添加将为每个工作表创建数据库表的脚本任务。我必须在这里处理的棘手部分是定义字段类型,这不会从 Excel 工作表或文本 CSV 文件中保留。因此,我将它们全部设为 nvarchar(255),或者,如果列标题类似于 Remark、Description 或其他内容,我将其设为 nvarchar(max),它适用于 4000 或 4262 个字符(我不确定)。这是我根据您的开始应用的动态代码。

#region Namespaces
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Data.OleDb;
using System.Xml.Serialization;
using System.IO;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;
#endregion

namespace yyyyyyyyyy
{
    [Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase

    public void Main()
    {
        // TODO: Add your code here
        string xlFile = Dts.Variables["User::ExcelFile"].Value.ToString();      //Includes full path and filename with extension
        //xlFilex = xlFilex.Replace(@"\", @"\\");

        string worksheet = Dts.Variables["User::Worksheet"].Value.ToString();   //Worksheet name from Excel file.
        string Tablename = Dts.Variables["User::TableName"].Value.ToString();   //Currently file name without path and extension.  Spaces replaced by underscores.

        string ExcelExtension = Path.GetExtension(xlFile);

        string columnName = "";
        string columnType = "";
        int i = 0;
        string worksheet2 = worksheet + "$";


        OleDbConnection xl = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + xlFile + ";Extended Properties=\"Excel 12.0 Xml;HDR=YES;IMEX=1\"");
        xl.Open();

        System.Data.DataTable dt = xl.GetOleDbSchemaTable(OleDbSchemaGuid.Columns, new object[] { null, null, worksheet2, null });

        List<string> listColumn = new List<string>();


        // Create the name of the table that will be created in the SQL Server database, which is
        // a concatentation of the root file name and worksheet name separated by two undescores.
        Tablename = Tablename + "__" + worksheet;

        string CreateTable = "CREATE TABLE " + Tablename + " (";
        string InsertTable = "INSERT INTO " + Tablename + " (";
        string SelectColumns = "";

        // Create the string that will be applied to create the table defining the field types based on the names
        foreach (DataRow row in dt.Rows)
        {
            listColumn.Add(row["Column_name"].ToString());
            columnName = listColumn[i].ToString();

            if ((columnName == "Remark") || (columnName == "remark") || (columnName == "REMARK") ||
                (columnName == "Remarks") || (columnName == "remarks") || (columnName == "REMARKS") ||
                (columnName.Contains("Remarks")) || (columnName.Contains("remarks")) || (columnName.Contains("REMARKS")) ||
                (columnName.Contains("Remark")) || (columnName.Contains("remark")) || (columnName.Contains("REMARK")) ||
                (columnName == "Comment") || (columnName == "comment") || (columnName == "COMMENT") ||
                (columnName == "Comments") || (columnName == "comments") || (columnName == "COMMENTS") ||
                (columnName == "Description") || (columnName == "description") || (columnName == "DESCRIPTION") ||
                (columnName.Contains("Description")) || (columnName.Contains("description")) || (columnName.Contains("DESCRIPTION")) ||
                (columnName == "Legal") || (columnName == "legal") || (columnName == "LEGAL") ||
                (columnName == "Note") || (columnName == "note") || (columnName == "NOTE") ||
                (columnName.Contains("Format")) || (columnName.Contains("format")) || (columnName.Contains("FORMAT")) ||
                (columnName == "Notes") || (columnName == "notes") || (columnName == "NOTES")
                )
            {
                columnType = "nvarchar(max),";
            }
            else
            {
                columnType = "nvarchar(255),";
            }

            CreateTable = CreateTable + "[" + columnName + "] " + columnType;
            InsertTable = InsertTable + "[" + columnName + "],";
            SelectColumns = SelectColumns + "[" + columnName + "],";
            //MessageBox.Show(columnName + " " + columnType);
            i++;
        }

        // Remove last comma from CreateTable and add closing
        CreateTable = CreateTable.Remove(CreateTable.Length - 1);
        CreateTable = CreateTable + ")";

        // Removoe last comman from InsertTable and add closing
        InsertTable = InsertTable.Remove(InsertTable.Length - 1);
        InsertTable = InsertTable + ")";

        // Removoe last comman from SelectColumns
        SelectColumns = SelectColumns.Remove(SelectColumns.Length - 1);


        xl.Close();

        string SQL = "";

        // Assemble the dynamic SQL that will be applied in the SQL task next to generate and populate a new database table
        if (ExcelExtension == ".xlsx")
        {
            SQL = "IF OBJECT_ID ('dbo." + Tablename + "') IS NOT NULL DROP TABLE dbo." + Tablename +
                         " " + CreateTable + " " +
                         InsertTable + " " + "SELECT " + SelectColumns + " FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0', " +
                //" INSERT INTO [dbo].[" + Tablename + "] SELECT * FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0', " +
                         "'Excel 12.0 Xml;HDR=YES;Database=" + xlFile + "', 'SELECT * FROM [" + worksheet + "$]');";

        }
        else if (ExcelExtension == ".xls")
        {
            SQL = "IF OBJECT_ID ('dbo." + Tablename + "') IS NOT NULL DROP TABLE dbo." + Tablename +
                          " " + CreateTable + " " +
                          " INSERT INTO [dbo].[" + Tablename + "] SELET * FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0', " +
                          "'Excel 8.0 Xml;HDR=YES;Database=" + xlFile + "', 'SELECT * FROM [" + worksheet + "$]');";
        }

        //MessageBox.Show(SQL);

        Dts.Variables["User::CreateTableSQL"].Value = SQL;

        Dts.TaskResult = (int)ScriptResults.Success;
    }


    #region ScriptResults declaration
    enum ScriptResults
    {
        Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
        Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
    };
    #endregion

}
 }

查看上面的脚本,您会注意到需要声明以下 ReadOnlyVariables:ExelFile、SourceFolder、TableName、tempFileName 和 Worksheet。然后需要声明以下 ReadWriteVariables:ColumnCount、CreateTable 和 InsertTableName。

5) 在嵌套的 ForEach 循环容器内和上述任务脚本的正下方,添加一个执行 SQL 任务,该任务将运行包含在 CreateTableSQL 变量中的 sql。请务必将 SQLSourceType 设置为“变量”。这将创建并填充表,如果它已经存在,甚至会覆盖它。

完成后,您应该有如下流程:

希望这会有所帮助,如果您有任何问题,请告诉我。我没有时间删除多余的东西,但这应该会让你走上正确的道路。此循环容器用于 Excel 文件,但您可以添加其他循环容器,并修改代码以处理 CSV 或其他文件类型。所有这些都可以包含在一个 SSIS 包中。

最后的 SQL 任务只是运行以下 TSQL,它会在数据库中查找包含单词之间空格的字段名称,并将该空格替换为下划线。这不是必需的,但可以避免必须对用括号 [] 括起来的列应用 SQL。

DECLARE My_Cursor Cursor
FOR
    SELECT 'sp_rename '''+table_name+'.['+column_name+']'','''+replace(column_name,' ','_')+''',''COLUMN''' 
    FROM information_schema.columns
    WHERE column_name like '% %'

OPEN My_Cursor
DECLARE @SQL NVARCHAR(1000)

FETCH NEXT FROM My_Cursor INTO @SQL
WHILE @@FETCH_STATUS <> -1
BEGIN
    EXECUTE sp_executesql @SQL

    FETCH NEXT FROM My_Cursor INTO @SQL
END

CLOSE My_Cursor
DEALLOCATE My_Cursor

【讨论】:

    猜你喜欢
    • 2018-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-25
    相关资源
    最近更新 更多