SELECT FROM EXTERNAL liefert Fehler bei Spaltennamen TIMESTAMP

Oracle kennt das Konzept der externen Tabellen schon seit vielen Jahren. Seit Oracle 18c kann man dies auch ohne explizites anlegen einer externen Tabelle über die Funktion EXTERNAL() nutzen.

Als Test soll der Inhalt von t1.dat geladen werden:

"SYS","OBJ$","2024-10-11:15:19:56","VALID"
"SYS","I_OBJ3","2024-10-11:15:19:56","VALID"

In dem Beispiel wird die Funktion EXTERNAL() genutzt um Testdaten in eine Tabelle zu laden.

SELECT * FROM EXTERNAL (
  (
  OWNER VARCHAR2(22),
  OBJECT_NAME VARCHAR2(121),
  TIMESTAMP VARCHAR2(19),
  STATUS VARCHAR2(5)
  )
 TYPE ORACLE_LOADER
 DEFAULT DIRECTORY ARCH$_TMP_LS1M4DG
 ACCESS PARAMETERS (
 RECORDS DELIMITED BY NEWLINE
 BADFILE 't1.bad'
 LOGFILE 't1.log'
fields terminated by "," optionally enclosed by '"'
missing field values are null
  (
  owner CHAR(22),
  object_name CHAR(121),
  timestamp CHAR(19),
  status CHAR(5)
  )
)
 LOCATION ('t1.dat') REJECT LIMIT UNLIMITED)
 result
 WHERE rownum <= 1
/

Dieser Versuch wird mit folgendem Fehler quittiert:

ERROR at line 94:
ORA-29913: error in executing ODCIEXTTABLEOPEN callout
ORA-29400: data cartridge error
KUP-00554: error encountered while parsing access parameters
KUP-01005: syntax error: found "timestamp": expecting one of: "double-quoted-string, identifier, single-quoted-string"
KUP-01007: at line 16 column 4

Oracle beschwert sich, dass „timestamp“ einen Syntax error hervorgerufen hat. Wäre es ein echtes reserviertes Wort, müsste man dieses mittels doppelten Anführungszeichen quoten. Prüfen wir das inmal in V$RESERVED_WORDS.

select keyword, reserved 
  from v$reserved_words 
 where keyword like 'TIMESTAMP%'

KEYWORD                        RESERVED
------------------------------ ----------
TIMESTAMP                      N
TIMESTAMP_TO_NUMBER            N

TIMESTAMP wird zwar tatsächlich aufgeführt, aber mit RESERVED = ‚N‘ als kein echtes reserviertes Keyword behandelt.

Schauen wir doch mal im Data Dictionary nach, wie oft es eine Spalte mit dem Namen TIMESTAMP gibt:

select owner, count(*) 
  from dba_tab_columns 
 where column_name='TIMESTAMP' 
 group by owner;

OWNER        COUNT(*)
---------- ----------
SYS               166
SYSTEM              5
APPQOSSYS           4
OJVMSYS             1
DVSYS               6
OUTLN               1

Anscheinend ist der Name TIMESTAMP als Spalte recht beliebt. Das war schon vor vielen Jahren, als es den Datentype TIMESTAMP bei Oracle noch nicht gegeben hat, so. Mit der Einführung vom Datentyp TIMESTAMP – in Wirklichkeit kennt Oracle intern viele verschiedene Versionen des Datentyps! Es gibt auch noch TIMESTAMP(0), TIMESTAMP(1), … Jeder der 10 möglichen Skalierungen ist ein eigener Datentyp (mal 3, weil es die Kombinationen mit WITH TIME ZONE und mit WITH LOCAL TIME ZONE auch noch gibt). Die Vermutung liegt nahe, dass dies nur deswegen so kompliziert (gereicht hätten ja 3 Datentypen: ohne TIME ZONE, WITH TIME ZONE und WITH LOCAL TIME ZONE), damit es eben keine Probleme mit den schon vorhandenen Spaltennamen Timestamp im Data Dictionary gibt. Änderungen am Data Dictionary – mit Ausnahme von zusätzlichen Spalten – mache Oracle extrem selten, damit bestehende Abfragen mit den neuen Oracle Datenbank Versionen auch weiterhin funktionieren.

Die Lösung / der Workaround

Einfach das Keyword TIMESTAMP großschreiben und mit Quotes umschließen.

SELECT * FROM EXTERNAL (
  (
  OWNER VARCHAR2(22),
  OBJECT_NAME VARCHAR2(121),
  TIMESTAMP VARCHAR2(19),
  STATUS VARCHAR2(5)
  )
 TYPE ORACLE_LOADER
 DEFAULT DIRECTORY ARCH$_TMP_LS1M4DG
 ACCESS PARAMETERS (
 RECORDS DELIMITED BY NEWLINE
 BADFILE 't1.bad'
 LOGFILE 't1.log'
fields terminated by "," optionally enclosed by '"'
missing field values are null
  (
  owner CHAR(22),
  object_name CHAR(121),
  "TIMESTAMP" CHAR(19),
  status CHAR(5)
  )
)
 LOCATION ('t1.dat') REJECT LIMIT UNLIMITED)
 result
 WHERE rownum <= 1
/

Schöner wäre es natürlich, wenn Oracle diese Ungereimtheit in der Data Cartridge beheben würde.