sql-server - 如何在不使用本地或临时文件的情况下将存储过程输出直接写入 FTP 上的文件?

标签 sql-server sql-server-2005 ssis ftp

我想获取存储过程的结果并将它们放入 CSV 文件中的 FTP 位置。

但问题是我无法创建一个本地/临时文件,然后我可以通过 FTP 传输。

我采用的方法是使用 SSIS 包来创建一个临时文件,然后在包内有一个 FTP 任务来通过 FTP 传输文件,但是我们的 DBA 不允许在任何服务器上创建临时文件。

in reply to Yaakov Ellis

我认为我们需要说服 DBA 让我在他们不操作的服务器上至少使用一个共享,或者询问他们将如何操作。

in reply to Kev

我喜欢 CLR 集成的想法,但我认为我们的 DBA 甚至不知道那是什么,哈哈,他们也可能不允许这样做。但我可能能够在可调度的 SSIS 包中的脚本任务中执行此操作。

最佳答案

这个循序渐进的例子适用于可能偶然发现这个问题的其他人。此示例使用 Windows Server 2008 R2 服务器和 SSIS 2008 R2。尽管该示例使用 SSIS 2008 R2,但所使用的逻辑也适用于 SSIS 2005。感谢 @Kev对于 FTPWebRequest 代码。

创建 SSIS 包 ( Steps to create an SSIS package )。我在开头以 YYYYMMDD_hhmm 格式命名包,然后是 SO 代表堆栈溢出,然后是 SO 问题 ID,最后是描述。我并不是说你应该这样命名你的包。这是让我以后轻松引用。请注意,我还有两个数据源,即 Adventure Works 和 Practice DB。我将使用 Adventure Works 数据源,它指向从 this link 下载的 AdventureWorks 数据库。 .引用截图 #1 在答案的底部。

在 AdventureWorks 数据库中,使用下面给定的脚本创建一个名为 dbo.GetCurrency 的存储过程。

CREATE PROCEDURE [dbo].[GetCurrency]
AS
BEGIN
    SET NOCOUNT ON;
    SELECT 
    TOP 10      CurrencyCode
            ,   Name
            ,   ModifiedDate 
    FROM        Sales.Currency
    ORDER BY    CurrencyCode
END
GO

在包的连接管理器部分,右键单击并选择从数据源新建连接。在“选择数据源”对话框中,选择 Adventure Works,然后单击“确定”。您现在应该在 Connection Managers 部分下看到 Adventure Works 数据源。引用截图 #2 , #3 #4 .

在包上,创建以下变量。引用截图 #5 .
  • ColumnDelimiter:此变量的类型为字符串。这将用于在写入文件时分隔列数据。在此示例中,我们将使用逗号 (,) 并且编写代码以仅处理可显示的字符。对于诸如制表符 (\t) 之类的不可显示字符,您可能需要相应地更改本示例中使用的代码。
  • 文件名:此变量的类型为字符串。它将包含文件的名称。在此示例中,我将文件命名为 Currencies.csv,因为我要导出货币名称列表。
  • FTPPassword:该变量是字符串类型。这将包含 FTP 网站的密码。理想情况下,应加密包以隐藏敏感信息。
  • FTPRemotePath:该变量是字符串类型。这将包含文件应上传到的 FTP 文件夹路径。例如,如果完整的 FTP URI 是 ftp://myFTPSite.com/ssis/samples/uploads ,那么 RemotePath 将是/ssis/samples/uploads。
  • FTPServerName:此变量的类型为字符串。这将包含 FTP 站点根 URI。例如,如果完整的 FTP URI 是 ftp://myFTPSite.com/ssis/samples/uploads ,那么 FTPServerName 将包含 ftp://myFTPSite.com .您可以将 FTPRemotePath 与此变量结合使用并拥有一个变量。这取决于您的喜好。
  • FTPUserName:此变量的类型为字符串。这将包含将用于连接到 FTP 网站的用户名。
  • ListOfCurrencies:此变量的类型为 Object。这将包含来自存储过程的结果集,并将在脚本任务中循环。
  • ShowHeader:这个变量是 bool 类型的。这将包含真/假值。 True 表示文件中的第一行将包含列名,False 表示第一行不包含列名。
  • SQLGetData:该变量是字符串类型。这将包含存储过程执行语句。此示例使用值 EXEC dbo.GetCurrency

  • 在包的控制流选项卡上,放置一个执行 SQL 任务并将其命名为获取数据。双击执行 SQL 任务以打开执行 SQL 任务编辑器。在执行 SQL 任务编辑器的常规部分,将 ResultSet 设置为 Full result set ,连接到 Adventure Works ,将 SQLSourceType 改为 Variable和 SourceVariable 到 User::SQLGetData .在结果集部分,单击添加按钮。将结果名称设置为 0 ,这表示索引和变量为 User::ListOfCurrencies .存储过程的输出将保存到此对象变量中。单击确定。引用截图 #6 #7 .

    在包的控制流选项卡上,在执行 SQL 任务下方放置一个脚本任务,并将其命名为保存到 FTP。双击脚本任务以打开脚本任务编辑器。在脚本部分,单击 Edit Script…按钮。引用截图 #8 .这将打开 Visual Studio Tools for Applications (VSTA) 编辑器。替换类中的代码 ScriptMain在编辑器中使用下面给出的代码。另外,请确保将 using 语句添加到 namespace System.Data.OleDb , System.IO , System.Net , System.Text .引用截图 #9 突出显示代码更改。关闭 VSTA 编辑器并单击确定关闭脚本任务编辑器。脚本代码在 OleDbDataAdapter 的帮助下获取对象变量 ListOfCurrencies 并将其存储到 DataTable 中,因为我们使用的是 OleDb 连接。然后代码循环遍历每一行,如果变量 ShowHeader 设置为 true,则代码将在写入文件的第一行中包含列名称。结果存储在 stringbuilder 变量中。在用所有数据填充字符串构建器变量后,代码创建一个 FTPWebRequest 对象并通过使用变量 FTPUserName 和 FTPPassword 中提供的凭据组合变量 FTPServerName、FTPRemotePath 和 FileName 来连接到 FTP Uri。然后将完整的字符串构建器变量内容写入文件。创建方法 WriteRowData 以遍历列并根据传递的参数提供列名称或数据信息。
    using System;
    using System.Data;
    using Microsoft.SqlServer.Dts.Runtime;
    using System.Windows.Forms;
    using System.Data.OleDb;
    using System.IO;
    using System.Net;
    using System.Text;
    
    namespace ST_7033c2fc30234dae8086558a88a897dd.csproj
    {
        [System.AddIn.AddIn("ScriptMain", Version = "1.0", Publisher = "", Description = "")]
        public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
        {
    
            #region VSTA generated code
            enum ScriptResults
            {
                Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
                Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
            };
            #endregion
    
            public void Main()
            {
                Variables varCollection = null;
    
                Dts.VariableDispenser.LockForRead("User::ColumnDelimiter");
                Dts.VariableDispenser.LockForRead("User::FileName");
                Dts.VariableDispenser.LockForRead("User::FTPPassword");
                Dts.VariableDispenser.LockForRead("User::FTPRemotePath");
                Dts.VariableDispenser.LockForRead("User::FTPServerName");
                Dts.VariableDispenser.LockForRead("User::FTPUserName");
                Dts.VariableDispenser.LockForRead("User::ListOfCurrencies");
                Dts.VariableDispenser.LockForRead("User::ShowHeader");
                Dts.VariableDispenser.GetVariables(ref varCollection);
    
                OleDbDataAdapter dataAdapter = new OleDbDataAdapter();
                DataTable currencies = new DataTable();
                dataAdapter.Fill(currencies, varCollection["User::ListOfCurrencies"].Value);
    
                bool showHeader = Convert.ToBoolean(varCollection["User::ShowHeader"].Value);
                int rowCounter = 0;
                string columnDelimiter = varCollection["User::ColumnDelimiter"].Value.ToString();
                StringBuilder sb = new StringBuilder();
                foreach (DataRow row in currencies.Rows)
                {
                    rowCounter++;
                    if (rowCounter == 1 && showHeader)
                    {
                        WriteRowData(currencies, row, columnDelimiter, true, ref sb);
                    }
    
                    WriteRowData(currencies, row, columnDelimiter, false, ref sb);
                }
    
                string ftpUri = string.Concat(varCollection["User::FTPServerName"].Value,
                                              varCollection["User::FTPRemotePath"].Value,
                                              varCollection["User::FileName"].Value);
    
                FtpWebRequest ftp = (FtpWebRequest)FtpWebRequest.Create(ftpUri);
                ftp.Method = WebRequestMethods.Ftp.UploadFile;
                string ftpUserName = varCollection["User::FTPUserName"].Value.ToString();
                string ftpPassword = varCollection["User::FTPPassword"].Value.ToString();
                ftp.Credentials = new System.Net.NetworkCredential(ftpUserName, ftpPassword);
    
                using (StreamWriter sw = new StreamWriter(ftp.GetRequestStream()))
                {
                    sw.WriteLine(sb.ToString());
                    sw.Flush();
                }
    
                Dts.TaskResult = (int)ScriptResults.Success;
            }
    
            public void WriteRowData(DataTable currencies, DataRow row, string columnDelimiter, bool isHeader, ref StringBuilder sb)
            {
                int counter = 0;
                foreach (DataColumn column in currencies.Columns)
                {
                    counter++;
    
                    if (isHeader)
                    {
                        sb.Append(column.ColumnName);
                    }
                    else
                    {
                        sb.Append(row[column].ToString());
                    }
    
                    if (counter != currencies.Columns.Count)
                    {
                        sb.Append(columnDelimiter);
                    }
                }
                sb.Append(System.Environment.NewLine);
            }
        }
    }
    

    一旦配置了任务,包的控制流应该看起来像截图 中所示。 #10 .

    截图 #11 显示存储过程执行语句 EXEC dbo.GetCurrency 的输出。

    执行包。截图 #12 显示包的成功执行。

    使用 FireFox 浏览器中提供的 FireFTP 插件,我登录到 FTP 网站并验证文件已成功上传到 FTP 网站。引用截图# 13 .

    通过在 Notepad++ 中打开文件来检查内容表明它与存储过程输出匹配。引用截图# 14 .

    因此,该示例演示了如何将结果从数据库写入 FTP 网站,而无需使用临时/本地文件。

    希望能帮助某人。

    截图:

    #1 : 解决方案_资源管理器

    Solution_Explorer

    #2 : New_Connection_From_Data_Source

    New_Connection_From_Data_Source

    #3 : Select_Data_Source

    Select_Data_Source

    #4 : Connection_Managers

    Connection_Managers

    #5 : 变量

    Variables

    #6 : Execute_SQL_Task_Editor_General

    Execute_SQL_Task_Editor_General

    #7 : Execute_SQL_Task_Editor_Result_Set

    Execute_SQL_Task_Editor_Result_Set

    #8 : Script_Task_Editor

    Script_Task_Editor

    #9 : Script_Task_VSTA_Code

    Script_Task_VSTA_Code

    #10 : Control_Flow_Tab

    Control_Flow_Tab

    #11 : Query_Results

    Query_Results

    #12 :Package_Execution_Successful

    Package_Execution_Successful

    #13 : File_In_FTP

    File_In_FTP

    #14 : 文件内容

    File_Contents

    关于sql-server - 如何在不使用本地或临时文件的情况下将存储过程输出直接写入 FTP 上的文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20587/

    相关文章:

    SQL Server 在多个列上进行透视

    SQL Server - 案例

    sql-server-2005 - 存储过程中的冒泡错误

    sql-server-2005 - 报告服务饼图

    sql-server-2005 - SQL Server 2005 : Difference between SQL mail and DB mail

    node.js - NodeJs express4-tedious - SELECT 的存储过程以 JSON 形式返回响应

    sql - 透视表值函数

    sql-server - SQL Server 数据工具安装后不可用

    etl - SSIS 与 Pentaho

    SQL 将任意字符转换为零 0