UGISS Community

Il sito della community dello User Group Italiano di SQL Server
Welcome to UGISS Community Sign in | Join | Help
in Search

Alessandro Alpi's blog

Parliamo di integration services e non solo..

luglio 2007 - Posts

  • [SSIS] Come lanciare dinamicamente un elenco di SSIS esterni

    Come succede per l'impostazione dinamica dei connection manager oledb o snac (ho scritto qualcosa in quest'altro post) è possibile anche lanciare più pacchetti esterni, configurando dinamicamente il connection manager di tipo file. Il task che ci consente di lanciare package residenti su SQL Server o su Filesystem è "Execute Package Task" (ho già accennato anche lui qui..)



    Quest'ultimo task ci consente di richiamare altri package esterni per effettuare operazioni al di fuori del SSIS da cui l'abbiamo lanciato, con la possibilità di eseguirlo anche in modalità Out Of Process, ovvero al di fuori del processo che gestisce il SSIS chiamante.
    L'interfaccia per configurarlo si divide in tre parti:

    Sezione General
    Consente di specificare il nome univoco del task e la sua descrizione.

    Sezione Package


    Consente di specificare la locazione in cui reperire il package, la connessione (in caso di filesystem) o una connessione a sql server abbinata al nome del package (nel caso di SQL Server), una eventuale password e il tipo di esecuzione, se nello stesso processo del chiamante o in un processo a parte.

    Sezione Expression
    Questa parte è identica alla sezione expression di ogni task di SSIS. Ovviamente con la possibilità di gestire proprietà differenti.

    Per arrivare alla configurazione dinamica dei package esterni da eseguire, illustriamo un semplice esempio:

    Supponiamo di avere l'esigenza di lanciare in serie tre pacchetti, package1.dtsx, package2.dtsx e package3.dtsx.
    Ora, ammettiamo di avere l'elenco dei SSIS da lanciare all'interno di una tabella di configurazione. Ogni SSIS deve essere eseguito all'interno del processo del chiamante.
    Procediamo passo passo..

    Prima cosa da fare, aggiungere un connection manager di tipo FILE (Existing) chiamato PackageN che punti ad un qualunque file (non ci interessa, tanto poi cambieremo dinamicamente il path). Esso ci servirà per dire cosa lanciare all'Execute Package Task.



    La prima parte del SSIS principale si occupa di ricavare i dati ordinati da eseguire. Utilizzaimo uno script task per simulare una semplice tabella di configurazione, senza usufruire di database o xml. Lo script è il seguente:

    Imports System
    Imports System.Data
    Imports System.Math
    Imports Microsoft.SqlServer.Dts.RuntimePublic Class ScriptMain
        Public Sub Main()
            ' datatable che conterrà l'elenco dei SSIS da chiamare
            Dim DT As New DataTable("tempTable")
            Dim DR As DataRow

            ' colonne di definizione del datatable
            DT.Columns.Add(New DataColumn("SSISName", GetType(String)))
            DT.Columns.Add(New DataColumn("SSISPath", GetType(String)))

            ' aggiungo i record
            ' ----------------------------------------------------------
            DR = DT.NewRow()
            DR("SSISName") = "Package1.dtsx"
            DR("SSISPath") = "D:\SSIS\Package1.dtsx"
            DT.Rows.Add(DR)
            DR = DT.NewRow()
            DR("SSISName") = "Package2.dtsx"
            DR("SSISPath") = "D:\SSIS\Package2.dtsx"
            DT.Rows.Add(DR)
            DR = DT.NewRow()
            DR("SSISName") = "Package3.dtsx"
            DR("SSISPath") = "D:\SSIS\Package3.dtsx"
            DT.Rows.Add(DR)
            ' ----------------------------------------------------------

            Dts.Variables("SSISList").Value = DT
            Dts.TaskResult = Dts.Results.Success
        End Sub
    End Class

    Abbiamo creato un semplice datatable con tre righe ognuna delle quali ha l'importante informazione del path in cui troveremo il SSIS da lanciare. Del nome, in realtà non ci interessa granchè, ai fini dell'esempio. Alla fine del listato assegnamo ad una variabile dichiarata come di Lettura Scrittura il valore del nostro datatable. La variabile SSISList è un oggetto così dichiarato:



    Ora abbiamo il nostro oggetto popolato dallo script e pronto per essere ciclato. Aggiungiamo un for each container così definito:




    In questo modo l'iteratore ADO cicla, riga per riga sulla nostra variabile oggetto SSISList. Ad ogni ciclo inoltre, valorizziamo la variabile del path, nella sezione Variable Mappings:



    Come possiamo vedere vi è anche una variabile di appoggio per il nome, ma interessiamoci solo di quella per il path, SSISPath.  A questo punto possiamo scegliere due strade. Utilizzare uno script che assegni il valore della variabile SSISPath al connection manager PackageN oppure impostare il connection manager con le Expressions.. In questo esempio seguiremo la prima via, perchè ci permette di rendere più visibile la modifica del percorso del file. Le expression a volte sono piuttosto nascoste .. Lascio a voi l'implementazione con il secondo metodo .
    Ecco quindi lo script di assegnazione del connection manager:



    Notiamo due variabili di sola lettura, ConnectionManagerName e SSISPath. La prima contiene il nome del connection manager da cambiare (Nel nostro esempio PackageN) e la seconda è la variabile mappata nel ciclo dei SSIS da lanciare. Il codice è il seguente:

    Imports System
    Imports System.Data
    Imports System.Math
    Imports Microsoft.SqlServer.Dts.Runtime

    Public Class ScriptMain

        ' proprietà che gestisce la variabile in sola lettura SSISPath
        Private ReadOnly Property SSISPath() As String
            Get
                Return Dts.Variables("SSISPath").Value.ToString()
           
    End Get
        End Property

        ' proprietà che gestisce la variabile in sola lettura ConnectionManagerName
        Private ReadOnly Property ConnectionManagerName() As String
            Get
                Return Dts.Variables("ConnectionManagerName").Value.ToString()
           
    End Get
        End Property

     
        ' proprietà che scrive il valore della connection string    Private WriteOnly Property CMConnectionString() As String
            Set(ByVal value As String)
                Dts.Connections(
    Me.ConnectionManagerName).ConnectionString = value
           
    End Set
        End Property

        Public Sub Main()

            Me.CMConnectionString = Me.SSISPath

            Dts.TaskResult = Dts.Results.Success    End Sub
    End
    Class

    Per comodità ho definito delle proprietà in modo da rendere più gestibile il contenuto dello script. Due proprietà ReadOnly per la lettura dei dati del connection manager ed una in sola scrittura per scrivere all'interno della connectionstring del connection manager il valore del path (come si nota nel metodo Main).
    Il passo successivo (e finale) è quello di aggiungere l'execute package task, legandolo alla connessione file fittizia, che a runtime cambierà in base ai dati di ogni ciclo e configurandolo per eseguire i package via filesystem:



     Il SSIS finale sarà il eguente:


    N.B. Ovviamente i tre ssis da eseguire e che sono elencati nel primo script, devono esistere nella posizione di destinazione definita (D:\SSIS). Nel caso in cui anche solo uno dei file non esista o non sia in corretta posizione, verrà sollevata un'eccezione di file not found, tranquillamente gestibile, senza bloccare il flusso di lavoro.

    CONCLUSIONI
    Come si può facilmente notare da questo post, la possibilità di rendere dinamico il lancio di pacchetti esterni non è cosa complessa. Facciamo attenzione ovviamente a tutti i settings possibili di ogni task. Ad esempio l'Execute Package Task, consente anche di eseguire, come dicevamo, un SSIS out of process. Tutte le analisi per decidere se eseguire nello stesso processo o al di fuori devono essere fatte nella misura delle risorse che occuperemo, dei carichi della macchina, se si tenderà a parallelizzare le chiamate o a mantenerle sequenziali. E' importante capire come muoverci in ogni caso che ci si presenta di fronte. L'utilizzo di Execute Package Task inoltre, può essere comodo per la modularità, per il grouping, e per scindere un grosso pacchetto in altri più piccoli.  Quest'ultimo metodo di sviluppo permette una migliore gestione dell'intero progetto e comunque riduce di molto le attese durante le fasi di validazione a design time ..

    Stay tuned!

  • [SSIS] Serio problema con Script Task

    Questa mattina sono arrivato a lavoro e ho cominciato a controllare lo stato dei job del SQL Server del cliente..
    La macchina ha un Windows Server 2003 R2 SP1 ed un SQL Server 2005 SP1 (ver. 9.0.2047, ITA)

    Ci sono una decina di JOB che schedulano l'esecuzione di altrettanti SSIS. Questi ultimi sono stati scritti utilizzando lo script task per generare dei log.
    Nulla di che.. Se non fosse per il simpatico errorino che mi sono trovato davanti Angry..

    Non ci sono i log generati dal SSIS, il JOB, come al solito non è parlante.. Ok.. niente panico.. debug.. Script Task rosso come non mai!!
    MA PERCHE'???



    Ho cercato un po' su internet.. e Microsoft mi indica questo errore:

    "This problem occurs because of a recent change in the common language runtime (CLR) workstation runtime for builds of the Microsoft .NET Framework 2.0. The CLR workstation runtime is named Mscorwks.dll. The change introduces a compatibility problem for the Microsoft Visual Basic runtime engine that VSA uses. The Visual Basic runtime engine is named Vsavb7rt.dll. The problem prevents assemblies from being created correctly during the macro compilation operation. Because of this problem, existing packages that contain compiled scripts do not run."

    Grrrr... Angry

    Ma che rabbia.. La fix c'è.. ma visto che la macchina non è opportunamente aggiornata, ho scelto di installarvi il Service Pack 2 in italiano (non so come mai la scelta sia ricaduta sulla nostra lingua, considerato che sul server è tutto in inglese)..
    Tutto è tornato come prima.. Tongue Tied mi è andata bene..

    Stay tuned! Stick out tongue

  • [SSIS] Il lookup task, vantaggi, svantaggi ed ottimizzazione.

    Il lookup transformation task è un componente in grado di effettuare query di ricerca mirata tramite equi-join dei dati di input ed un particolare oggetto referenziato.



    Tra i dati di input ed i dati di output deve esistere la corrispondenza di almeno una riga in base al criterio di join definito nell'editor apposito.
    Nel caso in cui il match non sia realizzato, viene restituita un eccezione.
    E' un task molto potente e molto spesso utile per la BI. Pensate ad un ambiente DataWareHouse e all'esigenza di ricavare la chiave surrogata di una particolare entità conoscendone solo alcune informazioni.

    I suoi principali vantaggi sono:
    - Effettua lookup per riga quindi rende più controllabile e gestibile il flusso dati
    - Permette la restrizione dell'utilizzo della memoria
    - Permette di implementare logiche di inserimento incrementale delle righe, controllando preventivamente l'esistenza delle stesse
    - Permette il caching dei dati di cui poi vi sarà il match

    Ma attenzione, perchè porta con se anche svantaggi da considerare in analisi:
    - Lavorando per riga, diminuisce le prestazioni, quindi ove possibile, sostituirlo con query o task più intelligenti
    - Se non trova un match restituisce un'eccezione e, potenzialmente, blocca il flusso dei dati
    - Durante la fase di cache impiega molto tempo per preparare i dati, ma questo è attribuibile anche a come il modeling del database è stato studiato
    - Ha cali di prestazioni se non ottimizzato, quindi un suo utilizzo standard è sconsigliato per moli di dati molto capienti
    - Ritorna solo la prima riga di cui si ha match, non un gruppo (e questo può essere un limite voluto a livello di progettazione, ricava solo un record per una chiave logica)

    Ma facciamo un esempio (molto semplificato) per capire il funzionamento del Lookup transformation e per cercare di ottimizzarlo; consideriamo un'ipotetica tabella dei fatti così formata:

    CREATE TABLE Fatti

     

    (

        IDFatto int IDENTITY(1,1) NOT NULL,

     

        IDCliente int NOT NULL,

     

        DataRiferimento smalldatetime NOT NULL,

     

        Stipendio decimal(8,2) NOT NULL,

     

          CONSTRAINT PK_Fatti PRIMARY KEY CLUSTERED

     

          (

     

                IDFatto

     

          )

     

    )

    ed una tabella dimensione clienti:

    CREATE TABLE DimClienti

     

    (

     

          IDCliente int IDENTITY(1,1) NOT NULL,

     

          CodiceFiscale Char(16) NOT NULL,

     

          Nome varchar(30) NOT NULL,

     

          Cognome varchar(30) NOT NULL,

     

          CONSTRAINT PK_DimClienti PRIMARY KEY CLUSTERED

     

          (

     

                IDCliente

     

          )

     

    )

     

    Ipotizziamo inoltre di ricevere in input un file di testo contenente alcuni dati utili alla tabella fatti, ma con il codice fiscale indicato per il cliente. In poche parole, tramite quel codice, dovremmo ricercare sulla tabella dei clienti l'id del cliente ad esso associato per poi inserire il record nella tabella Fatti.

    Il ssis risultante sarà simile a questo:



    Qui non è gestita la parte di gestione dell'errore, ma per l'esempio ipotizziamo che nel file vi siano dati già presenti sulla tabella su cui faremo lookup. Quindi, avremo un flusso con i codici fiscali (e non solo) e per ogni codice fiscale faremo lookup verso la tabella DimClienti per ottenere la chiave surrogata da inserire poi nella tabella dei fatti. Analizziamo ora il task di lookup:

    Nella prima schermata (Reference Table) selezioniamo la connessione e l'oggetto su cui fare l'operazione di ricerca mirata



    Nella seconda (Columns) andiamo a mappare i campi per il match



    Per ottimizzare il componente lookup, un primo passo è quello di rimuovere le informazioni in eccesso. Procedere come visto fin'ora porta ad uno spreco di risorse. Infatti, durante l'operazione di caching del lookup transformation task, il caricamento delle colonne in più non fa altro che sprecare risorse. Addirittura, nell'area progress viene stampato un warning. Ovviamente questo non è un esempio parlante, ma pensando ad una DimClienti dotata di più campi e con tantissimi record, eliminare informazioni inutili migliora non di poco le prestazioni.
    Ecco come procedere, partendo dalla clip Reference Table:



    Fare quindi attenzione alle informazioni che realmente ci servono, in questo caso, solo il CodiceFiscale e l'IDCliente.
    Di conseguenza avremo nella sezione Columns:



    Ridurre la quantità delle informazioni ritornate è un ottima mossa per ridurre anche la quantità di memoria utilizzata.
    Per aumentare ancora le prestazioni delle operazioni di lookup, è consigliabile l'utilizzo di un indice sul campo su cui verterà l'equi-join:

    CREATE UNIQUE NONCLUSTERED INDEX UK_DimClienti_CodiceFiscale ON DimClienti

    (

          CodiceFiscale

    )

    In questo modo, ogni lookup risulterà effettivamente più veloce ed il seguente setting sulla memoria usata sarà veramente efficiente.
    Quindi, una volta sull'area Advanced, ottimizziamo ancora il comportamento del componente, impostando correttamente la parte di Memory Restriction.



    Tra le impostazioni di memory restriction, la più importante è la gestione della cache. In base alla situazione reale è infatti possibile abilitare la cache interna di SSIS. Tutte le query effettuate per riga vengono aggiunte fino a riempire la quantità di cache massima consentita. Quindi SSIS interrogherà il DB solo se non trova già righe nella sua cache appositamente creata. Riducendo la quantità di memoria con la "scrematura" delle colonne definita sopra, creando opportuni indici e configurando adhoc il caching, le prestazioni del task migliorano notevolmente.

    Stay tuned!