Samstag, September 05, 2015

Vereinfachter csv-Import mit SQL Loader Express in 12c

Ich will nicht behaupten, dass es mich tief erschüttert hätte, dass Tom Kyte vor einigen Tagen eine Auszeit angekündigt und seine AskTom-Seite an Chris Saxon und Connor McDonald übergeben hat - aber völlig unberührt gelassen, hat es mich jedenfalls nicht. Meine Beschäftigung mit Oracle-Datenbanken hat im Jahr 2000 begonnen und AskTom war gerade in den ersten Jahren der Ort, an dem ich nach Erklärungen suchte für alle Verhaltensweisen des RDBMS, die ich nicht verstanden hatte - und das waren zunächst ziemlich viele. Die Bücher, die der Herr Kyte im Lauf der Jahre veröffentlicht hat, stehen immer in Griffweite im Regal und obwohl ich AskTom in den letzten Jahren nur noch selten besucht habe - unter anderem, weil die interessanten Threads irgendwann unendlich lang wurden - bedauere ich Toms Abschied; und freue mich andererseits darüber, dass er plant, weitere Bücher zu schreiben, und dass er so kompetente Nachfolger für die Weiterführung von AskTom gefunden hat.

Was mich zum eigentlichen Thema bringt: in seinem eigenen Blog (den ich auch seit vielen Jahren verfolge) hat Connor McDonald (von dem hier auch noch irgendwo ein Buch steht) vor kurzem einen Artikel zu einem interessanten neuen Feature in 12c veröffentlicht: dem SQL Loader Express. Dieser leistet etwas ganz Simples: er erlaubt ein vereinfachtes Einlesen von csv-Dateien - ein Feature, das mir bei Oracle seit vielen Jahren gefehlt hat, weil dieses Einlesen in anderen RDBMS schon seit langer Zeit sehr viel einfacher durchzuführen war. Dazu ein minimales Beispiel (das noch deutlich minimaler ist als das des Herrn McDonald und die entsprechenden Erläuterungen des von ihm verlinkten White Paper):

-- csv-Datei emp.txt mit folgenden Daten
7902,FORD,ANALYST
7844,TURNER,SALESMAN
7788,SCOTT,ANALYST
7654,MARTIN,SALESMAN
7566,JONES,MANAGER
7369,SMITH,CLERK
7876,ADAMS,CLERK
7900,JAMES,CLERK
7698,BLAKE,MANAGER
7782,CLARK,MANAGER
7521,WARD,SALESMAN
7934,MILLER,CLERK
7499,ALLEN,SALESMAN
7839,KING,PRESIDENT

-- Anlage einer entsprechenden Tabelle emp im Schema test
 create table emp(
  empno    number(4,0),
  ename    varchar2(10),
  job      varchar2(9)
);

-- Aufruf des sqlldr im Verzeichnis, in dem sich die csv-Datei befindet
sqlldr userid=test/test data=emp.txt table=emp

SQL*Loader: Release 12.1.0.2.0 - Production on Fri Oct 3 00:57:18 2014

Copyright (c) 1982, 2014, Oracle and/or its affiliates.  All rights reserved.

Express Mode Load, Table: EMP
Path used:      External Table, DEGREE_OF_PARALLELISM=AUTO

Table EMP:
  14 Rows successfully loaded.

Check the log files:
  emp.log
  emp_%p.log_xt
for more information about the load.

Das zugehörige Log zeigt sehr detailliert, was im Hintergrund geschieht:

Express Mode Load, Table: EMP
Data File:      emp.txt
  Bad File:     emp_%p.bad
  Discard File:  none specified

 (Allow all discards)

Number to load: ALL
Number to skip: 0
Errors allowed: 50
Continuation:    none specified
Path used:      External Table

Table EMP, loaded from every logical record.
Insert option in effect for this table: APPEND

   Column Name                  Position   Len  Term Encl Datatype
------------------------------ ---------- ----- ---- ---- ---------------------
EMPNO                               FIRST     *   ,       CHARACTER
ENAME                                NEXT     *   ,       CHARACTER
JOB                                  NEXT     *   ,       CHARACTER

Generated control file for possible reuse:
OPTIONS(EXTERNAL_TABLE=EXECUTE, TRIM=LRTRIM)
LOAD DATA
INFILE 'emp.txt'
APPEND
INTO TABLE EMP
FIELDS TERMINATED BY ","
(
  EMPNO,
  ENAME,
  JOB
)
End of generated control file for possible reuse.

created temporary directory object SYS_SQLLDR_XT_TMPDIR_00000 for path /home/oracle

enable parallel DML: ALTER SESSION ENABLE PARALLEL DML

creating external table "SYS_SQLLDR_X_EXT_EMP"

CREATE TABLE "SYS_SQLLDR_X_EXT_EMP"
(
  "EMPNO" NUMBER(4),
  "ENAME" VARCHAR2(10),
  "JOB" VARCHAR2(9)
)
ORGANIZATION external
(
  TYPE oracle_loader
  DEFAULT DIRECTORY SYS_SQLLDR_XT_TMPDIR_00000
  ACCESS PARAMETERS
  (
    RECORDS DELIMITED BY NEWLINE CHARACTERSET US7ASCII
    BADFILE 'SYS_SQLLDR_XT_TMPDIR_00000':'emp_%p.bad'
    LOGFILE 'emp_%p.log_xt'
    READSIZE 1048576
    FIELDS TERMINATED BY "," LRTRIM
    REJECT ROWS WITH ALL NULL FIELDS
    (
      "EMPNO" CHAR(255),
      "ENAME" CHAR(255),
      "JOB" CHAR(255)
    )
  )
  location
  (
    'emp.txt'
  )
)REJECT LIMIT UNLIMITED

executing INSERT statement to load database table EMP

INSERT /*+ append parallel(auto) */ INTO EMP
(
  EMPNO,
  ENAME,
  JOB
)
SELECT
  "EMPNO",
  "ENAME",
  "JOB"
FROM "SYS_SQLLDR_X_EXT_EMP"

dropping external table "SYS_SQLLDR_X_EXT_EMP"

Table EMP:
  14 Rows successfully loaded.

Demnach umfasst die Operation folgende Schritte:
  • Generierung eines wiederverwendbaren sqlldr control files.
  • Erzeugung eines temporären directories für das Verzeichnis, in dem die csv-Datei liegt und in dem der sqlldr-Aufruf erfolgte.
  • Aktivierung von parallel dml für die Session.
  • Anlage einer external table basierend auf der Struktur der csv-Datei.
  • parallelisiertes Insert Append zur Übertragung der Daten der External Table in die Zieltabelle.
  • Drop der External Table.
Das Verfahren erspart eine Menge Definitionsaufwand und kann als External Table Generator verwendet werden (wie David Aldridge in seinem Kommentar bemerkt).

Nachtrag 14.09.2015: offenbar auch wieder ein Thema, das aktuell ein gewisses Interessae hervorruft - jedenfalls hat auch Oren Nakdimon ein schönes entsprechendes Beispiel veröffentlicht.

Keine Kommentare:

Kommentar veröffentlichen