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

StackOverflow https://stackoverflow.com/questions/20587

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

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

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

回复 雅科夫·埃利斯

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

回复 凯夫

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

有帮助吗?

解决方案

此分步示例适用于可能偶然发现此问题的其他人。本示例使用 Windows Server 2008 R2 服务器SSIS 2008 R2. 。尽管如此,该示例使用 SSIS 2008 R2, ,所使用的逻辑适用于 2005年国际安全峰会 以及。谢谢 @Kev 为了 FTP网络请求 代码。

创建一个 SSIS 包(创建 SSIS 包的步骤)。我在开头以 YYYYMMDD_hhmm 的格式命名该包,然后是 所以 代表堆栈溢出,后面跟着 所以问题 ID, ,最后是描述。我并不是说你应该这样命名你的包。这是为了我以后可以轻松地引用它。请注意,我还有两个数据源,即 冒险作品实践数据库. 。我将使用 冒险作品 数据源,它指向 冒险工场 数据库下载自 这个链接. 。参考截图 #1 在答案的底部。

在里面 冒险工场 数据库,创建一个名为的存储过程 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 数据源。参考截图 #2, #3#4.

在包上,创建以下变量。参考截图 #5.

  • 列分隔符: :该变量是字符串类型。当将列数据写入文件时,这将用于分隔列数据。在此示例中,我们将使用逗号 (,),并且编写的代码仅处理可显示的字符。对于制表符 ( ) 等不可显示的字符,您可能需要相应地更改此示例中使用的代码。

  • 文件名: :该变量是字符串类型。它将包含文件的名称。在此示例中,我将文件命名为 Currencies.csv,因为我要导出货币名称列表。

  • FTP密码: :该变量是字符串类型。这将包含 FTP 网站的密码。理想情况下,应该对包进行加密以隐藏敏感信息。

  • FTP远程路径: :该变量是字符串类型。这将包含文件应上传到的 FTP 文件夹路径。例如,如果完整的 FTP URI 是 ftp://myFTPSite.com/ssis/samples/uploads, ,那么 RemotePath 将为 /ssis/samples/uploads。

  • FTP服务器名称: :该变量是字符串类型。这将包含 FTP 站点根 URI。例如,如果完整的 FTP URI 是 ftp://myFTPSite.com/ssis/samples/uploads, ,那么 FTPServerName 将包含 ftp://myFTPSite.com. 。您可以将 FTPRemotePath 与此变量组合起来并拥有一个变量。这取决于您的喜好。

  • FTP用户名:该变量是字符串类型。这将包含用于连接 FTP 网站的用户名。

  • 货币列表: :该变量是对象类型。这将包含存储过程的结果集,并将在脚本任务中循环遍历。

  • 显示标题: :该变量是布尔类型。这将包含值 true/false。True 表示文件中的第一行将包含列名称,False 表示第一行不包含列名称。

  • SQL获取数据: :该变量是字符串类型。这将包含存储过程执行语句。此示例使用值 EXEC dbo.GetCurrency

包装上的 控制流 选项卡,放置一个 执行SQL任务 并将其命名为 获取数据. 。双击“执行 SQL 任务”以显示 执行 SQL 任务编辑器. 。上 一般的 的部分 执行 SQL 任务编辑器, ,设置 结果集Full result set, , 这 联系Adventure Works, , 这 SQL源类型Variable源变量User::SQLGetData. 。在“结果集”部分中,单击“添加”按钮。将结果名称设置为 0, ,这表示索引和变量 User::ListOfCurrencies. 。存储过程的输出将保存到该对象变量中。点击 好的. 。参考截图 #6#7.

包装上的 控制流 选项卡,将脚本任务放置在执行 SQL 任务下方,并将其命名为 保存到FTP. 。双击脚本任务以显示 脚本任务编辑器. 。在脚本部分,单击 Edit Script… 按钮。参考截图 #8. 。这将打开 Visual Studio Tools for Applications (VSTA) 编辑器。替换类内的代码 ScriptMain 在编辑器中使用下面给出的代码。另外,请确保将 using 语句添加到命名空间 System.Data.OleDb, System.IO, System.Net, System.Text. 。参考截图 #9 突出显示代码更改。关闭 VSTA 编辑器,然后单击“确定”关闭脚本任务编辑器。脚本代码采用对象变量 ListOfCurrency 并在 OleDbDataAdapter 的帮助下将其存储到 DataTable 中,因为我们使用的是 OleDb 连接。然后,代码循环遍历每一行,如果变量 ShowHeader 设置为 true,则代码将在写入文件的第一行中包含列名称。结果存储在字符串生成器变量中。在字符串生成器变量填充所有数据后,代码创建一个 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 显示包执行成功。

使用 火力FTP 附加组件可用于 火狐 浏览器登录FTP网站,验证文件已成功上传至FTP网站。参考截图#13.

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

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

希望对某人有帮助。

截图:

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

Solution_Explorer

#2: :来自数据源的新连接

New_Connection_From_Data_Source

#3: :选择数据源

Select_Data_Source

#4: :连接管理器

Connection_Managers

#5: :变量

Variables

#6: :执行_SQL_任务_编辑器_常规

Execute_SQL_Task_Editor_General

#7: :执行_SQL_任务_编辑器_结果_集

Execute_SQL_Task_Editor_Result_Set

#8: :脚本_任务_编辑器

Script_Task_Editor

#9: :脚本_任务_VSTA_代码

Script_Task_VSTA_Code

#10: :控制流选项卡

Control_Flow_Tab

#11: :查询结果

Query_Results

#12: :打包_执行_成功

Package_Execution_Successful

#13: :文件输入_FTP

File_In_FTP

#14: :文件内容

File_Contents

其他提示

如果您被允许实现 CLR 集成程序集,您实际上可以使用 FTP,而无需编写临时文件:

public static void DoQueryAndUploadFile(string uri, string username, string password, string filename)
{
    FtpWebRequest ftp = (FtpWebRequest)FtpWebRequest.Create(uri + "/" + filename);
    ftp.Method = WebRequestMethods.Ftp.UploadFile;
    ftp.Credentials = new System.Net.NetworkCredential(username, password);

    using(StreamWriter sw = new StreamWriter(ftp.GetRequestStream()))
    {
        // Do the query here then write to the ftp stream by iterating DataReader or other resultset, following code is just to demo concept:
        for (int i = 0; i < 100; i++)
        {
            sw.WriteLine("{0},row-{1},data-{2}", i, i, i);
        }
        sw.Flush();
    }
}

是否有任何地方可以使用服务器来创建临时文件?如果是这样,请创建一个 Web 服务,返回一个包含文件内容的数组。从可以创建临时文件的计算机调用 Web 服务,使用数组的内容构建临时文件并将其通过 ftp 传输。

如果没有地方 根本不 您可以在其中创建临时文件,但我不知道如何通过 FTP 发送任何内容。

尝试使用 CLR 存储过程。您也许能够想出一些办法,但如果不先创建临时文件,这可能仍然很困难。您能否在另一台计算机上设置共享并写入内容,然后从那里进行 ftp?

来自 FTP 服务器的脚本,只需调用存储过程即可。

但是,我无法创建一个本地/临时文件,然后我可以ftp ftp。

这个限制没有任何意义,试着和DBA好好谈谈并向他/她解释一下。对于任何 Windows 进程或作业来说,在适当的位置创建临时文件是完全合理的,即%TEMP% 文件夹。实际上,SSIS 运行时本身经常会在那里创建临时文件 - 因此,如果 DBA 允许您运行 SSIS,他就会 允许您创建临时文件:)。

只要 DBA 明白这些临时文件不会给他带来问题或额外的工作量(解释他确实这样做) 不是 必须设置特殊权限,或备份它们等),他应该同意让你创建它们。

DBA 的唯一维护任务是定期清理 %TEMP% 目录,以防 SSIS 作业失败并留下文件。但无论如何他都应该这样做,因为许多其他进程也可能会这样做。一个简单的 SQL 代理作业即可完成此操作。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top