¿Cómo escribir la salida del procedimiento almacenado directamente en un archivo en un FTP sin utilizar archivos locales o temporales?

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

Pregunta

Quiero obtener los resultados de un procedimiento almacenado y colocarlos en un archivo CSV en una ubicación FTP.

Sin embargo, el problema es que no puedo crear un archivo local/temporal que luego pueda enviar por FTP.

El enfoque que estaba tomando era usar un paquete SSIS para crear un archivo temporal y luego tener una tarea FTP dentro del paquete para enviar el archivo por FTP, pero nuestros administradores de bases de datos no permiten que se creen archivos temporales en ningún servidor.

en respuesta a Yaakov Ellis

Creo que tendremos que convencer a los administradores de bases de datos para que me dejen usar al menos un recurso compartido en un servidor que ellos no operan, o preguntarles cómo lo harían.

en respuesta a Kev

Me gusta la idea de la integración de CLR, pero no creo que nuestros administradores de bases de datos ni siquiera sepan qué es eso. jajaja y probablemente tampoco lo permitirían.Pero probablemente podré hacer esto dentro de una tarea de secuencia de comandos en un paquete SSIS que pueda programarse.

¿Fue útil?

Solución

Este ejemplo paso a paso es para otras personas que puedan tropezar con esta pregunta.Este ejemplo utiliza Servidor Windows Server 2008 R2 y SSIS 2008 R2.Aunque el ejemplo utiliza SSIS 2008 R2, la lógica utilizada es aplicable a SSIS 2005 también.Gracias a @Kev Para el Solicitud web FTP código.

Cree un paquete SSIS (Pasos para crear un paquete SSIS).He nombrado el paquete en el formato AAAAMMDD_hhmm al principio seguido de ENTONCES significa Stack Overflow, seguido del ID de la pregunta, y finalmente una descripción.No estoy diciendo que debas nombrar tu paquete así.Esto es para que pueda consultarlo fácilmente más adelante.Tenga en cuenta que también tengo dos fuentes de datos, a saber trabajo de aventura y Practica DB.estaré usando trabajo de aventura fuente de datos, que apunta a Trabajo de aventura base de datos descargada de este enlace.Consultar captura de pantalla #1 al final de la respuesta.

En el Trabajo de aventura base de datos, cree un procedimiento almacenado llamado dbo.GetCurrency utilizando el siguiente script.

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

En la sección Administrador de conexión del paquete, haga clic derecho y seleccione Nueva conexión desde la fuente de datos.Sobre el Seleccionar fuente de datos cuadro de diálogo, seleccione trabajo de aventura y haga clic DE ACUERDO.Ahora debería ver la fuente de datos de Adventure Works en la sección Administradores de conexión.Consultar captura de pantalla #2, #3 y #4.

En el paquete, cree las siguientes variables.Consultar captura de pantalla #5.

  • Delimitador de columnas:Esta variable es de tipo String.Esto se utilizará para separar los datos de la columna cuando se escriban en el archivo.En este ejemplo, usaremos la coma (,) y el código está escrito para manejar solo caracteres visualizables.Para caracteres no visibles como tabulador ( ), es posible que deba cambiar el código utilizado en este ejemplo en consecuencia.

  • Nombre del archivo:Esta variable es de tipo String.Contendrá el nombre del archivo.En este ejemplo, nombré el archivo Currencies.csv porque voy a exportar una lista de nombres de monedas.

  • FTPContraseña:Esta variable es de tipo String.Este contendrá la contraseña del sitio web FTP.Lo ideal es que el paquete esté cifrado para ocultar información confidencial.

  • FTPRutaRemota:Esta variable es de tipo String.Esto contendrá la ruta de la carpeta FTP a la que se debe cargar el archivo.Por ejemplo, si el URI FTP completo es ftp://myFTPSite.com/ssis/samples/uploads, entonces RemotePath sería /ssis/samples/uploads.

  • Nombre del servidor FTP:Esta variable es de tipo String.Este contendrá el URI raíz del sitio FTP.Por ejemplo, si el URI FTP completo es ftp://myFTPSite.com/ssis/samples/uploads, entonces FTPServerName contendría ftp://myFTPSite.com.Puede combinar FTPRemotePath con esta variable y tener una única variable.Depende de tu preferencia.

  • Nombre de usuario FTP:Esta variable es de tipo String.Este contendrá el nombre de usuario que se utilizará para conectarse al sitio web FTP.

  • Lista de monedas:Esta variable es de tipo Objeto.Esto contendrá el conjunto de resultados del procedimiento almacenado y se recorrerá en bucle en la tarea de secuencia de comandos.

  • Mostrar Encabezado:Esta variable es de tipo booleano.Esto contendrá valores verdadero/falso.Verdadero indica que la primera fila del archivo contendrá nombres de columnas y Falso indica que la primera fila no contendrá nombres de columnas.

  • SQLGetData:Esta variable es de tipo String.Este contendrá la declaración de ejecución del procedimiento almacenado.Este ejemplo utiliza el valor EXEC dbo.GetCurrency

En el paquete Flujo de control pestaña, coloque un Ejecutar tarea SQL y nombrarlo como Obtener datos.Haga doble clic en Ejecutar tarea SQL para que aparezca el Ejecutar el editor de tareas SQL.Sobre el General sección de la Ejecutar el editor de tareas SQL, selecciona el Conjunto resultante a Full result set, el Conexión a Adventure Works, el Tipo de fuente SQL a Variable y el FuenteVariable a User::SQLGetData.En la sección Conjunto de resultados, haga clic en el botón Agregar.Establezca el nombre del resultado en 0, esto indica el índice y la Variable a User::ListOfCurrencies.La salida del procedimiento almacenado se guardará en esta variable de objeto.Hacer clic DE ACUERDO.Consultar captura de pantalla #6 y #7.

En el paquete Flujo de control pestaña, coloque una Tarea de secuencia de comandos debajo de la Tarea Ejecutar SQL y asígnele el nombre Guardar en FTP.Haga doble clic en la tarea de secuencia de comandos para que aparezca el Editor de tareas de script.En la sección Script, haga clic en el Edit Script… botón.Consultar captura de pantalla #8.Esto abrirá el editor de Visual Studio Tools para aplicaciones (VSTA).Reemplazar el código dentro de la clase. ScriptMain en el editor con el código que figura a continuación.Además, asegúrese de agregar las declaraciones de uso a los espacios de nombres. System.Data.OleDb, System.IO, System.Net, System.Text.Consultar captura de pantalla #9 que resalta los cambios de código.Cierre el editor VSTA y haga clic en Aceptar para cerrar el Editor de tareas de script.El código de secuencia de comandos toma la variable de objeto ListOfCurrencies y la almacena en una tabla de datos con la ayuda de OleDbDataAdapter porque estamos usando una conexión OleDb.Luego, el código recorre cada fila y si la variable ShowHeader se establece en verdadero, el código incluirá los nombres de las columnas en la primera fila escrita en el archivo.El resultado se almacena en una variable de creación de cadenas.Después de que la variable del generador de cadenas se completa con todos los datos, el código crea un objeto FTPWebRequest y se conecta al FTP Uri combinando las variables FTPServerName, FTPRemotePath y FileName utilizando las credenciales proporcionadas en las variables FTPUserName y FTPPassword.Luego, el contenido completo de la variable del generador de cadenas se escribe en el archivo.El método WriteRowData se crea para recorrer las columnas y proporcionar los nombres de las columnas o la información de los datos en función de los parámetros pasados.

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

Una vez que se hayan configurado las tareas, el flujo de control del paquete debería verse como se muestra en la captura de pantalla. #10.

Captura de pantalla #11 muestra el resultado de la declaración de ejecución del procedimiento almacenado EXEC dbo.GetCurrency.

Ejecute el paquete.Captura de pantalla #12 muestra la ejecución exitosa del paquete.

Utilizando el FuegoFTP complemento disponible en zorro de fuego navegador, inicié sesión en el sitio web FTP y verifiqué que el archivo se había cargado correctamente en el sitio web FTP.Consulte la captura de pantalla n.°13.

Examinar el contenido abriendo el archivo en Notepad++ muestra que coincide con la salida del procedimiento almacenado.Consulte la captura de pantalla n.°14.

Por lo tanto, el ejemplo demostró cómo escribir resultados de una base de datos en un sitio web FTP sin tener que utilizar archivos temporales/locales.

Espero que ayude a alguien.

Capturas de pantalla:

#1:Explorador de la solución

Solution_Explorer

#2:Nueva_conexión_desde_fuente_de_datos

New_Connection_From_Data_Source

#3:Seleccionar_fuente_datos

Select_Data_Source

#4:Administradores de conexión

Connection_Managers

#5:variables

Variables

#6:Ejecutar_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:Paquete_Ejecución_Exitosa

Package_Execution_Successful

#13:Archivo_En_FTP

File_In_FTP

#14:Contenido_archivo

File_Contents

Otros consejos

Si se le permitiera implementar ensamblajes de integración CLR, podría usar FTP sin tener que escribir un archivo temporal:

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 algún servidor en algún lugar que pueda usar y donde pueda crear un archivo temporal?Si es así, cree un servicio web que devuelva una matriz que contenga el contenido del archivo.Llame al servicio web desde la computadora donde puede crear un archivo temporal, use el contenido de la matriz para crear el archivo temporal y envíelo por ftp.

si no hay donde en absoluto donde puedes crear un archivo temporal, no veo cómo podrás enviar algo por FTP.

Intente utilizar un procedimiento almacenado CLR.Es posible que se le ocurra algo, pero sin crear primero un archivo temporal, aún puede resultar difícil.¿Podrías configurar un recurso compartido en otra máquina y escribir en ella y luego realizar ftp desde allí?

Script desde el servidor FTP y simplemente llame al proceso almacenado.

Sin embargo, la captura es que no puedo crear un archivo local/temporal que pueda FTP.

Esta restricción no tiene ningún sentido, intenta hablar amablemente con el DBA y explícaselo.Es totalmente razonable que cualquier proceso o trabajo de Windows cree archivos temporales en la ubicación adecuada, es decir.Carpeta %TEMP%.En realidad, el tiempo de ejecución de SSIS a menudo crea archivos temporales allí, por lo que si DBA le permite ejecutar SSIS, él es permitiéndole crear archivos temporales :).

Siempre que el DBA comprenda que estos archivos temporales no le crean problemas ni cargas de trabajo adicionales (explique que no no (tiene que establecer permisos especiales, o hacer una copia de seguridad de ellos, etc.), debería aceptar permitirle crearlos.

La única tarea de mantenimiento para DBA es limpiar periódicamente el directorio %TEMP% en caso de que su trabajo SSIS falle y deje el archivo.Pero debería hacerlo de todos modos, ya que muchos otros procesos pueden hacer lo mismo.Un simple trabajo del Agente SQL hará esto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top