SQL*Loader (Parte 1)


¿Qué es SQL*Loader? Es el software de Oracle por excelencia para realizar cargas en nuestras tablas a partir de ficheros planos, donde podemos:
Cargar datos en situaciones donde los archivos se encuentran en ubicaciones distintas a la base de datos permitiendo una mayor flexibilidad.
Capacidad de cargar múltiples archivos o tablas en una misma sesión.
Posibilidad de carga paralela (PARALLEL=TRUE) para aquellos escenarios que involucran grandes volúmenes de información.
Posibilidad de emplear diferentes conjuntos de caracteres a nivel de sesión garantiza que se pueda trabajar con datos en diversos formatos.
Manipulación de los datos antes de su carga es otra característica valiosa, ya que permite decidir qué información se debe importar.
El uso de funciones SQL para la manipulación de datos antes de su almacenamiento en las tablas proporciona un control adicional sobre el proceso de carga.
Posibilidad de usar cargas convencionales, direct path o tablas externas.
En el siguiente diagrama podemos ver el flujo que genera el uso del SQL*Loader:
Vamos a describir cada uno de los puntos que vemos en el flujo:
Input: Fichero con los datos que queremos insertar en nuestras tablas.
CTL: Archivo de control. Es un fichero de texto cuya sintaxis es case-insensitive. Cuando usamos el SQL*Loader lo podemos hacer de dos maneras, con o sin archivo de control. El uso de un archivo de control en las operaciones, nos permitirá tener un mejor control en las operaciones de carga. Algo idóneo cuando nuestras cargas cada vez son más complejas. Cuando no usamos archivos de control, esto se conoce como SQL*Loader Express.
El archivo de control tiene tres secciones principales:
Información de la sesión
Información de la tabla así como sus campos.
Datos de entrada (opcional)
Ejemplo de un fichero de control:
OPTIONS (SKIP=1)
LOAD DATA
APPEND
INTO TABLE <SCHEMA>.<TABLE>
FIELDS TERMINATED BY ";"
TRAILING NULLCOLS
(
COL_01 CONSTANT '%?VALUE?%',
COL_02 EXPRESSION
"case
when SUBSTR(SUBSTR('%?NOMBRE_FICHERO?%',0,INSTR('%?NOMBRE_FICHERO?%', '-',1)-1),5)*7 > 366 then
trunc(to_date(concat(SUBSTR(SUBSTR('%?NOMBRE_FICHERO?%',1,6),1,4),365) , 'YYYYddd'), 'iw')
else
trunc(to_date(concat(SUBSTR(SUBSTR('%?NOMBRE_FICHERO?%',1,6),1,4),SUBSTR(SUBSTR('%?NOMBRE_FICHERO?%',0,INSTR('%?NOMBRE_FICHERO?%', '-',1)-1),5)*7), 'YYYYdd d'), 'iw')
end",
COL_03 CHAR TERMINATED BY ";",
DSC: Fichero de descarte, contiene los registros que no se han podido insertar porque no cumplen con los criterios de selección establecidos en el archivo de control, es decir, aquellos registros que han sido filtrados y no se procesarán.
[oracle@]$ cat CONTROL_33.dsc ;;;;;;;;;;;;;;;;;;;;;;;
LOG: Archivo de log de la operación. Contiene información detallada del flujo generado, incluyendo descripciones o errores que se hayan producido. El log se genera cuando empieza el proceso del SQL*Loader y es importante recalcar que si el SQL*Loader no puede crear el archivo log, el proceso termina.
[oracle]$ head -30 74334127.log SQL*Loader: Release 19.0.0.0.0 - Production on Sat Mar 15 19:35:26 2025 Version 19.21.0.0.0 Copyright (c) 1982, 2019, Oracle and/or its affiliates. All rights reserved. Control File: CONTROL_33.ctl Character Set UTF8 specified for all input. Data File: CONTROL_33.dat Bad File: CONTROL_33.bad Discard File: CONTROL_33.dsc (Allow all discards) Number to load: ALL Number to skip: 0 Errors allowed: 100000 Bind array: 100 rows, maximum of 1048576 bytes Continuation: none specified Path used: Conventional Silent options: FEEDBACK Table <TABLE>, loaded from every logical record. Insert option in effect for this table: APPEND TRAILING NULLCOLS option in effect Column Name Position Len Term Encl Datatype ------------------------------ ---------- ----- ---- ---- --------------------- .....
BAD: Contiene los registros que fueron rechazados por SQL*Loader o por Oracle Database. Este fichero se crea automáticamente aunque no se especifique. El nombre que toma al crearse automáticamente, será el del fichero de datos pero cambiando la extensión a ".bad”.
[oracle@]$ head -10 CONTROL_33.bad 1010228;ES0031000001010228GB1F;ERZ;22;RA;220;;;;1010228-01;S;;;954269244;;;;;;N;;GS;fakedns-testformaxlength255-202310191143-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000:9999;;0;;;;;;;R;;;;;;
Para ejecutar esta herramienta, lo haremos con el comando sqlldr. Al ejecutarlo, internamente lo que hace es leer los archivos de datos si están especificados en el archivo de control (INFILE='CONTROL.dat' ) o bien a la hora de ejecutarlo (sqlldr user/pass@local_sid DATA='CONTROL.dat').
Los archivos de datos no son más que registros para nuestras tablas, donde cada registro puede ser:
Tamaño fijo: Cuando los registros tienen la misma longitud en bytes. Vamos a modificar el ejemplo anterior, especificando un fijo de 8 bytes (incluyendo espacios) “INFILE ___ "fix 8"“
OPTIONS (SKIP=1) LOAD DATA INFILE 'CONTROL_33.dat' "fix 8" INTO TABLE <SCHEMA>.<TABLE> FIELDS TERMINATED BY ";" TRAILING NULLCOLS ( ....
Variable: Cuando la longitud de cada entrada en un campo de caracteres se incorpora al inicio de cada registro en el archivo de datos, se proporciona una mayor flexibilidad en comparación con un tamaño fijo. Este método permite gestionar de manera más eficiente las variaciones en la longitud de los datos, lo que facilita su procesamiento y almacenamiento.
La especificación del tamaño variable de 8 bytes “INFILE ___ "var 8"“. El valor por defecto es 5 en caso de no especificar y un valor máximo de 40.
LOAD DATA INFILE 'CONTROL_33.dat' "var 8" INTO TABLE <SCHEMA>.<TABLE> FIELDS TERMINATED BY ";" TRAILING NULLCOLS ( ....
De flujo: Cuando no especificamos un tamaño, será el propio SQL*Loader el que formará los registros buscando el record terminator.
LOAD DATA INFILE 'CONTROL_33.dat' INTO TABLE <SCHEMA>.<TABLE> FIELDS TERMINATED BY ";" TRAILING NULLCOLS ( ....
Vamos hacer un ejemplo de un flujo de SQL*Loader, definiendo una tabla y un fichero con cierto contenido para inyectar con SQL*Loader. En este caso no voy a especificar el fichero de datos en el archivo de control, sino que lo voy a especificar en la ejecución del comando. ¡Vamos a ello!
Contenido que vamos a insertar en nuestra tabla:
@> ! cat datos.dat
A;B
JOIN;consumidor directo mercado
VIEPR;Productor
ATAL;Productor
BURGE;Productor
Definición de una tabla para guardar el contenido del fichero Será una tabla sencilla de tres columnas, donde una de ellas será incremental.
@>
Create Table t_Sqlloader (
id number generated by default as identity,
Info_1 Varchar2(100),
Info_2 Varchar2(100)
);
Archivo de Control, especificamos que nuestro fichero tiene una fila que no queremos que se vuelque en nuestro tabla porque es de encabezado y queremos que SQL*Loader la ignore (OPTIONS (SKIP=1))
@> ! cat datos.ctl
OPTIONS (SKIP=1)
LOAD DATA
CHARACTERSET UTF8
INTO TABLE T_SQLLOADER
FIELDS TERMINATED BY ';' TRAILING NULLCOLS
(
INFO_1,
INFO_2
)
Ejecutamos y consultamos la tabla para ver el resultado. En nuestro caso especifico los nombres para el archivo LOG, BAD y DSC. Ninguno de ellos es obligatorio, en caso de no especificarlo, tomaría por defecto el nombre del archivo de datos pero cambiando la extensión.
SQL> !
sqlldr "*****"/"*****"@*****
DATA=datos.dat CONTROL=datos.ctl LOG=FLUJO.log BAD=REGISTROS_KO.bad DISCARD=REGISTROS_ERR.dsc
SQL*Loader: Release 19.0.0.0.0 - Production on Tue Mar 18 17:13:14 2025
Version 19.21.0.0.0
Copyright (c) 1982, 2019, Oracle and/or its affiliates. All rights reserved.
Path used: Conventional
Commit point reached - logical record count 4
Table T_SQLLOADER:
4 Rows successfully loaded.
Check the log file:
FLUJO.log
for more information about the load.
SQL> r
1* Select Info_1, Info_2 From t_SqlLoader
ID INFO_1 INFO_2
---------- ---------- ----------------------------------------
1 JOIN consumidor directo mercado
2 VIEPR Productor
3 ATAL Productor
4 BURGE Productor
Si volvemos a ejecutar la misma instrucción, recibimos el siguiente error:
SQL> !
sqlldr "*****"/"*****"@*****
DATA=datos.dat CONTROL=datos.ctl LOG=FLUJO.log BAD=REGISTROS_KO.bad DISCARD=REGISTROS_ERR.dsc
SQL*Loader: Release 19.0.0.0.0 - Production on Tue Mar 18 17:15:47 2025
Version 19.21.0.0.0
Copyright (c) 1982, 2019, Oracle and/or its affiliates. All rights reserved.
Path used: Conventional
SQL*Loader-601: For INSERT option, table must be empty. Error on table T_SQLLOADER
Esto es debido a que la tabla debe estar vacía a no ser que indiquemos que lo que queremos hacer es añadir más filas (APPEND). Vamos a modificar el archivo de control incluyendo esta clausula:
SQL> ! cat datos.ctl
OPTIONS (SKIP=1)
LOAD DATA
CHARACTERSET UTF8
APPEND
INTO TABLE T_SQLLOADER
FIELDS TERMINATED BY ';' TRAILING NULLCOLS
(
INFO_1,
INFO_2
)
Ejecutamos de nuevo y vemos el resultado:
SQL> !
sqlldr "*****"/"*****"@*****
DATA=datos.dat CONTROL=datos.ctl LOG=FLUJO.log BAD=REGISTROS_KO.bad DISCARD=REGISTROS_ERR.dsc
SQL*Loader: Release 19.0.0.0.0 - Production on Tue Mar 18 17:22:08 2025
Version 19.21.0.0.0
Copyright (c) 1982, 2019, Oracle and/or its affiliates. All rights reserved.
Path used: Conventional
Commit point reached - logical record count 4
Table LIQUIDACIONES.T_SQLLOADER:
4 Rows successfully loaded.
Check the log file:
FLUJO.log
for more information about the load.
SQL> r
1* Select Info_1, Info_2 From t_SqlLoader
INFO_1 INFO_2
-------------------- ----------------------------------------
JOIN consumidor directo mercado
VIEPR Productor
ATAL Productor
BURGE Productor
JOIN consumidor directo mercado
VIEPR Productor
ATAL Productor
BURGE Productor
Vamos a complicar un poco más la carga de este fichero para que veamos la multitud de opciones que nos brinda esta herramienta.
Lo primero de todo vamos a limpiar la tabla y vamos a definir una nueva tabla con la misma estructura que la anterior, porque la idea es que el mismo archivo de control grabe en varias tablas.
Ejecutamos la creación de una nueva tabla, con la misma estructura pero con diferente nombre.
@>
Create Table t_sqlloader_Productor (
id number generated by default as identity,
Info_1 Varchar2(100),
Info_2 Varchar2(100)
);
Modificamos el archivo de control para que informe los registros con valor Productor en la tabla T_SQLLOADER_PRODUCTOR y el resto en la tabla T_SQLLOADER.
OPTIONS (SKIP=1)
LOAD DATA
CHARACTERSET UTF8
APPEND
INTO TABLE T_SQLLOADER WHEN INFO_2 <> 'Productor'
FIELDS TERMINATED BY ";" OPTIONALLY ENCLOSED BY '"'
TRAILING NULLCOLS
(
INFO_1 CHAR ,
INFO_2 CHAR
)
INTO TABLE T_SQLLOADER_PRODUCTOR WHEN INFO_2 = 'Productor'
FIELDS TERMINATED BY ";" OPTIONALLY ENCLOSED BY '"'
TRAILING NULLCOLS
(
INFO_1 POSITION(1) CHAR ,
INFO_2 CHAR
)
En este contexto, la clave será el uso de POSITION(1). Al emplear la cláusula WHEN para diferentes bloques, es necesario utilizar POSITION(1) para el primer campo. Si no se especifica, se anticipa que los campos siguientes correspondan a las posiciones de los campos de la cláusula WHEN anterior, lo que resultaría en que los registros que cumplan con la condición INFO_2 = 'Productor' no se insertarían en nuestra tabla. Otra particularidad de la cláusula WHEN es que no admite el operador OR; únicamente se permite el uso de AND.
SQL> !
sqlldr "*****"/"*****"@*****
DATA=datos.dat CONTROL=datos.ctl LOG=datos.log BAD=datos.bad DISCARD=datos.dsc
SQL*Loader: Release 19.0.0.0.0 - Production on Tue Mar 18 20:35:08 2025
Version 19.21.0.0.0
Copyright (c) 1982, 2019, Oracle and/or its affiliates. All rights reserved.
Path used: Conventional
Commit point reached - logical record count 4
Table T_SQLLOADER:
1 Row successfully loaded.
Table T_SQLLOADER_PRODUCTOR:
3 Rows successfully loaded.
Check the log file:
datos.log
for more information about the load.
SQL> Select Info_1, Info_2 From t_Sqlloader;
INFO_1 INFO_2
-------------------- ----------------------------------------
JOIN consumidor directo mercado
SQL> Select Info_1, Info_2 From t_Sqlloader_Productor;
INFO_1 INFO_2
-------------------- ----------------------------------------
VIEPR Productor
ATAL Productor
BURGE Productor
Otro manera de utilizar el SQL*Loader es External Table. Una External Tables es una tabla que no reside dentro de la base de datos. Oracle proporciona dos drivers para acceder a ellas: ORACLE_LOADER y ORACLE_DATAPUMP.
Vamos ha definir una tabla externa para el ejemplo anterior.
Lo primero que te tenemos que definir es un directorio.
SQL> Create Directory TMP_SQLLOADER As '/u01/app/oracle/sqlloader';
El fichero de datos lo tenemos, con lo que lo único que tendríamos que definir es la tabla. La sintaxis para crear una tabla es muy similar al o que definimos en un fichero de Control.
SQL> r
CREATE TABLE t_Ext_Sqlloader
(
Info_1 Varchar2(100),
Info_2 Varchar2(100)
)
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY TMP_SQLLOADER
ACCESS PARAMETERS
(
RECORDS DELIMITED BY NEWLINE
SKIP 1
CHARACTERSET UTF8
NOBADFILE
NODISCARDFILE
NOLOGFILE
FIELDS TERMINATED BY ';'
OPTIONALLY ENCLOSED BY '"'
MISSING FIELD VALUES ARE NULL
(
INFO_1,
INFO_2
)
)
LOCATION ('datos.dat')
)
REJECT LIMIT UNLIMITED
Table created.
Y si consultamos la tabla:
SQL> Select * From t_Ext_SqlLoader;
INFO_1 INFO_2
-------------------- ----------------------------------------
JOIN consumidor directo mercado
VIEPR Productor
ATAL Productor
BURGE Productor
También podemos cambiar el fichero fácilmente sin tener que recrear la tabla:
- external modify ( default directory <DIRECTORY> location('<FILE>'))
SQL> r
Select *
From t_Ext_SqlLoader
external modify ( default directory TMP_SQLLOADER location('datos2.dat'))
INFO_1 INFO_2
-------------------- ----------------------------------------
JOIN consumidor directo mercado
VIEPR Productor
ATAL Productor
BURGE Productor
JOIN consumidor directo mercado
VIEPR Productor
ATAL Productor
BURGE Productor
JOIN consumidor directo mercado
VIEPR Productor
ATAL Productor
BURGE Productor
- external modify ( location(<DIRECTORY>'<FILE>'))
SQL> r
Select * From t_Ext_SqlLoader external modify ( location(TMP_SQLLOADER:'datos2.dat') )
INFO_1 INFO_2
-------------------- ----------------------------------------
JOIN consumidor directo mercado
VIEPR Productor
ATAL Productor
BURGE Productor
JOIN consumidor directo mercado
VIEPR Productor
ATAL Productor
BURGE Productor
JOIN consumidor directo mercado
VIEPR Productor
ATAL Productor
BURGE Productor
Ambas sentencias no modifican la estructura interna de la tabla como podemos observar:
SQL>
COLUMN DIRECTORY_NAME FORMAT A20
COLUMN LOCATION FORMAT A20
Select Directory_Name, Location From Dba_External_Locations Where Table_Name = 'T_EXT_SQLLOADER'
DIRECTORY_NAME LOCATION
-------------------- --------------------
TMP_SQLLOADER datos.dat
En los próximos capítulos, continuaremos con el resto de opciones que nos ofrece SQL*Loader.
Espero que os sirva.
Subscribe to my newsletter
Read articles from David Sanz directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

David Sanz
David Sanz
Soy desarrollador, Analista, DBA Oracle y Arquitecto OCI, certificado en OCI Migration and Integration Certified Professional y Certified Architect Associate con más de 15 años de experiencia en plataformas Oracle además de especialista en temas de rendimiento.