Como gravar a saída do procedimento armazenado diretamente em um arquivo em um FTP sem usar arquivos locais ou temporários?

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

Pergunta

Quero obter os resultados de um procedimento armazenado e colocá-los em um arquivo CSV em um local FTP.

O problema, porém, é que não consigo criar um arquivo local/temporário que possa ser transferido por FTP.

A abordagem que eu estava adotando era usar um pacote SSIS para criar um arquivo temporário e, em seguida, ter uma tarefa FTP dentro do pacote para transferir o arquivo por FTP, mas nossos DBAs não permitem a criação de arquivos temporários em nenhum servidor.

em resposta a Yaakov Ellis

Acho que precisaremos convencer os DBAs a me deixarem usar pelo menos um compartilhamento em um servidor que eles não operam, ou perguntar como fariam isso.

em resposta a Kev

Gosto da ideia da integração do CLR, mas acho que nossos DBAs nem sabem o que é isso lol e provavelmente também não permitiriam isso.Mas provavelmente poderei fazer isso em uma tarefa de script em um pacote SSIS que pode ser agendado.

Foi útil?

Solução

Este exemplo passo a passo é para outras pessoas que possam se deparar com essa questão.Este exemplo usa Servidor Windows Server 2008 R2 e SSIS 2008 R2.Mesmo assim, o exemplo usa SSIS 2008 R2, a lógica usada é aplicável a SSIS 2005 também.Graças a @Kev para o FTPWebRequest código.

Crie um pacote SSIS (Etapas para criar um pacote SSIS).Nomeei o pacote no formato AAAAMMDD_hhmm no início seguido por ENTÃO significa Stack Overflow, seguido pelo ID da pergunta SO, e, finalmente, uma descrição.Não estou dizendo que você deva nomear seu pacote assim.Isso é para eu consultar isso facilmente mais tarde.Observe que também tenho duas fontes de dados, a saber Obras de aventura e Pratique banco de dados.eu estarei usando Obras de aventura fonte de dados, que aponta para AdventureWorks banco de dados baixado de esse link.Consulte a captura de tela #1 na parte inferior da resposta.

No AdventureWorks banco de dados, crie um procedimento armazenado chamado dbo.GetCurrency usando o script fornecido abaixo.

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

Na seção Connection Manager do pacote, clique com o botão direito e selecione Nova conexão da fonte de dados.No Selecione a fonte de dados caixa de diálogo, selecione Obras de aventura e clique OK.Agora você deve ver a fonte de dados Adventure Works na seção Connection Managers.Consulte a captura de tela #2, #3 e #4.

No pacote, crie as seguintes variáveis.Consulte a captura de tela #5.

  • ColunaDelimitador:Esta variável é do tipo String.Isso será usado para separar os dados da coluna quando eles forem gravados no arquivo.Neste exemplo, usaremos vírgula (,) e o código é escrito para lidar apenas com caracteres exibíveis.Para caracteres não exibíveis como tabulação ( ), pode ser necessário alterar o código usado neste exemplo de acordo.

  • Nome do arquivo:Esta variável é do tipo String.Ele conterá o nome do arquivo.Neste exemplo, nomeei o arquivo como Currencies.csv porque irei exportar uma lista de nomes de moedas.

  • Senha FTP:Esta variável é do tipo String.Isso conterá a senha do site FTP.Idealmente, o pacote deve ser criptografado para ocultar informações confidenciais.

  • FTPRemotePath:Esta variável é do tipo String.Isso conterá o caminho da pasta FTP para a qual o arquivo deve ser carregado.Por exemplo, se o URI FTP completo for ftp://myFTPSite.com/ssis/samples/uploads, então o RemotePath seria /ssis/samples/uploads.

  • NomedoServidorFTP:Esta variável é do tipo String.Ele conterá o URI raiz do site FTP.Por exemplo, se o URI FTP completo for ftp://myFTPSite.com/ssis/samples/uploads, então o FTPServerName conteria ftp://myFTPSite.com.Você pode combinar FTPRemotePath com esta variável e ter uma única variável.Depende da sua preferência.

  • FTPUserName:Esta variável é do tipo String.Ele conterá o nome de usuário que será usado para se conectar ao site FTP.

  • Lista de moedas:Esta variável é do tipo Object.Isso conterá o conjunto de resultados do procedimento armazenado e será repetido na tarefa de script.

  • Mostrar Cabeçalho:Esta variável é do tipo Boolean.Isso conterá valores verdadeiro/falso.True indica que a primeira linha do arquivo conterá nomes de colunas e False indica que a primeira linha não conterá nomes de colunas.

  • SQLGetData:Esta variável é do tipo String.Isso conterá a instrução de execução do procedimento armazenado.Este exemplo usa o valor EXEC dbo.GetCurrency

No pacote Controle de fluxo guia, coloque um Executar tarefa SQL e nomeá-lo como Adquirir dados.Clique duas vezes na tarefa Executar SQL para trazer o Executar Editor de Tarefas SQL.No Em geral seção do Executar Editor de Tarefas SQL, colocou o Conjunto de resultados para Full result set, o Conexão para Adventure Works, o SQLSourceType para Variable e a Variável de origem para User::SQLGetData.Na seção Conjunto de resultados, clique no botão Adicionar.Defina o nome do resultado como 0, isso indica o índice e a variável a ser User::ListOfCurrencies.A saída do procedimento armazenado será salva nesta variável de objeto.Clique OK.Consulte a captura de tela #6 e #7.

No pacote Controle de fluxo guia, coloque uma tarefa de script abaixo da tarefa Executar SQL e nomeie-a como Salvar em FTP.Clique duas vezes na tarefa de script para trazer o Editor de tarefas de script.Na seção Script, clique no botão Edit Script… botão.Consulte a captura de tela #8.Isso abrirá o editor Visual Studio Tools for Applications (VSTA).Substitua o código dentro da classe ScriptMain no editor com o código fornecido abaixo.Além disso, certifique-se de adicionar as instruções using aos namespaces System.Data.OleDb, System.IO, System.Net, System.Text.Consulte a captura de tela #9 que destaca as alterações de código.Feche o editor VSTA e clique em Ok para fechar o Editor de Tarefas de Script.O código do script pega a variável de objeto ListOfCurrencies e a armazena em um DataTable com a ajuda de OleDbDataAdapter porque estamos usando a conexão OleDb.O código então percorre cada linha e se a variável ShowHeader estiver definida como verdadeira, o código incluirá os nomes das colunas na primeira linha gravada no arquivo.O resultado é armazenado em uma variável stringbuilder.Após a variável construtor de string ser preenchida com todos os dados, o código cria um objeto FTPWebRequest e se conecta ao FTP Uri combinando as variáveis ​​FTPServerName, FTPRemotePath e FileName usando as credenciais fornecidas nas variáveis ​​FTPUserName e FTPPassword.Em seguida, o conteúdo completo da variável do construtor de string é gravado no arquivo.O método WriteRowData é criado para percorrer colunas e fornecer os nomes das colunas ou informações de dados com base nos parâmetros passados.

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);
        }
    }
}

Depois que as tarefas forem configuradas, o fluxo de controle do pacote deverá ter a aparência mostrada na captura de tela #10.

Captura de tela #11 mostra a saída da instrução de execução do procedimento armazenado EXEC dbo.GetCurrency.

Execute o pacote.Captura de tela #12 mostra a execução bem-sucedida do pacote.

Usando o FireFTP complemento disponível em Raposa de fogo navegador, entrei no site FTP e verifiquei se o arquivo foi carregado com sucesso no site FTP.Consulte a captura de tela #13.

Examinar o conteúdo abrindo o arquivo no Notepad++ mostra que ele corresponde à saída do procedimento armazenado.Consulte a captura de tela #14.

Assim, o exemplo demonstrou como gravar resultados do banco de dados em um site FTP sem ter que usar arquivos temporários/locais.

Espero que ajude alguém.

Capturas de tela:

#1:Solução_Explorador

Solution_Explorer

#2:Nova_Conexão_From_Data_Source

New_Connection_From_Data_Source

#3:Selecione_Data_Source

Select_Data_Source

#4:Gerenciadores de conexão

Connection_Managers

#5:Variáveis

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:Consulta_Resultados

Query_Results

#12:Pacote_Execução_Sucesso

Package_Execution_Successful

#13:Arquivo_In_FTP

File_In_FTP

#14:Arquivo_Conteúdo

File_Contents

Outras dicas

Se você tivesse permissão para implementar assemblies de integração CLR, você poderia realmente usar o FTP sem precisar escrever um arquivo temporário:

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();
    }
}

Existe um servidor em algum lugar que você possa usar para criar um arquivo temporário?Nesse caso, crie um serviço web que retorne um array contendo o conteúdo do arquivo.Chame o serviço da web do computador onde você pode criar um arquivo temporário, use o conteúdo da matriz para construir o arquivo temporário e envie-o por FTP.

Se não há onde de forma alguma onde você pode criar um arquivo temporário, não vejo como você conseguirá enviar algo por FTP.

Tente usar um procedimento armazenado CLR.Você pode conseguir inventar algo, mas sem primeiro criar um arquivo temporário, ainda pode ser difícil.Você poderia configurar um compartilhamento em outra máquina e escrever para ela e depois fazer FTP a partir daí?

Script do servidor FTP e apenas chame o processo armazenado.

A captura é que não posso criar um arquivo local/temporário que possa depois FTP.

Essa restrição não faz sentido, procure conversar bem com o DBA e explicar para ele.É totalmente razoável que qualquer processo ou trabalho do Windows crie arquivos temporários em locais apropriados, ou seja,Pasta %TEMP%.Na verdade, o próprio tempo de execução do SSIS geralmente cria arquivos temporários lá - portanto, se o DBA permitir que você execute o SSIS, ele é permitindo que você crie arquivos temporários :).

Contanto que o DBA entenda que esses arquivos temporários não criam problemas ou carga de trabalho adicional para ele (explique que ele não cria não tiver que definir permissões especiais ou fazer backup delas, etc.), ele deverá concordar em permitir que você as crie.

A única tarefa de manutenção do DBA é limpar periodicamente o diretório %TEMP% caso seu trabalho SSIS falhe e deixe o arquivo para trás.Mas ele deveria fazer isso de qualquer maneira, pois muitos outros processos podem fazer o mesmo.Um trabalho simples do SQL Agent fará isso.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top