Jun 132013
 

Giorni fa, mentre stavo sistemando un po’ di codice dell’utility PacMan per adattarla agli scopi del nostro team, mi sono imbattuto in un bug del DTExec.

In particolare, quando in un Data Flow Source Component vengono definite delle colonne che non saranno poi consumate nel Data Flow Task, durante l’esecuzione del pacchetto è possibile visualizzare nei log dei messaggi del tipo:

The output column “ColumnName” (1358) on output “OLE DB Source Output” (1289) and component “OLE_SRC Name” (1279) is not subsequently used in the Data Flow task. Removing this unused output column can increase Data Flow task performance.

Un esempio è il seguente:

Ai fini di ottimizzare le performance di caricamento della fase di ETL, questi warning potrebbero dunque risultare utili. L’unico inconveniente è che per ottenerli ho dovuto eseguire il pacchetto. E se volessi raccogliere questi warning per fare un po’ di pulizia nei miei pacchetti, senza dover necessariamente eseguirli tutti (e dunque attendere tempi non trascurabili), come potrei fare? Mi sono ricordato del parametro VALIDATE del DTExec, che dovrebbe fare proprio al caso mio! Ho dunque eseguito il comando DTExec come segue:

DTEXEC.EXE /FILE "<my_folder>\<package_name>.dtsx" /VALIDATE  /REPORTING EWIC

specificando la reporting optionW‘ per ottenere anche i warning. Il risultato non è stato però quello atteso:

Microsoft (R) SQL Server Execute Package Utility
Version 10.50.4266.0 for 32-bit
Copyright (C) Microsoft Corporation 2010. All rights reserved.

Started: 17:29:46
Info: 2013-05-13 17:29:47.61
Code: 0x4004300A
Source: Load Fact Portafoglio Danni Polizze Titoli Arretrati SSIS.Pipeline
Description: Validation phase is beginning.
End Info
DTExec: The package validation returned DTSER_SUCCESS (0).
Started: 17:29:46
Finished: 17:29:47
Elapsed: 0.842 seconds

Mancano proprio i warning che avrei voluto catturare!

Ho indagato più a fondo, andando a smanettare persino con l’Object Model di Integration Services (per approfondimenti vedi qui), ed in effetti sembrerebbe che il parametro VALIDATE del DTExec non scateni i warning generati dalla fase di validazione delle SSIS.Pipeline.

Ho dunque aperto un item su Connect a questo link:

https://connect.microsoft.com/SQLServer/feedback/details/788331/ssis-dtexec-validate-doesnt-log-ssis-pipeline-validation-phase-performance-warnings

Mi raccomando, votatelo tutti! :)

Feb 032013
 

Oltre che ad occuparmi a tempo pieno di SQL Server e di Business Intelligence, mi diverto spesso a sviluppare in .NET. Per questo ho deciso di introdurre in questo blog qualche “sprazzo” di codice in C# che potrebbe tornare utile a qualcuno di voi.

In questi giorni mi sono imbattuto nel dover mandare in stampa delle etichette contenenti dei QR Code su una stampante Zebra. La cosa si risolve abbastanza agevolmente una volta che si ha il codice ZPL dell’etichetta da stampare. A quel punto è sufficiente modificare il testo da trasformare in QR Code (in chiaro nel codice) da programma ed inviare quindi il codice direttamente alla stampante. Il modo più rapido per ottenere il codice ZPL corrispondente ad una etichetta è quello di disegnare il layout dell’etichetta tramite lo Zebra Designer, che gestisce però le etichette, intese come oggetto grafico, nel formato proprietario LBL. Per poter dunque ottenere il codice ZPL corrispondente ad un file LBL è sufficiente reindirizzare la stampa dell’etichetta dal Designer su file:

Quello che si ottiene, come qualcuno già saprà, è un file PRN. Ma è sufficiente rinominare l’estensione in ZPL ed il gioco è fatto. Un esempio di etichetta realizzata sul designer e del relativo codice ZPL è il seguente:

###CT~~CD,~CC^~CT~ 
^XA~TA000~JSN^LT0^MNW^MTT^PON^PMN^LH0,0^JMA^PR5,5~SD15^JUS^LRN^CI0^XZ 
^XA 
^MMT 
^PW609 
^LL0406 
^LS0 
^FT189,319^BQN,2,9 
^FDMA,Testo del QR Code^FS 
^FT181,363^A0N,28,28^FH^FDTesto1^FS 
^FT205,327^A0N,28,28^FH^FDTesto2^FS 
^PQ1,0,1,Y^XZ

dove al posto di ### sono presenti alcune informazioni di testata interpretate come comando speciale dalla stampante. Come si può notare dalle parti evidenziate nel codice, il contenuto testuale degli oggetti grafici nell’etichetta è tutto in chiaro.

A questo punto è sorto il problema di come inviare alla stampante un file ZPL. Il modo più immediato è quello di ricorrere al caro DOS e di utilizzare il comando COPY:

COPY /B “\path\etichetta.zpl” LPT1:

e, nel caso di stampante collegata via USB, una volta condivisa in rete, tramite il comando:

COPY /B “\path\etichetta.zpl” \computer_name\printer_share_name

su Windiws XP. Invece su Vista o Windows 7:

NET USE LPT1 \computer_name\printer_share_name

COPY /b “\path\etichetta.zpl” LPT1:

A fronte delle diverse modalità di invio del file tramite il comando COPY, dipendenti dal tipo di collegamento della stampante al computer e dal tipo di sistema operativo, mi sono chiesto se ci fosse stato un modo diretto in .NET per inviaredei file in stampa in modalità “raw”. Cercando in rete, ho trovato questo articolo che faceva esattamente al caso mio:

http://support.microsoft.com/kb/322091/en-us

Oltre al codice presente nell’articolo, ho aggiunto un metodo che restituisce l’ultimo eventuale messaggio di errore (in rosso il codice aggiunto):

class RawPrinterHelper 
{ 
    ...

    private static int lastError = -1;

    ...

    public static string GetLastError() 
 { 
 return lastError != -1 ? new System.ComponentModel.Win32Exception(lastError).Message : String.Empty; 
 }

    ...

    public static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, Int32 dwCount) 
    {

    ...

        if (bSuccess == false) 
        { 
            dwError = Marshal.GetLastWin32Error(); 
            lastError = dwError; 
        } 
        return bSuccess; 
    } 
}

Fin qui tutto bene. Una volta integrato il codice nel mio progetto, testando la stampa mi sono accorto che il file di testo inviatoin stampa tramite il metodo RawPrinterHelper.SendFileToPrinter(printerName, labelPath) restava lockato fin quando l’applicazione terminava. Analizzando bene il codice mi sono accorto che il FileStream utilizzato nel suddetto metodo veniva aperto, ma non veniva mai chiuso! Ho aggiunto semplicemente la chiamata al metodo Close del FileStream instanziato:

public static bool SendFileToPrinter(string szPrinterName, string szFileName) 
{ 
    ...

    Marshal.FreeCoTaskMem(pUnmanagedBytes); 
    fs.Close();
    return bSuccess; 
}

e finalmente la stampa dell’etichetta è andata a buon fine su qualsiasi sistema operativo, a prescindere dal tipo di collegamento della stampante col PC.

Enjoy!

Sep 292012
 

Tutti noi, che abbiamo a che fare quotidianamente con progetti di Business Intelligence, conosciamo la comodissima funzione messa a disposizioine da BIDS Helper per effettuare il deploy del solo codice MDX di un cubo:

Giorni fa ho avuto la necessità di modificare il codice MDX di un cubo sulla macchina di Sviluppo di un cliente, e, una volta verificata la validità del nuovo codice, ho dovuto riportare tali modifiche sui relativi progetti negli ambienti di Test e Qualità.

Tutto facile (addirittura un click) per deployare il nuovo codice su Sviluppo, dove è presente il progetto di Analysis Services in BIDS, grazie alla funzione suddetta di BIDS Helper. Se fosse stato possibile accedere alle istanze di Analysis sugli altri ambienti, avrei potuto cambiare il server e il database target nelle impostazioni di deploy e procedere allo stesso modo:

Purtroppo non potevo accedere all’istanza di Analysis sulla macchina di Qualità, dunque il modo più ovvio per deployare le modifiche in questo ambiente era quello di fornire il file .asdatabase al DBA affinché lo eseguisse col Deployment Wizard. Mi sono chiesto però cosa facesse dietro le quinte il BIDS Helper per poter deployare “on-the-fly” il solo script MDX e dunque mi sono messo in ascolto col Profiler sull’istanza di Analysis di Sviluppo e il risultato è stato il seguente:

che, al netto delle CalculationProperties, può essere sintetizzato col seguente script:

<Alter AllowCreate="true"
ObjectExpansion="ExpandFull"
xmlns="http://schemas.microsoft.com/analysisservices/2003/engine"
xmlns:as="http://schemas.microsoft.com/analysisservices/2003/engine">
	<Object>
		<DatabaseID>YourDatabaseID</DatabaseID>
		<CubeID>YourCubeID</CubeID>
		<MdxScriptID>MdxScript</MdxScriptID>
	</Object>
	<ObjectDefinition>
		<MdxScript>
			<ID>MdxScript</ID>
			<Name>MdxScript</Name>
			<Commands
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ddl2="http://schemas.microsoft.com/analysisservices/2003/engine/2"
xmlns:ddl2_2="http://schemas.microsoft.com/analysisservices/2003/engine/2/2"
xmlns:ddl100_100="http://schemas.microsoft.com/analysisservices/2008/engine/100/100"
xmlns:ddl200="http://schemas.microsoft.com/analysisservices/2010/engine/200"
xmlns:ddl200_200="http://schemas.microsoft.com/analysisservices/2010/engine/200/200"
xmlns:dwd="http://schemas.microsoft.com/DataWarehouse/Designer/1.0">
				<Command>
					<Text>

						<!-- MDX script here -->

					</Text>
				</Command>
			</Commands>
		</MdxScript>
	</ObjectDefinition>
</Alter>

A questo punto è sufficiente inserire il codice MDX nel punto evidenziato nello script precedente e sostituire opportunamente il DatabaseID e il CubeID. Inoltre, occorre aggiungere i tag “<![CDATA[" e "]]>“ in testa e in coda allo script MDX rispettivamente, per evitare che il parser XMLA interpreti in modo non corretto il codice MDX (ad esempio, nel caso dei caratteri ‘&‘, ‘<‘ o ‘>‘):

<Text>
<![CDATA[

<!-- MDX script here -->

]]>
</Text>

E’ possibile dunque consegnare lo script così “confezionato” al DBA per farlo eseguire come Query XMLA sull’istanza di Analysis non accessibile direttamente che contiene il cubo da aggiornare.

Jun 122012
 

Capita spesso che un cliente ci fornisca un elenco di elementi per i quali estrarre determinate informazioni. Immaginiamo, ad esempio, che il nostro cliente desideri avere le informazioni di testata degli ordini di vendita solo per determinati ordini. Molto probabilmente egli ci fornirà un elenco di elementi in colonna in Excel, oppure, meno probabilmente, un semplice file di testo con gli identificativi degli ordini in colonna:

   

Fin quando i valori forniti sono al più una decina, non ci costa nulla copiare e incollare i valori nel nostro SSMS e sistemarli in una clausula WHERE, utilizzando l’operatore IN, facendo attenzione ad includere gli apici se trattasi di elementi alfanumerici (il database di esempio è AdventureWorks2008R2):

SELECT *
FROM Sales.SalesOrderHeader AS SOH
WHERE SOH.SalesOrderNumber IN (
'SO43667'
,'SO43709'
,'SO43726'
,'SO43746'
,'SO43782'
,'SO43796'
)

Evidentemente, il dover aggiungere virgole ed apici diventa un’operazione alquanto improba quando si ha a che fare con un centinaio di elementi (cosa che ovviamente è capitata a tutti noi!). Ecco dunque che sarebbe comodo fare un semplice copia e incolla, lasciare gli elementi così come vengono incollati, ed far sì che essi siano interpretati correttamente dalla query.

La cosa è fattibile tramite una User Defined Function che restituisce come risultato una tabella. Basta fornire alla funzione come parametro di input la stringa contenente gli elementi incollati dalla lista fornita dal cliente. Vi fornisco direttamente il codice della funzione, dove i commenti vanno a chiarire quanto scritto:

CREATE FUNCTION [dbo].[SplitCRLFList] (@List VARCHAR(MAX))
RETURNS @ParsedList TABLE (
--< Set the item length as your needs
Item VARCHAR(255)
)
AS
BEGIN
DECLARE
--< Set the item length as your needs
@Item VARCHAR(255)
,
@Pos BIGINT
--< Trim TABs due to indentations
SET @List = REPLACE(@List, CHAR(9), '')
--< Trim leading and trailing spaces, then add a CR\LF at the end of the list
SET @List = LTRIM(RTRIM(@List)) + CHAR(13) + CHAR(10)
--< Set the position at the first CR/LF in the list
SET @Pos = CHARINDEX(CHAR(13) + CHAR(10), @List, 1)
--< If exist other chars other than CR/LFs in the list then...
IF REPLACE(@List, CHAR(13) + CHAR(10), '') <> ''
BEGIN
--< Loop while CR/LFs are over (not found = CHARINDEX returns 0)
WHILE @Pos > 0
BEGIN
--< Get the heading list chars from the first char to the first CR/LF and trim spaces
SET @Item = LTRIM(RTRIM(LEFT(@List, @Pos - 1)))
--< If the so calulated item is not empty...
IF @Item <> ''
BEGIN
--< ...insert it in the @ParsedList temporary table
INSERT INTO @ParsedList (Item)
VALUES (@Item)
--(CAST(@Item AS int)) --< Use the appropriate conversion if needed
END
--< Remove the first item from the list...
SET @List = RIGHT(@List, LEN(@List) - @Pos - 1)
--< ...and set the position to the next CR/LF
SET @Pos = CHARINDEX(CHAR(13) + CHAR(10), @List, 1)
--< Repeat this block while the upon loop condition is verified
END
END
RETURN
END

A questo punto, dopo aver creato la funzione nel database, la nostra query si trasforma banalmente in:
SELECT *
FROM Sales.SalesOrderHeader AS SOH
WHERE SOH.SalesOrderNumber IN (
SELECT Item
FROM SplitCRLFList('SO43667 SO43709 SO43726 SO43746 SO43782 SO43796') AS SCL)

 

May 312012
 

Durante lo sviluppo di una soluzione di BI, sappiamo tutti che l’80% del tempo viene speso nella fase ETL (Extract, Transform, Load). Se si utilizzano i BI Stack Tool messi a disposizione da Microsoft SQL Server, questa fase viene realizzata tramite lo sviluppo di n pacchetti di Integration Services (SSIS). In generale, il numero di pacchetti realizzati nella fase ETL per una soluzione di BI non banale è abbastanza significativo.

Un pacchetto SSIS, dunque, estrae dei dati da una sorgente, li “martella” :) e poi li trasferisce in una destinazione specifica. Molto spesso succede che la connessione alla sorgente dei dati sia la stessa per tutti i pacchetti. Utilizzando gli Integration Services, questo si traduce nel fatto di avere uno stesso Connection Manager (magari con stesso nome) per tutti i pacchetti:

I dati sorgenti della mia soluzione BI provengono tutti da un database di Helper (HLP), dunque per ogni pacchetto di importazione di tali dati, ho il Connection Manager HLP (per chi se lo dovesse chiedere, l’utilizzo di uno Shared Data Source è sconsigliato, poiché la Connection String è cablata al suo interno e dunque per poterla modificare è necessario aprire il progetto SSIS ed utilizzare l’apposito wizard…). Per modificare la Connection String del nostro HLP a runtime potremmo utilizzare le Package Configuration, oppure potremmo eseguire i nostri pacchetti con il DTLoggedExec di Davide Mauri (un must-have se si sta sviluppando con le versioni 2005/2008 di SQL Server). La mia necessità era però quella di modificare tutte le connessioni HLP di tutti i pacchetti all’interno del progetto SSIS di Visual Studio, in modo da poter poi versionare correttamente gli stessi pacchetti tramite Team Foundation Server (TFS).

Un buon amanuense, dotato di parecchia pazienza, avrebbe dovuto cambiare a mano tutte le connessioni facendo doppio-click sul Connection Manager HLP di ogni pacchetto, per poi modificare il server/database da cui estrarre i dati:

Non essendo io dotato di tali virtù :) ho impiegato un po’ di tempo a scrivere un piccolo script in PowerShell, sfruttando il fatto che un pacchetto SSIS (con estensione .dtsx) altro non è che un file xml, e dunque è modificabile abbastanza facilmente. Non sono un guru di PowerShell, ma sono riuscito più o meno a mettere insieme le seguenti linee di codice:

$LeftDelimiterString = “Initial Catalog=”
$RightDelimiterString = “;Provider=”
$ToBeReplacedString = “AstarteToBeReplaced”
$ReplacingString = “AstarteReplacing”
$MainFolder = “C:\MySSISPackagesFolder”

$files = get-childitem “$MainFolder” *.dtsx `
      | Where-Object {!($_.PSIsContainer)}

foreach ($file in $files)
{
(Get-Content $file.FullName)
`
            | % {$_ -replace “($LeftDelimiterString)($ToBeReplacedString)($RightDelimiterString)”, “`$1$ReplacingString`$3″}
`
| Set-Content $file.FullName;
}

Il suddetto script altro non fa che aprire ogni pacchetto SSIS (.dtsx) nel folder indicato; per ognuno di essi va alla ricerca del testo del tipo:

Initial Catalog=AstarteToBeReplaced;Provider=

e lo va a sostituire con:

Initial Catalog=AstarteReplacing;Provider=

Ora non entro nel dettaglio della spiegazione di ogni cmdlet utilizzata. Lascio al lettore digiuno di PowerShell lo stimolo di rispondere alle sue curiosità tramite qualche ricerca mirata in rete.

In alternativa, o si utilizza uno specifico modello a oggetti esposto in alcuni assemblies .NET messi a disposizione da Integration Services, oppure è sempre possibile utilizzare l’utility Pacman (http://www.codeplex.com/pacman)

Enjoy! :)

N.B. Avendo come sistema di versioning il TFS, prima di lanciare lo script ho posto in check out i pacchetti; al termine dell’esecuzione dello script, ho effettuato un check in.

May 242012
 

Durante il lavoro quotidiano ho sempre trovato tedioso dover necessariamente aprire prima Outlook e poi cliccare sull’apposita icona per poter scrivere una nuova mail.

Oggi ho deciso di trovare un modo più veloce per scrivere una mail e, navigando un pochino, ho trovato una soluzione. Gli step da seguire sono questi:

  1. Porsi in una porzione libera del desktop, cliccare sul destro e creare un nuovo Collegamento:

  2. Nella schermata che viene fuori, cercare il file OUTLOOK.EXE tramite il tasto Browse (nella mia macchina a 64-bit ho installato Outlook 2010 a 32-bit, dunque il path completo è C:\Program Files (x86)\Microsoft Office\Office14\OUTLOOK.EXE):

  3. Aggiungere dunque accanto al path riportato nella casella di testo il parametro “/c ipm.note”, facendo attenzione a separarlo con uno spazio dal doppio apice del path:

  4. Nella schermata successiva fornire un nome per il Collegamento che si sta creando, ad esempio “Nuova e-mail”
  5. Cliccare col destro sul Collegamento appena creato sul Desktop, porsi sul campo Scorciatoia (Shortcut key) e digitare una lettera alla quale associare il comando (io ho scelto la ‘M’; da notare che automaticamente si aggiungeranno anche i tasti CTRL e ALT alla selezione):

Il gioco è fatto! Ora con un semplice [CTRL] + [ALT] + [M] si aprirà un nuovo messaggio e-mail di Outlook.

Ciauz!

Apr 032012
 

Da qualche giorno i Labs di Red Gate hanno messo a disposizione sul web un servizio gratuito che permette di formattare le nostre query scritte in T-SQL :)

Il servizio in questione è disponibile su questo sito:

http://www.format-sql.com/

Come è evidente dalla interfaccia messa a disposizione nella pagina web, il servizio controlla la sintassi della query e permette la condivisione della stessa formattata, sia tramite un link diretto alla pagina, sia tramite un link che permette l’embedding della query in una pagina web.

Il motore si poggia su SQL Prompt, un add-in di SSMS e Visual Studio a pagamento della Red Gate, che facilita di molto lo sviluppo di script in T-SQL.

E’ un prodotto giovanissimo e sembra che Red Gate sia avida di feedback da parte degli utenti per capirne le necessità :) dunque affrettatevi!

P.S. Per dovere di completezza, condivido con voi un servizio analogo un po’ più maturo del suddetto: trattasi di Poor SQL, che mette a disposizione gratuitamente anche un add-in per SSMS.

Feb 062012
 

Per chi non conoscesse l’add-in, le OLAP PivotTable Extensions aggiungono delle feature interessanti alle tabelle pivot di Excel 2007 o 2010 che puntano ad un cubo OLAP di Analysis Services. Una delle tante che personalmente utilizzo più spesso, è quella di conoscere il codice MDX di una query associata alla pivot in quel momento utilizzata in Excel:

AvgTaxMDX2.png

Tutti i dettagli li trovate nella Home del progetto:

http://olappivottableextend.codeplex.com/

E’ stata rilasciata da poco una nuova release dell’add-in, la 0.7.4, che non introduce nessuna nuova feature, ma risolve un bug significativo:

Release 0.7.4 now properly handles languages but introduces no new features. International users who run a different Windows language than their Excel UI language may be receiving an error message when they double click a cell and perform drillthrough which reads: "XML for Analysis parser: The LocaleIdentifier property is not overwritable and cannot be assigned a new value". This error was caused by OLAP PivotTable Extensions in some situations, but release 0.7.4 fixes this problem.

Ciao!

Jan 302012
 

Vi è mai capitato di dover aggiungere più di un pacchetto esistente (immaginiamo 20 pacchetti) di Integration Services in un progetto SSIS? Bene, qualcuno di voi penserà giustamente che una Open Dialog supporti la selezione multipla di file per importarne più di uno alla volta…

Ebbene, in BIDS l’Open Dialog non permette questa cosa, si può selezionare un solo file!!! Di qui la perdita di tempo prezioso speso ad importare i pacchetti uno alla volta.

Oggi sono però venuto a conoscenza di un trucchetto che risolve il problema, grazie a questo post di Matt Masson.

E’ sufficiente copiare da Windows Explorer tutti i pacchetti da importare (CTRL + C):

Dopodiché basta cliccare col destro sulla cartella SSIS Packages del progetto di Integration Services e fare un semplice Past:

Ora “automagicamente” avremo tutti i pacchetti importati nel nostro progetto!!

Che dire… questa feature era ben nascosta!! Smile