web 2.0

SSIS: CSV-Dateien mit Textbegrenzungszeichen innerhalb des Textes importieren

Damit man Sonderzeichen innerhalb der Daten einer CSV-Datei nutzen kann, gibt es Textbegrenzungszeichen (engl.: text qualifier). Soll dieses Begrenzerzeichen in den Daten vorkommen, so wird dieses einfach verdoppelt (maskiert). Nachfolgend eine Beispieldatei mit dem “ als Textbegrenzungszeichen:

1|"John Doe"|17.01.1946
2|"Dwayne ""The Rock"" Johnson"|02.05.1972
3|"Steve ""Stone Cold"" Austin"|18.12.1964
4|"Randy ""Macho Man"" Savage"|15.11.1952
5|"Hulk Hogan"|11.10.1953
6|"Mark "The Undertaker" Calaway"|24.03.1965

Fügt man einen Verbindungsmanger für die CSV-Datei hinzu und wählt das doppelte Anführungszeichen(“)als “Text qualifier” aus

1 Flat File Connection Manager Editor

dann kommt es beim Wechsel auf Columns zu folgender Fehlermeldung:

2 Error

Ignoriert man diese Meldung und führt das Paket trotzdem aus, schlägt dieses fehl mit der Meldung:

[SSIS.Pipeline] Error: SSIS-Fehlercode 'DTS_E_PRIMEOUTPUTFAILED'. Die PrimeOutput-Methode in 'Komponente 'Flatfilequelle' (1)' hat den Fehlercode 0xC0202092 zurückgegeben. Die Komponente gab einen Fehlercode zurück, als das Pipelinemodul 'PrimeOutput()' aufgerufen hat. Die Bedeutung des Fehlercodes wird von der Komponente definiert. Der Fehler ist jedoch schwerwiegend, und die Ausführung der Pipeline wurde beendet. Möglicherweise wurden bereits Fehlermeldungen veröffentlicht, die weitere Fehlerinformationen beinhalten.

Damit ist klar, SSIS unterstütz keine Textbegrenzungszeichen innerhalb eines Datenfeldes (zumindest bis einschließlich der Version für SQL Server 2008).

Wichtig: Als Workaround zeige ich jetzt einen kleinen Trick, der allerdings nur hilft, wenn das Spaltentrennzeichen nicht im Text vorkommt.

Als erstes entfernt man das Textbegrenzungszeichen  im Verbindungsmanager für die CSV-Datei. Würde man dann die Daten in eine Datenbank importieren, würde man dieses Ergebnis sehen:

3 unerwuenschtes Ergebnis

Hier sind jetzt natürlich eine Menge Anführungszeichen zu sehen. Innerhalb des Textes kommen diese auch doppelt vor, was so nicht beabsichtigt ist. Deshalb führt man im Datenflusstask eine Abgeleitete Spalte (engl.: derived column) hinzu. Damit kann man die Daten in der Spalte mit diversen Funktionen bearbeiten.

4 Datenfluss

Zuvor benötigt man noch zwei Variablen, auf die ich gleich noch einmal zurückkomme.

5 Variablen

Jetzt ist alles vorbereitet und man kann den Transformationstask “Abgeleitete Spalte” bearbeiten. Dort wird unter Derived Column die zu bearbeitende Spalte ausgewählt und bei Expression folgender Wert eingegeben:

REPLACE(SUBSTRING([Column 1],2,LEN([Column 1]) - 2),@[User::q1],@[User::q2])

Im Prinzip wird hier nur der String am Anfang und Ende beschnitten (die Anführungszeichen entfernt) und doppelte Vorkommen durch ein Anführungszeichen ersetzt.

Das Ganze sieht dann so aus:

6 Derived Column Transformation Editor

Führt man jetzt das Paket aus, erscheint folgendes Ergebnis:

7 Ergebnis

Die Anführungszeichen am Anfang und Ende sind verschwunden und innerhalb des Textes kommen diese nur noch einfach vor.

Hinweis: Anscheinend kommt es nur bei Dateien im Unicode-Format zu diesen Fehler. Bei einem Test mit einer ANSI-Datei lief alles fehlerlos.

Tags:

SSIS | Datenbanken

SSIS: Importieren von CSV-Dateien mit unterschiedlicher Spaltenanzahl

Der Import von CSV-Dateien in eine Datenbank stellt ein paar Tücken bereit. Nicht immer hat man die volle Kontrolle über die CSV-Datei. Besonders wenn die Daten von einem Fremdanbieter kommen, der nicht so auf die Qualität seiner Daten achtet. Da kann es schon mal vorkommen, dass die Spaltenanzahl in den Zeilen abweicht oder durch falsches Escaping von Trennzeichen(Delimeter) erst gar nicht richtig erkannt werden.

In meinem Beispiel verwende ich eine Datei mit Komma als Spaltentrennzeichen:

1,Wert1,01.01.2001
2,Wert2,12.06.1999
3,Wert3
4,Wert4,31.01.2002
5,Wert5,01.01.2011
6,Wert6
7,Wert7
8,Wert8,24.12.1953
9,Wert9,15.09.1988
10,Wert10

Würde man diese Datei mit den Standardeinstellungen importieren, käme die folgende Zuordnung heraus:

1 falsche Zuordnung

Um dieses Problem zu umgehen, gibt es einen Workaround: Man importiert die CSV-Datei komplett als eine Spalte und extrahiert sich anschließend per Transformationstask die vorhandene Spalten.

Hierfür löscht man im Connection-Manager für die CSV-Datei alle erkannten Spalten und legt eine einzelne neue Spalte an. Da in CSV-Dateien durchaus auch größere Daten vorkommen können (z.B. Beschreibungstexte), wähle ich Textdatenstrom als Datentyp. Daher muss später im Skript-Task auch noch eine Konvertierung vorgenommen werden.

2 eine Spalte

Daraus ergibt sich folgende Zuordnung:

3  Zuordnung mit einer Spalte

Jetzt kann man einen Datenflusstask anlegen, der wie folgt aussehen könnte:

4 Datenflusstask

Im Transformationstask muss dann die zuvor festgelegte Spalte aus dem Verbindungsmanager als Input definiert werden:

5 Input

Des weiteren müssen die Ausgabespalten für den Transformationstask definiert werden:

6 Output

Das Skript für den Transformationstask sieht wie folgt aus:

   1:  public class ScriptMain : UserComponent
   2:  {
   3:   
   4:      private char[] columnDelimiter = new char[] { ',' };
   5:   
   6:      public override void Eingabe0_ProcessInputRow(Eingabe0Buffer Row)
   7:      {
   8:          string[] columnValues = null;
   9:   
  10:          var blobLen = Convert.ToInt32(Row.Line.Length);
  11:          byte[] blobBytes = new byte[blobLen];
  12:          blobBytes = Row.Line.GetBlobData(0, blobLen);
  13:   
  14:          string line = System.Text.Encoding.Unicode.GetString(blobBytes);
  15:          columnValues = line.Split(columnDelimiter);
  16:   
  17:          // Test Correct number of rows
  18:          if (columnValues.Length == 3)
  19:          {
  20:              // Row is OK, output values
  21:              Row.Column1 = columnValues.GetValue(0).ToString();
  22:              Row.Column2 = columnValues.GetValue(1).ToString();
  23:              Row.Column3 = columnValues.GetValue(2).ToString();
  24:          }
  25:          else
  26:          {
  27:              //Row is not complete - Handle error
  28:              Row.Column1_IsNull = true;
  29:              Row.Column2_IsNull = true;
  30:              Row.Column3_IsNull = true;
  31:          }
  32:      }
  33:  }

In der columnDelimeter-Variable wird das Spaltentrennzeichen definiert. Da ich für den Eingabeparameter Line den Datentyp Textdatenstrom  gewählt habe ist Row.Line vom Typ Microsoft.SqlServer.Dts.Pipeline.BlobColumn. Dieser wird in den Zeilen 10 –14 in einen String umgewandelt und kann anschließend per Split-Funktion geteilt werden. Ist die korrekte Anzahl an Spalten vorhanden (in meinem Fall 3), werden die Daten an die Ausgabeparameter übergeben. Andernfalls werden NULL-Werte zurückgegeben.

Die Daten importiert in eine Datenbank liefern folgendes Ergebnis:

7 Ergebnis

Alle Zeilen, die nicht die korrekte Anzahl an Spalten haben werden ignoriert und es werden NULL-Werte in die Datenbank geschrieben. Dieses Verhalten kann man natürlich im Skript-Task nach seinen Wünschen anpassen.

Tags:

SSIS | Datenbanken

Dynamische Connectionstrings für SSIS

Gerade beim Erstellen von SSIS-Packages probiert man oft viel aus und ändert gern mal ein paar Einstellungen. Besonders nervend ist das, wenn man mal den Connectionstring für eine Datenbank ändern muss. Das SSIS-Package öffnen, die Werte anpassen und neu auf dem Server bereit stellen ist da keine gute Lösung.

Hierfür bietet sich die Paketkonfiguration an. Man kann alle verwendeten Variablen als Konfiguration abspeichern, unter anderem auch als XML-Datei. Diese XML-Datei kann man dann anpassen, ohne das SSIS-Package neu zu veröffentlichen.

Als erstes legen wir uns ein neue Connection an. In meinem Beispiel ist es eine ADO.NET Connection. Die eingegebenen Werte sind hier erst einmal nicht wichtig, da sie später durch die Variablenwerte überschrieben werden.

1 ConnectionManager

Dazu werden folgende vier Variablen vom Typ String angelegt: DatabaseServer, DatabaseName, DatabaseUsername und DatabasePassword. Als Value werden hier die entsprechenden Werte für den Namen des Datenbankservers usw. angegeben.

2 Variables

Wenn man sich dann die Eigenschaften der neu erstellen Connection anschaut (Rechtsklick auf die Connection und den Punkt Properties aus dem Kontextmenu wählt), erscheint dort auch ein Eintrag für Expressions.

3 Connection Properties b

Durch einen Klick in das leere Feld unter Property öffnet sich eine Auswahlliste aller verfügbaren und anpassbaren Eigenschaften. Wir wählen hier ConnectionString aus und klicken anschließend auf die entsprechende Expression.

4 Expression

In dem geöffneten Expression Builder geben wir den folgenden Wert in das Feld Expression ein:

"Data Source=" + @[User::DatabaseServer] + ";User ID=" + @[User::DatabaseUsername] + ";Password=" + @[User::DatabasePassword] + ";Initial Catalog=" + @[User::DatabaseName] +";"

Die Expression kann natürlich durch eigene Variablen weiter angepasst werden. Diese kann man entweder per Hand eintippen oder per Drag&Drop in das Eingabefeld der Expression ziehen.

5 Expression Builder

Ob die Expression korrekt erstellt wurde, kann man durch einen Klick auf den Button “Evaluate Expression” testen. Wenn alles in Ordnung ist, wird die Expression in das ausgegraute Feld “Evaluated value” übernommen.

Damit ist die erzeugte Connection jetzt vollständig über die Variablen konfigurierbar. Der Vorteil der eingangs erwähnten Konfigurationsdateien ist die Entwicklung von SSIS-Packages auf einem anderen Rechner als den später verwendeten Server.

Hinweis: Alternativ könnte man auch den kompletten Connectionstring in eine Variable speichern. Aus Gründen der Übersichtlichkeit bevorzuge ich aber die Variante mit mehreren Variablen.

Tags:

SSIS | Datenbanken