# 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**:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1742201191127/e96ea9e1-4c35-4966-8c33-ff662b118bd7.png align="center")

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:
    
    ```sql
    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.
    
* ```sql
      [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.
    
    ```sql
    [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**”.
    
    ```sql
    [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"**“
    
    ```sql
    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.
    
    ```sql
    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***.
    
* ```sql
      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:

```sql
@> ! 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.

```sql
@> 
 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)**)

```sql
@> ! 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
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
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
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
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.

```sql
@> 
 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.

```sql
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
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
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
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
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 &lt;DIRECTORY&gt; location('&lt;FILE&gt;'))
    

```sql
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(&lt;DIRECTORY&gt;'&lt;FILE&gt;'))
    

```sql
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
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.
