我收到一个月度XLSX文件,需要使用SSIS包导入SQL Server。遗憾的是,发件人不遵循UNC命名文件名或工作表,我们最近迁移到SQL Server 2012导致程序包失败 - 即使使用Excel连接管理器也是如此。我们也尝试向他们发送模板,但他们拒绝遵循它,我们没有任何动力迫使他们这样做。
我一直在尝试对软件包进行更新,该软件包将使用脚本任务将每个Excel工作表中的每一个导入到每个System.Object
中,然后我可以查询或循环导入数据进入目标SQL Server表。
到目前为止,使用Microsoft here中的示例,我成功地将Excel文件路径/名称和两个工作表名称导入到Object变量中。但是,这不会创建包含任一工作表中的实际数据集的Object。
基于此处和网络上的其他示例,我已经启动了一个C#脚本,我相信它会将工作表数据输出到一个Object变量中,但我对C#不是很熟练并且很难获得它调试没有完整的示例来复制。到目前为止,这是我的代码:
using System;
using System.Data;
using System.Data.OleDb;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;
[Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
{
public DataSet Main()
{
string fileName;
string connectionString;
fileName = Dts.Variables["ExcelFile"].Value.ToString();
Console.WriteLine(fileName);
connectionString = "Provider=Microsoft.ACE.OLEDB.12.0;" +
"Data Source=" + fileName + ";Extended Properties=Excel 12.0 Xml";
Console.WriteLine(connectionString);
DataSet data = new DataSet();
using (OleDbConnection con = new OleDbConnection(connectionString))
{
con.Open();
OleDbDataAdapter adapter = new OleDbDataAdapter("SELECT * FROM [Sheet1$]", connectionString);
adapter.Fill(data);
}
return data;
}
}
代码构建成功,但是当我运行包时,我收到一个非描述错误
错误:脚本任务为0x1:调用目标抛出了异常 任务失败:脚本任务
我没有从Console.WriteLine
命令获得任何输出,所以我相信脚本任务会立即失败。我确实有延迟验证=真,虽然改变它并没有什么不同。你在我的脚本中看到任何明显的/新手错误吗?我已经使用SQL和SSIS多年了,但我的C#/ VB / Java /等。知识和经验是有限的。
另外,如果我在SSIS中忽略了一种更好的方法来完成此操作(除了Excel连接,这不起作用),请告诉我。
更新 - 2016年5月31日:我认为今天我有一点时间参与这个项目,并取得了一些进展。我已更新我的脚本任务以包含以下内容:
DataSet data = new DataSet();
using (OleDbConnection con = new OleDbConnection(connectionString))
{
con.Open();
OleDbDataAdapter adapter = new OleDbDataAdapter(query, con);
//OleDbDataAdapter adapter = new OleDbDataAdapter("SELECT * FROM [Indemnity Scores$]", con);
adapter.Fill(data);
Dts.Variables["ExcelDataTable_IndemnityScores"].Value = data;
}
脚本任务现在成功完成,因此我之后添加了一个Foreach循环容器,将其设置为Foreach From Variable Enumerator,并选择ExcelDataTable_IndemnityScores作为集合。
但是,现在我很难从这个Object变量中提取数据。它具有(或者至少应该拥有)两列,我在变量映射中设置了这两列,并使用Execute SQL命令将值插入表中。不幸的是,每个列只插入一个空白值。
接下来,我用一个简单的脚本任务替换了Execute SQL,以返回每个变量的值。不幸的是,而不是它返回的值#34; Microsoft.SqlServer.Dts.Runtime.Variable"。我认为这对我来说是一个新手错误,但我还没有找到任何在线解释错误的内容吗?
2016年6月14日更新: 我终于完成了这个包,它昨天成功投入生产。我最后使用了这里提出的建议,以及其他地方的例子。我的一般工作流程需要三重嵌套的Foreach循环来获取从源工作簿导入的两个工作表 - 我只期望每月一个,但没有任何内容与此任务100%一致。
我的最外层循环只是枚举我的导入目录,以查找FTP进程下载的文件。它包含两个脚本任务。第一个只是确认FTP进程下载的第一个电子表格的文件名。我使用上面的Microsoft链接作为我的代码,只对我的变量名进行了少量修改。
第二个任务从第一个电子表格中获取所有工作表名称,并且还使用上面的Microsoft链接构建。但是,我用"#"排除任何工作表名称。防止将XML数据库分配给我的变量。
第二个循环(第一个内循环)枚举在第一个循环中解析的每个工作表名称。它包含三个脚本任务,第一个脚本任务将第一个工作表中的数据导入到我的对象变量中。
public void Main() { 尝试 {
string fileName;
string connectionString;
string worksheetName;
string query;
fileName = Dts.Variables["ExcelFile"].Value.ToString();
//MessageBox.Show("InsertWorksheetDataIntoObject - Filename: " + fileName);
connectionString = String.Format("Provider=Microsoft.ACE.OLEDB.12.0;" +
"Data Source={0};Extended Properties=Excel 12.0 Xml;", fileName);
//MessageBox.Show("Connection: " + connectionString);
worksheetName = Dts.Variables["ExcelTable"].Value.ToString();
worksheetName = worksheetName.Replace("'", "");
//MessageBox.Show("InsertWorksheetDataIntoObject - Worksheet: " + worksheetName);
query = string.Format("SELECT * FROM [" + worksheetName + "]");
//MessageBox.Show("Query: " + query);
DataSet data = new DataSet();
using (OleDbConnection con = new OleDbConnection(connectionString))
{
con.Open();
OleDbDataAdapter adapter = new OleDbDataAdapter(query, con);
adapter.Fill(data);
Dts.Variables["ExcelDataTable"].Value = data;
}
Dts.TaskResult = (int)ScriptResults.Success;
}
catch (Exception ex)
{
Dts.Events.FireError(-1, "ErrorMessage", ex.ToString(), "", 0);
Dts.TaskResult = (int)ScriptResults.Failure;
}
//return data;
}
此循环中的第二个脚本任务只是从Excel中删除任何空白行。我可以将它与上面的脚本合并,但我保持它的可移植性,以便将来在其他地方重用。
此循环中的第三个脚本任务使用工作表名称来设置在下一个循环中用于确定目标表的变量。
第三个循环(第二个内循环)枚举包含工作表中数据的对象变量中的行。它包含单个执行SQL任务,该任务根据上面工作表名称设置的变量值将两个源列中的数据导入到正确的目标表中。由于工作表名称并不总是一致的,因此该循环直接连接到我的对象变量,这样就无需按名称调用源列。相反,我只是将每一个分配给Foreach循环中的目标变量,并将该数据逐行传递到我的表中。
再次感谢大家的帮助和建议!
答案 0 :(得分:0)
通常当我立即收到该消息时,这意味着我在您的案例ExcelFile中拼写了错误的变量名称。我执行sql查询时也会收到此错误,并返回null。你最好的办法就是注释你的代码部分,直到执行了令牌,然后你至少知道导致问题的代码。
我不明白为什么excel连接不起作用。如果文件位于UNC路径上并且导致问题,则可以使用脚本任务将文件移动到可以工作的位置。
答案 1 :(得分:0)
Joe C可能是对的,您可能会通过错误的名称引用变量。您是否已将变量/参数传递给脚本任务?
但是,我不明白为什么你没有使用其中一个数据流脚本任务。您可以定义输入和输出列,然后将它们填入脚本任务的代码中:
public override void CreateNewOutputRows()
{
/*
Add rows by calling the AddRow method on the member variable named "<Output Name>Buffer".
For example, call MyOutputBuffer.AddRow() if your output was named "MyOutput".
*/
}
这些输出行可以转移到数据流中的下一个任务 - 就像SSIS喜欢它一样。此外,使用变量要容易得多。您可以通过this.Variables.ExcelFile
(例如。)来访问它们,就像通常的属性一样。
另请注意:不要忘记设置(控制流程)脚本任务的结果。您的任务可能会成功完成,但在以下顺序流程中不会有任何限制。
Dts.TaskResult = (int)ScriptResults.Success;
答案 2 :(得分:0)
哇,当你的工作变得像其他人一样复杂的时候,你不讨厌它!所以有很多方法可以解决你的问题我的个人意见都在一个脚本任务中,你可能更容易遵循逻辑并完成,但@Johannes也提出了另一个好的方法。脚本任务有两个位置,它们是完全不同的编码和思考过程方法。一个是控制流中可用的“脚本任务”,它似乎是您编码并将对象添加到变量的位置。
第二个是数据流任务中提供的“脚本组件”。前者需要将其视为一个独立的脚本,它完全独立于其他所有内容,后者嵌入在数据流任务中并且是作为来源,目的地或转变的行为。这意味着它可以用于填充要使用的记录集变量(对象)。
因此,在选项1中,您当前所有需要完成代码的方式是添加一些c#来更新/填充您想要的SQL表。以下是我从我的一个软件包中窃取的一些代码:
SqlConnection sqlConnection = new SqlConnection(sqlConnectionString);
sqlConnection.Open();
SqlBulkCopy bulkCopy = new SqlBulkCopy(sqlConnection);
bulkCopy.DestinationTableName = _stagingTableName;
foreach (DataColumn col in _jobRecDT.Columns)
{
//System.Windows.Forms.MessageBox.Show(col.ColumnName);
bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
}
bulkCopy.WriteToServer(_jobRecDT);
sqlConnection.Close();
对于选项2,我曾经对此有所了解或许@Johannes有一个链接或者有人可以在这里发表评论。但是在这种方法中,您应该能够重用大量代码,然后将其移动到“脚本组件”。然后在对象上定义记录集模式,并像使用数据流任务中的任何其他源一样使用它。
有两个问题需要考虑,需要进一步的逻辑。 1)如果使用选项1,则需要在使用批量复制或动态管理列映射之前将表/数据集重命名为预期值。 2)在数据流选项中,您需要在填充最终记录集变量之前转换数据集,使其始终具有相同的列和数据类型。
这两个选项都有性能和数据有效性考虑因素。第一种可能是更好的性能,但SSIS不处理数据有效性/错误检查。选项2,您将获得大数据集的SSIS错误检查和性能的好处。如果您的数据集非常大,则需要调整两个选项。还有其他一些注意事项,例如线程,但我不相信这些注意事项适用于您。
我希望这会有所帮助。
答案 3 :(得分:0)
我编辑了原始问题,概述了最终为我工作的解决方案。如果有人有疑问或想了解更多细节/示例,请告诉我。