Archivo
Recursividad en PL/SQL (II) – Implementación alternativa
Ref. ”Recursividad en PL/SQL – Un ejemplo“
Como bien se identifica en el ejemplo de la referencia, se está tratando con datos existentes en la BBDD que tienen estructura jerárquica. También se identifica una relación unívoca entre un ‘maestro’ / ‘detalle’ (‘Carpeta’ vs ‘Documento’) y operaciones donde si se actúa sobre la entidad ‘maestro’ implica acciones sobre la ‘detalle’ –operación de ‘DELETE’-.
Partiendo de esas dos premisas iniciales, a continuación se expone una propuesta de nuevo modelo de programación a partir del caso del ejemplo de la referencia que, aunque considerándose como un ejemplo muy acertado, permitirá ver otro tipo de soluciones más óptimas.
1.- Uso del Modelo Relacional
Por definición, si un registro de ‘Carpeta’ es borrado, carece de sentido la existencia de registros asociados en ‘Documento’. Es más, por integridad referencial no es posible borrar una carpeta que tenga documentos vinculados.
Por lo tanto es recomendable modificar la relación establecida y definida a nivel de ‘CONSTRAINT’ de clave foránea -’FK’- :
Evitar errores de “tabla mutante” en Oracle Database
Hace poco me preguntaron en un cliente cómo podían evitar la aparición del error:
ORA-04091: table xxx.yyyy is mutating, trigger/function might not see it
Primero os comento un poco qué es una “tabla mutante“. Se considera “mutante” una tabla que está siendo modificada, por ejemplo la tabla sobre la que el propio trigger se ha disparado por causa de un update/insert/delete.
O lo que es lo mismo, dentro de un trigger no podemos hacer referencia a la misma tabla que lo ha disparado, ya que, al estar siendo modificada, Oracle no puede asegurar una visión “consistente” de sus datos. En caso de hacerlo aparece este error.
Correcto, ¿y qué podemos hacer? Pues hay varias soluciones:
Hasta la versión 10gR2 podíamos:
Cambiar el código y no usar triggers: Podría ser que se pudiese solucionar modificando el diseño del modelo de datos o reprogramando una parte del codigo.
Usar un trigger “AFTER”: En “AFTER” los cambios en la tabla ya estan “consumados” y podemos acceder a una visión “consistente” de ésta.
Usar “PRAGMA AUTONOMOUS_TRANSACTION”: Nuestro trigger se ejecuta en una transacción diferente, y por tanto vemos la tabla en modo “consistente”. No es muy recomendable usar este sistema, ya que por ejemplo:
- Si queremos realizar varios cambios seguidos dentro de la misma transacción no tendremos acceso a ellos (es una transacción diferente cada vez)
- O si el trigger falla y se hace rollback tampoco nos enteraremos (no deshará la transacción en que se ha disparado el trigger)
A partir de la versión 11gR1, a las anteriores opciones podemos añadir:
Usar “COMPOUND TRIGGERS”: En la versión 11gR1 ha aparecido un nuevo tipo de trigger llamado “Compound Triggers“. En estos triggers podemos realizar cálculos previos para definir un “estado” que después es accesible durante la ejecución del trigger.
En resumen, que los cálculos que queramos hacer sobre la tabla afectada por el trigger los haremos previamente, guardando los valores necesarios en variables a las que accederemos posteriormente (evitando el acceso directo a la tabla y por tanto el problema). No nos soluciona todos los casos de tablas mutantes pero sí una parte importante de ellos.
El ejemplo que viene en la documentación es bastante bueno, por lo que os lo referencio directamente: Example 9-4 Compound Trigger that Avoids Mutating-Table Error
PD: No dejéis de estudiar qué otras ventajas ofrecen los “compound triggers”, pues la solución a las tablas mutantes sólo es una consecuencia de sus funcionalidades
Recursividad en PL/SQL – Un ejemplo
El PL/SQL, como la mayoría de lenguajes modernos, nos permite implementar algoritmos recursivos en nuestros procesos. En los modelos relacionales de base de datos, es frecuente encontrarse con estructuras reflexivas. La recursividad nos facilita la programación de tareas que afectan a estos tipos de construcciones. A continuación presentamos un escenario donde aplicando recursividad disminuye la complejidad del proceso.
Hemos diseñado un sistema sencillo que almacena documentos en carpetas. El modelo simplificado de nuestro sistema es el siguiente:
Como se puede observar, una carpeta puede contener otras carpetas, y estas a su vez, otras. El usuario puede guardar una estructura como la que sigue:
Leer un archivo de texto mediante una consulta de BBDD
En Oracle existen las tablas externas que nos permiten vincular archivos de texto a tablas de base de datos. Este sistema es útil en numerosos casos, pero tiene el inconveniente que hay que declarar la estructura del archivo (columnas, separador, nulos…), tarea a menudo laboriosa. A veces sólo nos interesa poder consultar los datos de un archivo de forma ocasional. Vamos a detallar un método que nos permitirá leer mediante una consulta el contenido de un archivo sin necesidad de declarar tablas externas.
La solución propuesta consiste en la creación de una función que lee el archivo y devuelve el contenido de éste en un array. Para conseguir mejores resultados en el caso de archivos grandes, usaremos el método de las “pipelined table functions”, que nos permitirá ir devolviendo las líneas a medida que son leídas desde el archivo.
El primer paso será crear un tipo array de varchar2.
CREATE TYPE Ttb_archivo_texto AS TABLE OF VARCHAR2(4000);
A continuación creamos la función que lee el archivo. La función recibirá por parámetro el directorio de lectura y el nombre del archivo a leer. Devolverá el contenido del archivo en un array del tipo que acabamos de crear.
CREATE FUNCTION LEER_ARCHIVO (P_DIRECTORIO VARCHAR2, P_ARCHIVO VARCHAR2 ) RETURN Ttb_archivo_texto PIPELINED AS vArchivo utl_file.file_type; vLinea varchar2(4000); BEGIN vArchivo := utl_File.fopen (P_DIRECTORIO, P_ARCHIVO, 'R'); -- Leemos cada una de las líneas del archivo y la retornamos Loop Begin utl_file.get_line (vArchivo, vLinea); exception when NO_DATA_FOUND then exit; end; Pipe row (vLinea); end loop; utl_file.fclose (vArchivo); return; END; /
Ya podemos usar la función en una consulta. Para ello, vamos a llamarla en la cláusula FROM utilizando la función pl/sql “table”:
Select COLUMN_VALUE
from table (LEER_ARCHIVO ('OUT_SALDSV', 'test.txt'));
Nota: COLUMN_VALUE es el nombre que recibe por defecto la columna devuelta por la función de tabla.




