Как записать вывод хранимой процедуры непосредственно в файл на FTP без использования локальных или временных файлов? - PullRequest
11 голосов
/ 21 августа 2008

Я хочу получить результаты хранимой процедуры и поместить их в файл CSV на FTP-сервер.

Суть в том, что я не могу создать локальный / временный файл, который затем можно передать по FTP.

Подход, который я использовал, состоял в том, чтобы использовать пакет служб SSIS для создания временного файла, а затем создать задачу FTP внутри пакета для передачи файла через FTP, но наши администраторы баз данных не позволяют создавать временные файлы на каких-либо серверах. 1005 *

в ответ на Яакова Эллиса

Я думаю, нам нужно будет убедить администраторов баз данных позволить мне использовать хотя бы общий ресурс на сервере, который они не работают, или спросить их, как они это сделают.

в ответ Кеву

Мне нравится идея интеграции CLR, но я не думаю, что наши администраторы баз данных даже знают, что это такое lol , и они, вероятно, тоже не допустят этого. Но я, вероятно, смогу сделать это в рамках задачи «Сценарий» в пакете служб SSIS, который можно запланировать.

Ответы [ 6 ]

14 голосов
/ 23 июля 2011

Этот пошаговый пример предназначен для тех, кто может наткнуться на этот вопрос. В этом примере используются сервер Windows Server 2008 R2 и SSIS 2008 R2 . Несмотря на то, что в примере используется SSIS 2008 R2 , используемая логика применима и к SSIS 2005 . Спасибо @Kev за FTPWebRequest код.

Создание пакета служб SSIS ( Шаги по созданию пакета служб SSIS ). В начале я назвал пакет в формате YYYYMMDD_hhmm, за которым следует SO , обозначающий переполнение стека, за которым следует SO, идентификатор вопроса и, наконец, описание. Я не говорю, что вы должны назвать свою посылку вот так. Это для меня, чтобы легко отослать это позже. Обратите внимание, что у меня также есть два источника данных, а именно Adventure Works и Practice DB . Я буду использовать источник данных Adventure Works , который указывает на базу данных 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 и нажмите OK . Теперь вы должны увидеть источник данных Adventure Works в разделе «Менеджеры соединений». Смотрите скриншот # 2 , # 3 и # 4 .

В пакете создайте следующие переменные. Смотрите скриншот # 5 .

  • ColumnDelimiter : эта переменная имеет тип String. Это будет использоваться для разделения данных столбца при его записи в файл. В этом примере мы будем использовать запятую (,), а код написан для обработки только отображаемых символов. Для неотображаемых символов, таких как tab (\ t), вам может потребоваться соответственно изменить код, используемый в этом примере.

  • FileName : эта переменная имеет тип String. Он будет содержать имя файла. В этом примере я назвал файл как Currencies.csv, потому что я собираюсь экспортировать список названий валют.

  • FTPPassword : эта переменная имеет тип String. Это будет содержать пароль к FTP-сайту. В идеале пакет должен быть зашифрован, чтобы скрыть конфиденциальную информацию.

  • FTPRemotePath : эта переменная имеет тип String. Он будет содержать путь к папке FTP, в которую должен быть загружен файл. Например, если полный URI FTP равен ftp: //myFTPSite.com/ssis/samples/uploads, то RemotePath будет /ssis/samples/uploads.

  • FTPServerName : эта переменная имеет тип String. Он будет содержать корневой URI FTP-сайта. Например, если полный URI FTP равен ftp: //myFTPSite.com/ssis/samples/uploads, то FTPServerName будет содержать ftp: //myFTPSite.com. Вы можете комбинировать FTPRemotePath с этой переменной и иметь одну переменную. Это на ваше усмотрение.

  • FTPUserName : эта переменная имеет тип String. Он будет содержать имя пользователя, которое будет использоваться для подключения к FTP-сайту.

  • ListOfCurrencies : Эта переменная имеет тип Object. Он будет содержать набор результатов из хранимой процедуры, и он будет зациклен в задаче сценария.

  • ShowHeader : эта переменная имеет тип Boolean. Это будет содержать значения true / false. Значение True указывает, что первая строка в файле будет содержать имена столбцов, а значение False указывает, что первая строка не будет содержать имен столбцов.

  • SQLGetData : эта переменная имеет тип String. Это будет содержать оператор выполнения хранимой процедуры. В этом примере используется значение EXEC dbo.GetCurrency

На вкладке Поток управления пакета поместите Выполнить задачу SQL и назовите ее Получить данные . Дважды щелкните на задаче «Выполнение SQL», чтобы вызвать редактор «Выполнить задачу SQL» . В разделе General редактора задач Execute SQL установите ResultSet на Full result set, Connection на Adventure Works, SQLSourceType до Variable и SourceVariable до User::SQLGetData. В разделе «Набор результатов» нажмите кнопку «Добавить». Установите для имени результата значение 0, это указывает на индекс и переменную на User::ListOfCurrencies. Вывод хранимой процедуры будет сохранен в этой переменной объекта. Нажмите OK . Смотрите скриншот # 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 и нажмите Ok, чтобы закрыть редактор задач Script. Код сценария берет объектную переменную ListOfCurrencies и сохраняет ее в DataTable с помощью OleDbDataAdapter, потому что мы используем соединение OleDb. Затем код проходит по каждой строке, и если для переменной ShowHeader задано значение true, код будет включать имена столбцов в первой строке, записанной в файл. Результат сохраняется в переменной stringbuilder. После того как переменная построителя строк заполнена всеми данными, код создает объект FTPWebRequest и подключается к Uri FTP путем объединения переменных FTPServerName, FTPRemotePath и FileName с использованием учетных данных, указанных в переменных FTPUserName и FTPPassword. Затем полное содержимое переменной строителя строки записывается в файл. Метод 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 показывает успешное выполнение пакета.

Используя надстройку FireFTP , доступную в браузере FireFox , я вошел на сайт FTP и убедился, что файл успешно загружен на сайт FTP. Смотрите скриншот # 13 .

Изучение содержимого путем открытия файла в Notepad ++ показывает, что оно совпадает с выводом хранимой процедуры. Смотрите скриншот # 14 .

Таким образом, в примере показано, как записывать результаты из базы данных на веб-сайт FTP без использования временных / локальных файлов.

Надеюсь, это кому-нибудь поможет.

Скриншоты:

# 1 : Solution_Explorer

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

File_Contents

9 голосов
/ 21 августа 2008

Если бы вам было разрешено реализовать интеграционные сборки 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();
    }
}
1 голос
/ 21 августа 2008

Есть ли где-нибудь сервер, который вы можете использовать, где вы можете создать временный файл? Если это так, создайте веб-сервис, который возвращает массив, содержащий содержимое файла. Вызовите веб-службу с компьютера, на котором вы можете создать временный файл, используйте содержимое массива для создания временного файла и переместите его через ftp.

Если вообще нет места , где вы можете создать временный файл, я не вижу, как вы сможете что-либо отправить по FTP.

0 голосов
/ 21 сентября 2008

Суть в том, что я не могу создать локальный / временный файл, который я могу затем передать по FTP.

Это ограничение не имеет никакого смысла, попробуйте красиво поговорить с администратором базы данных и объяснить ему / ей. Для любого процесса или задания Windows вполне разумно создавать временные файлы в соответствующем месте, то есть в папке% TEMP%. На самом деле среда выполнения служб SSIS часто создает там временные файлы - поэтому, если администратор баз данных разрешает запускать службы SSIS, он равен , позволяя создавать временные файлы :).

До тех пор, пока администратор БД понимает, что эти временные файлы не создают для него проблемы или дополнительной рабочей нагрузки (объясните, что он не должен устанавливать специальные разрешения или резервирует их и т. Д.), Он должен согласиться чтобы позволить вам создать их.

Единственной задачей обслуживания для администратора баз данных является периодическая очистка каталога% TEMP% в случае сбоя задания SSIS и оставления файла позади. Но он должен сделать это в любом случае, так как многие другие процессы могут сделать то же самое. Простое задание агента SQL сделает это.

0 голосов
/ 01 сентября 2008

Сценарий с FTP-сервера и просто вызов хранимого процесса.

0 голосов
/ 21 августа 2008

Попробуйте использовать хранимую процедуру CLR. Возможно, вы сможете что-то придумать, но без предварительного создания временного файла это все еще может быть затруднено. Не могли бы вы настроить общий ресурс на другом компьютере и написать на него, а затем оттуда установить ftp?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...