web 2.0

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

Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading