SlideShare uma empresa Scribd logo
1 de 23
Baixar para ler offline
Oracle PL/SQL



                            Autor:


                Eduardo Bottini
                      ebottini@c-s-i.com.ar




Prohibida la reproducción total o parcial sin permiso explícito del autor.
Oracle PL/SQL – Variables de tipo Cursor.
Oracle nos permite utilizar dentro de sus procedimientos almacenados
variables que referencian a cursores de datos. Estas variables son
punteros y llevan el nombre de Ref Cursor. Delphi, por su parte, accede a
éstas en forma nativa mediante el tipo de dato ftCursor.

 A partir de este número de Mundo Delphi comenzamos con una serie de artículos que tratan sobre las
características más potentes del lenguaje PL/SQL de Oracle. En esta edición, donde ahondaremos en los
detalles del tema del título principal, veremos también algunos de estos atributos tales como el manejo de
excepciones y la programación utilizando paquetes (packages), temas éstos que profundizaremos en
próximas entregas.

El tipo Ref Cursor
  Las variables de tipo cursor son referencias a cursores, más precisamente son punteros a un área de
trabajo donde Oracle almacena los datos que resultan de una selección de múltiples registros.
Estos punteros almacenan la dirección de memoria del objeto apuntado (y no el objeto en sí, tal como lo
hace un cursor explícito) y por lo tanto pueden pasarse como parámetros de entrada/salida entre
procedimientos almacenados o funciones de PL/SQL residentes en el servidor, y programas clientes
escritos en otros lenguajes como Delphi, Pro*C, etc.
Este pasaje de parámetros puede realizarse en ambos sentidos (Cliente/Servidor/Cliente) a fin de poder
trabajar en cualquiera de estos entornos accediendo simultáneamente al mismo área de trabajo donde
residen los datos de la consulta realizada. Esto equivale a decir que podemos compartir entre
procedimientos y funciones escritas en diferentes lenguajes y ejecutándose en diferentes máquinas el
resultado de una consulta residente en el servidor, sin importar quién la haya generado.

Para crear este tipo de variables debe definirse primero un tipo Ref Cursor y luego declarar la variable de
este tipo. Por ejemplo:

TYPE tRc_tipo IS REF CURSOR;
Res tRc_tipo;


En este pequeño ejemplo hemos declarado Res como una variable de tipo cursor.
En PL/SQL, cualquier puntero posee el tipo de dato REF (que es la abreviatura de Reference) y la clase
de objeto a la que apunta. De allí que un Ref Cursor sea un puntero a un Cursor.

Tipos de Ref Cursor según la clase de resultado al que referencian
Dependiendo de la especificación o no del formato de datos a los que apunta una variable de tipo cursor,
se las puede clasificar en 2 tipos:

Tipo restrictivo: Se debe especificar un tipo de resultado asociado al formato del registro que devolverá
el Select. En este caso el compilador de PL/SQL controla que el resultado de la consulta coincida con el
tipo de dato asociado. Por ejemplo, si asociamos el resultado con un %RowType (tipo de registro que
representa a una fila de una tabla) sólo podremos utilizar la variable cursor con una consulta que devuelva
un registro exactamente igual a ese %RowType.

Tipo no Restrictivo: A partir de la versión 7.3 de Oracle no es necesario especificar un tipo de resultado,
pudiéndose de esta manera asociar un puntero a cualquier clase de consulta. Esta forma es mucho más
flexible ya que podremos declarar los punteros una vez y utilizarlos en cualquier momento para manipular
cualquier consulta.
 Al tipo de resultado apuntando por un Ref Cursor no restrictivo se lo denomina Variant Record.

Ejemplos de definición, declaración y uso de Ref Cursor
 Para todos los ejemplos que veremos en este artículo utilizaremos un “modelo de datos” de dos tablas:
Libros y Autores
Veamos a continuación el esquema de estas tablas en formato de comandos de creación de las mismas
junto con sus índices, llaves primarias y foráneas.




                                                                                                       1
Oracle PL/SQL – Variables de tipo Cursor.

     CREATE TABLE AUTORES (
            CODIGO_AUTOR           NUMBER(5) NOT NULL,
            NOMBRE                 VARCHAR2(100) NOT NULL,
            FECHA_NACIMIENTO       DATE NULL,
                    PRIMARY KEY (CODIGO_AUTOR)
     );

     CREATE INDEX XIE1AUTORES ON AUTORES (NOMBRE);


     CREATE TABLE LIBROS (
            CODIGO                 NUMBER(5) NOT NULL,
            NOMBRE                 VARCHAR2(100) NOT NULL,
            FECHA_EDICION          DATE NOT NULL,
            PRESTADO               VARCHAR2(1) NOT NULL CHECK (PRESTADO IN ('S', 'N')),
            CANT_PAGINAS           NUMBER(5) NULL,
            CODIGO_AUTOR           NUMBER(5) NOT NULL,
                    PRIMARY KEY (CODIGO),
                    FOREIGN KEY (CODIGO_AUTOR) REFERENCES AUTORES
     );

     CREATE INDEX XIE1LIBROS ON LIBROS (FECHA_EDICION);
     CREATE INDEX XIE2LIBROS ON LIBROS (NOMBRE);


En el siguiente ejemplo detallaremos cómo definir las dos clases de Ref Cursor que vimos anteriormente.
En el primer caso, REFCURS_AUTORES es de tipo restrictivo ya que las variables declaradas bajo este
tipo de dato deberán apuntar obligatoriamente a cursores de un tipo de dato equivalente. En este ocasión
el tipo de dato coincide con el formato de registro de la tabla Autores (RowType). Si se intentara utilizar
una variable de este tipo para apuntar a un cursor no coincidente con la definición de la misma, el
compilador de PL/SQL nos indicará el error.
El segundo tipo de Ref Cursor, REFCURS, corresponde a uno no restrictivo y tal como veremos en los
ejemplos puede utilizarse para apuntar a cualquier tipo de cursor de datos.
Para realizar la definición de ambos tipos utilizaremos un encabezado de Paquete. Esto nos permitirá que
nuestra definición de tipos sea global y podamos utilizarla en cualquier procedimiento o función
almacenada dentro del servidor.

         CREATE OR REPLACE PACKAGE PKG_REFCURS AS
                 TYPE REFCURS_AUTORES IS REF CURSOR RETURN AUTORES%ROWTYPE;
             TYPE REFCURS IS REF CURSOR;
             END;
         /


 Una vez definidos los tipos podemos declarar las variables y utilizarlas. En el siguiente ejemplo
crearemos una función que utiliza el tipo restrictivo de variable para retornar un puntero que referencia a
un cursor cuya fuente de datos es una selección de la tabla Autores.


    CREATE OR REPLACE FUNCTION FRC_TRAE_AUTORES
              RETURN PKG_REFCURS.REFCURS_AUTORES
    AS
           -- Declaración de la variable de tipo cursor, utilizando el tipo restrictivo
           res PKG_REFCURS.REFCURS_AUTORES;
    BEGIN
        OPEN res FOR
                   SELECT * FROM AUTORES;

        -- Retorno del puntero al resultado del Select
            RETURN res;
    END;
    /




                                                                                                       2
Oracle PL/SQL – Variables de tipo Cursor.
 Luego de creado, para ejecutarlo desde el SqlPlus de Oracle debe primero declararse dentro de este
entorno una variable de tipo RefCursor que nos servirá como repositorio del puntero devuelto por la
función.
Para declarar la variable, que nos servirá también para el resto de los ejemplos, debe escribirse:

         VAR    R     RefCursor;


Para ejecutar la función, escribimos:

         EXEC       :R    := FRC_TRAE_AUTORES;


Para ver el resultado de la consulta debemos mostrar el contenido del puntero R. Para esto se utiliza el
comando PRINT, de la siguiente forma:

    PRINT      R;


Otra forma de programar el ejemplo anterior es mediante un procedimiento almacenado en lugar de una
función. En este caso veremos cómo debe recibirse la variable de tipo cursor en forma de parámetro de
entrada/salida (IN OUT) ya que a través de la misma se recibirá/retornará el valor del puntero. Esta
variable debe encontrase previamente declarada en el programa llamador (en este caso el SqlPlus, nuestra
ya declarada variable R).


                         CREATE OR REPLACE PROCEDURE PRC_TRAE_AUTORES
                                (RES IN OUT PKG_REFCURS.REFCURS_AUTORES)
                             AS
                         BEGIN
                                OPEN RES FOR SELECT * FROM AUTORES;
                                        RETURN;
                         END;
                         /


 Para ejecutarlo, se debe llamar al procedimiento almacenado enviando como parámetro la variable R, y
luego mostramos el contenido de la misma con el comando PRINT.

         EXEC PRC_TRAE_AUTORES (:R) ;
         PRINT R;



 Ahora mostraremos un ejemplo de uso del tipo no restrictivo. A esta nueva función le pasaremos como
parámetro una señal que indicará sobre cual tabla queremos que realice la consulta: Autores o Libros.
Al ser la variable Res del tipo Ref Cursor no restrictivo puede apuntar tanto a uno como a otro cursor
indistintamente, dependiendo de la señal A_O_L que le es enviada a la función.


 CREATE OR REPLACE FUNCTION FRC_TRAE_AUTORES_O_LIBROS (A_O_L IN VARCHAR2)
            RETURN PKG_REFCURS.REFCURS
      AS
      -- Declaración de la variable de tipo cursor, usando el tipo no restrictivo
      res PKG_REFCURS.REFCURS;
 BEGIN
            IF (A_O_L = ‘A’) THEN
                         OPEN res FOR
                         SELECT * FROM AUTORES;
            ELSIF (A_O_L = ‘L’) THEN
                         OPEN res FOR
                         SELECT * FROM LIBROS;
      ELSE




                                                                                                     3
Oracle PL/SQL – Variables de tipo Cursor.

                    OPEN res FOR SELECT null FROM DUAL;
               END IF;

               -- Dependiendo de la señal A_O_L , la variable Res apuntará al query de Autores o Libros.
               RETURN res;
        END;
        /


Para ejecutarlo:

         EXEC :R := FRC_TRAE_AUTORES_O_LIBROS('L');

         Ó

         EXEC :R := FRC_TRAE_AUTORES_O_LIBROS('A');


    Y luego:

         PRINT R;



 Dependiendo del parámetro enviado, R nos mostrará el resultado de la selección de la tabla Autores o de
la tabla Libros.

Conveniencia del uso de variables de tipo cursor
 Probablemente a esta altura del artículo el lector se esté preguntando cuáles son las ventajas de trabajar
con este esquema de manipulación de datos.
Ventajas pueden citarse muchas, como ser más potencia y flexibilidad en la programación, mayor
claridad y calidad del código, etc.
Pero sin duda la mayor ventaja es la posibilidad de crear aplicaciones Cliente/Servidor realmente robustas
y que puedan obtener el máximo provecho de todas las capacidades que nos ofrece un motor de base de
datos como Oracle a través de su poderoso lenguaje PL/SQL.
A fin de ejemplificar el concepto, voy a detallar dos características importantes. Existen más y las iremos
viendo en próximas entregas, en la medida que vayamos profundizando en las poderosas herramientas
que nos ofrece Oracle.

Encapsulamiento: Las consultas quedan centralizadas en los procedimientos y funciones almacenadas en
lugar de tenerlas codificadas en la aplicación cliente. La inteligencia, complejidad y métodos para obtener
la información (donden se involucran las “reglas del negocio”) residen de esta manera en el servidor de
datos, ocultando todo detalle de funcionamiento a las aplicaciones clientes.
Como ventaja adicional podemos agregar que se reduce sensiblemente el tráfico en la red al recibir el
programa cliente la información completamente elaborada y lista para mostrar o imprimir.
Y por supuesto, también se simplifica notablemente la tarea de mantenimiento de las aplicaciones.

Seguridad mejorada: Los usuarios sólo necesitan tener permisos de ejecución de los procedimientos y
funciones que realizan las consultas, no siendo necesario que tengan permisos de acceso a las tablas que
estos procedimientos consultan. De esta manera se evitan acc esos indebidos o no deseados a la
información por fuera de las restricciones que imponen nuestras aplicaciones.

Un ejemplo completo de función PL/SQL que devuelve un Ref Cursor
 Como ejemplo final veremos una función que realiza una consulta de libros y autores, con filtros
determinados por los parámetros que recibe. Estos parámetros son el nombre del autor, o parte del mismo,
y una fecha de edición de libros que será tomada como fecha límite base para seleccionar los datos.
Como detalle, y a diferencia de los anteriores ejemplos, podemos ver un manejo básico de errores en el
bloque Exception y una sencilla forma de armar en tiempo de ejecución una cláusula Like agregando




                                                                                                           4
Oracle PL/SQL – Variables de tipo Cursor.
comodines al parámetro recibido.
Finalmente veremos cómo ejecutar esta función desde otro procedimiento PL/SQL y desde un programa
Delphi.


        CREATE OR REPLACE    FUNCTION FRC_TRAE_LIBROS
                                (AUTOR AUTORES.NOMBRE%TYPE,
                 FECHA_DESDE LIBROS.FECHA_EDICION%TYPE)
                       RETURN PKG_REFCURS.REFCURS
         AS
              -- Declaración de variables locales.
                  -- Res es de tipo no restrictivo
                         res PKG_REFCURS.REFCURS;

              -- Esta variable se utiliza para armar la cláusula Like en tiempo
                  -- de ejecución para buscar por nombre de autor.
                  cadena_auxiliar VARCHAR2(150);

         BEGIN    -- Comienzo del bloque de código

              cadena_auxiliar := '%' || RTRIM (LTRIM (AUTOR) ) || '%';

                 OPEN res FOR
                        SELECT A.CODIGO AS CODIGO_LIBRO, A.NOMBRE AS NOMBRE_LIBRO,
                                A.FECHA_EDICION, A.PRESTADO, B.NOMBRE AS NOMBRE_AUTOR,
                 B.FECHA_NACIMIENTO
                        FROM LIBROS A, AUTORES B
                        WHERE A.FECHA_EDICION >= fecha_desde
                                AND A.CODIGO_AUTOR = B.CODIGO_AUTOR
                                AND B.NOMBRE LIKE cadena_auxiliar;

              -- Retorno del puntero al cursor obtenido
                  RETURN res;

          EXCEPTION – Comienzo del bloque de manejo de excepciones
            WHEN OTHERS THEN
                 RAISE_APPLICATION_ERROR
             (-20999, TO_CHAR(SQLCODE) ||
        ': Se produjo un error en la búsqueda de libros - ' ||
        SUBSTR(SQLERRM, 1, 256) );
                   CLOSE res;
               RETURN res;      -- En caso de que se produzca cualquier excepción
                         -- devolvemos un puntero a un cursor cerrado.
          END;
        /


Ejecución y acceso desde un bloque PL/SQL
 El siguiente bloque de código PL/SQL ejecuta la función FRC_TRAE_LIBROS, mostrando el resultado
de la consulta generada y devuelta a través del Ref Cursor que retorna esta función.
Nuestro criterio de selección será obtener los datos de libros del autor Stephen King (de quien sólo
enviaremos la sub-cadena King) y cuya fecha de edición sea posterior al 1-1-1970.
Nota: Antes de ser ejecutado este código, en el SqlPlus debe escribirse: SET SERVEROUTPUT ON. Esta
sentencia activa la funcionalidad del DBMS_OUTPUT.PUT_LINE (que es el equivalente PL/SQL al
writeln de Pascal)

   DECLARE
          -- declaración de un tipo de registro donde guardar cada fila de la consulta
           TYPE tRecRes IS RECORD (
           codigo_libro LIBROS.CODIGO%TYPE,
                  nombre_libro LIBROS.NOMBRE%TYPE,
                  fecha_edicion LIBROS.FECHA_EDICION%TYPE,
           prestado LIBROS.PRESTADO%TYPE,
       nombre_autor AUTORES.NOMBRE%TYPE,
       fecha_nacimiento AUTORES.FECHA_NACIMIENTO%TYPE
           );




                                                                                                 5
Oracle PL/SQL – Variables de tipo Cursor.

    -- Definición de la variable de tipo cursor
        Res PKG_REFCURS.REFCURS;

    -- Definición de la variable de tipo registro
        RecRes tRecRes;

BEGIN
    -- Llamamos a la función que abre la consulta y nos devuelve un puntero a la misma
    -- A esta función le enviamos como parámetros parte del nombre del autor y una fecha
    -- de edición base, con el fin de filtrar la consulta.

                  Res := FRC_TRAE_LIBROS (‘KING’,              TO_DATE(‘01011970’, ‘ddmmyyyy’));

         -- Recorremos el cursor y mostramos todas sus filas por pantalla
         LOOP
                FETCH Res INTO RecRes;
                EXIT WHEN Res%NOTFOUND;
                DBMS_OUTPUT.PUT_LINE (‘Nombre Libro: ’ || RecRes.nombre_libro);
         DBMS_OUTPUT.PUT_LINE (‘Fecha Edición: ’ ||
               TO_CHAR (RecRes.fecha_edicion, ‘dd-mm-yyyy’));
                DBMS_OUTPUT.PUT_LINE (‘Código Libro: ’ ||
               TO_CHAR (RecRes.codigo_libro));
                DBMS_OUTPUT.PUT_LINE (‘Prestado (s/n): ’ || RecRes.prestado);
         DBMS_OUTPUT.PUT_LINE (‘Nombre Autor: ’ || RecRes.nombre_autor);
         DBMS_OUTPUT.PUT_LINE (‘F. Nac. Autor: ’ ||
               TO_CHAR (RecRes.fecha_nacimiento, ‘dd-mm-yyyy’));
                DBMS_OUTPUT.PUT_LINE (‘------------------------------------------------------‘);
         END LOOP;

    END;
/


    Ejecución y acceso desde Delphi
     Desde nuetros programas Delphi podemos ejecutar un procedimiento almacenado o función Oracle de
    las carácterísticas de FRC_TRAE_LIBROS utilizando el componente TStoredProc.
    El resultado de la función, un Ref Cursor, será recibido en un parámetro Result del tipo ftCursor. Un
    importante detalle al que debe prestarse atención es que el TStoredProc debe ser ejecutado utilizando la
    sentencia Open en lugar de ExecSql debido a que precisamente lo que obtenemos como resultado de la
    ejecución es un DataSet, tal como si se tratara de la apertura de un TQuery.
    En los ejemplos que se adjuntan con la revista (el POracle_RC) se incluye tanto el código que sigue a
    continuación como un ejemplo de uso del componente TstoredProc en tiempo de diseño y con salida de la
    información a una grilla.
    El ejemplo está codificado en Delphi 4 C/S y tiene las mismas características de filtrado y presentación de
    la información que el ejemplo inmediato anterior en PL/SQL.

                           Procedure TFOracle_RC.BtnTestClick(Sender: TObject);
                           Var
                             sp: TStoredProc;
                           Begin
                             Try
                               sp := TStoredProc.Create(nil);
                               With sp do
                               Begin
                                DatabaseName := base.DatabaseName; // base es el nombre de un TDatabase
                                StoredProcName := 'FRC_TRAE_LIBROS';
                                Params.clear;
                                Params.CreateParam(ftString, 'AUTOR', ptInput);
                                Params.CreateParam(ftDateTime, 'FECHA_DESDE', ptInput);
                                Params.CreateParam(ftCursor, 'Result', ptResult);
                                ParamByName('AUTOR').AsString := 'KING';
                                ParamByName('FECHA_DESDE').AsDateTime := strtodatetime('01/01/1970');




                                                                                                          6
Oracle PL/SQL – Variables de tipo Cursor.

    Open;            // Usamos OPEN en lugar de ExecProc
                     // y luego tomamos los valores con FieldByName.
     While not EOF do
     Begin
             Showmessage( 'Nombre Libro: ' + Fieldbyname('NOMBRE_LIBRO').AsString + #10#13 +
      'Fecha Edición: ' + FormatDateTime('dd/mm/yyyy',
            Fieldbyname('FECHA_EDICION').AsDateTime) + #10#13 +
                         'Código Libro: ' + Fieldbyname('CODIGO_LIBRO').AsString + #10#13 +
                         'Prestado (s/n): ' + Fieldbyname('PRESTADO').AsString + #10#13 +
                         'Nombre Autor: ' + Fieldbyname('NOMBRE_AUTOR').AsString + #10#13 +
                         'F.Nac. Autor: ' + FormatDateTime('dd/mm/yyyy',
            Fieldbyname('FECHA_NACIMIENTO').AsDateTime));
              Next;
     End;
     Close;
   End;
  Finally
      sp.Free;
  End;
End;




                                                                                               7
Oracle PL/SQL – programación con Paquetes.

Además de brindarnos múltiples elementos que nos permiten desarrollar
una aplicación robusta, Oracle nos ofrece la posibilidad de programar en
forma modular, clara y eficiente.

 Siguiendo con nuestro tutorial sobre elementos avanzados de programación Oracle PL/Sql, en esta
oportunidad veremos cómo embeber procedimientos, funciones, definiciones de tipos de datos y
declaraciones de variables en una misma estructura que los agrupe y relacione lógicamente. Esta
estructura se denomina Package (Paquete) y su uso nos permite no sólo mejorar la calidad de diseño de
nuestras aplicaciones sino también optimizar el desempeño de las mismas.

Packages.
 Como vimos, un Paquete es un objeto PL/Sql que agrupa lógicamente otros objetos PL/Sql relacionados
entre sí, encapsulándolos y convirtiéndolos en una unidad dentro de la base de datos.
Los Paquetes están divididos en 2 partes: especificación (obligatoria) y cuerpo (no obligatoria). La
especificación o encabezado es la interfaz entre el Paquete y las aplicaciones que lo utilizan y es allí
donde se declaran los tipos, variables, constantes, excepciones, cursores, procedimientos y funciones que
podrán ser invocados desde fuera del paquete.
En el cuerpo del paquete se implementa la especificación del mismo.

Entonces, el formato de un Paquete es el siguiente:


           CREATE [OR REPLACE] PACKAGE nombre AS -- especificación (parte visible)
              -- declaración de tipos y variables públicas
              -- especificación de procedimientos y funciones
           END [nombre];

           CREATE [OR REPLACE] PACKAGE BODY nombre AS -- cuerpo (parte oculta)
              -- declaración de tipos y variables privadas
              -- cuerpo de procedimientos y funciones
           [BEGIN
              -- rutinas de ejecución inicial]
           END [nombre];


La especificación contiene las declaraciones públicas, o sea, la declaración de elementos que son visibles
a las aplicaciones que tienen acceso a ese mismo esquema dentro de la base de datos.
El cuerpo contiene los detalles de implementación y declaraciones privadas, manteniéndose todo ésto
oculto a las aplicaciones externas, siguiendo el conocido concepto de “caja negra”.
Dicho de otra manera, sólo las declaraciones hechas en la especificación del paquete son visibles y
accesibles desde fuera del paquete (por otras aplicaciones o procedimientos almacenados) quedando los
detalles de implementación del cuerpo del paquete totalmente ocultos e inaccesibles para el exterior.

Para acceder a los elementos declarados en un paquete basta con anteceder el nombre del objecto a
referenciar con el nombre del paquete donde está declarado y un punto, de esta manera:

Paquete.Objeto


donde Objeto puede ser un tipo, una variable, un cursor, un procedimiento o una función declarados
dentro del paquete.
 Esta notación es válida tanto para referenciar desde procedimientos o triggers externos al paquete como
desde aplicaciones escritas en otros lenguajes o mismo desde herramientas como el Sql*Plus.
Para referenciar objetos desde adentro del mismo paquete donde han sido declarados no es necesario
especificar el nombre del paquete y pueden (deberían) ser referenciados directamente por su nombre.

Finalmente y siguiendo a la parte declarativa del cuerpo de un paquete puede, opcionalmente, incluirse la
sección de inicialización del paquete. En esta sección pueden, por ejemplo, inicializarse variables que



                                                                                                     8
Oracle PL/SQL – programación con Paquetes.
utiliza el paquete tal como veremos en el ejemplo final. La sección de inicialización se ejecuta sólo la
primera vez que una aplicación referencia a un paquete, ésto es, se ejecuta sólo una vez por sesión.

Ventajas del uso de Paquetes.
Dentro de las ventajas que ofrece el uso de paquetes podemos citar que:

 Permite modularizar el diseño de nuestra aplicación
 El uso de Paquetes permite encapsular elementos relacionados entre sí (tipos, variables, procedimientos,
funciones) en un único módulo PL/Sql que llevará un nombre que identifique la funcionalidad del
conjunto.

 Otorga flexibilidad al momento de diseñar la aplicación
 En el momento de diseñar una aplicación todo lo que necesitaremos inicialmente es la información de
interfaz en la especificación del paquete. Puede codificarse y compilarse la especificación sin su cuerpo
para posibilitar que otros sub-programas que referencian a estos elementos declarados puedan compilarse
sin errores. De esta manera podremos armar un “prototipo” de nuestro sistema antes de codificar el detalle
del mismo.

 Permite ocultar los detalles de implementación
 Pueden especificarse cuáles tipos, variables y sub-programas dentro del paquete son públicos (visibles y
accesibles por otras aplicaciones y sub-programas fuera del paquete) o privados (ocultos e inaccesibles
fuera del paquete). Por ejemplo, dentro del paquete pueden existir procedimientos y funciones que serán
invocados por otros programas, así como también otras rutinas de uso interno del paquete que no tendrán
posibilidad de ser accedidas fuera del mismo. Esto asegura que cualquier cambio en la definición de estas
rutinas internas afectará sólo al paquete donde se encuentran, simplificando el mantenim iento y
protegiendo la integridad del conjunto.

 Agrega mayor funcionalidad a nuestro desarrollo
 Las definiciones públicas de tipos, variables y cursores hechas en la especificación de un paquete
persisten a lo largo de una sesión. Por lo tanto pueden ser compartidas por todos los sub-programas y/o
paquetes que se ejecutan en ese entorno durante esa sesión. Por ejemplo, puede utilizarse esta técnica (en
dónde sólo se define una especificación de paquete y no un cuerpo) para mantener tipos y variables
globales a todo el sistema.
 Un ejemplo concreto de ésto fue visto en el número 1 de Mundo Delphi, en el artículo que trata sobre
“Ref Cursores”, donde precisamente utilizamos una especificación de paquete para declarar un tipo REF
CURSOR global a toda la aplicación.

 Introduce mejoras al rendimiento
 En relación a su ejecución, cuando un procedimiento o función que está definido dentro de un paquete es
llamado por primera vez, todo el paquete es ingresado a memoria. Por lo tanto, posteriores llamadas al
mismo u otros sub-programas dentro de ese paquete realizarán un acceso a memoria en lugar de a disco.
Esto no sucede con procedimientos y funciones estándares.

 Permite la “Sobrecarga de funciones” (Overloading).
 PL/Sql nos permite que varios procedimientos o funciones almacenadas, declaradas dentro de un mismo
paquete, tengan el mismo nombre. Esto nos es muy útil cuando necesitamos que los sub-programas
puedan aceptar parámetros que contengan diferentes tipos de datos en diferentes instancias. En este caso
Oracle ejecutará la “versión” de la función o procedimiento cuyo encabezado se corresponda con la lista
de parámetros recibidos.

Un ejemplo.
A continuación veremos un ejemplo de los conceptos explicados anteriormente.
El objetivo de esta tabla, Accesos, es llevar un registro por cada acceso inválido que realice un usuario en
cada sesión, y un histórico total acumulativo a fines de facilitar la lectura de la historia. Supondremos que
la función Alta será invocada por una aplicación que supervisa esta tarea de control.



                                                                                                        9
Oracle PL/SQL – programación con Paquetes.
Alta es la única función pública del paquete.

Veamos la definición de la tabla:

        CREATE TABLE ACCESOS (
           USUARIO                               VARCHAR2(15) NOT NULL,
           FECHA_HORA_INICIO_SESION        DATE NOT NULL,
           TERMINAL_SESION                 VARCHAR2(15) NOT NULL,
           FECHA_HORA_ACCESO               DATE NOT NULL,
           NRO_ACCESO_EN_SESION            NUMBER(10) NOT NULL,
           TOTAL_HISTORICO                 NUMBER(10) NOT NULL
        );

        --
        --   Usuario: Usuario Oracle.
        --   Fecha_Hora_Inicio_Sesion: Momento en que comienza la sesión actual.
        --   Terminal_Sesion: Terminal desde donde se encuentra conectado
        --                    el usuario.
        --   Fecha_Hora_Acceso: Momento en que se produce el acceso inválido.
        --   Nro_Acceso_En_Sesion: Cuenta el número de acceso inválido
        --                         en la sesión actual.
        --   Total_Historico: Cuenta el total de accesos inválidos que se
        --                    registran históricamente para este usuario.


y ahora el Paquete:

         -- Especificación del paquete
         -- Sólo se declara aquí el único elemento que será visible
         -- a las aplicaciones que invoquen este paquete.
         CREATE OR REPLACE PACKAGE PKG_ACCESOS AS
           FUNCTION ALTA RETURN NUMBER;
         END;
         /


         -- Cuerpo del paquete
         CREATE OR REPLACE PACKAGE BODY PKG_ACCESOS AS
           -- Variables globales al paquete, pero inaccesibles desde afuera.
           -- Su valor se asigna en la inicialización del paquete.
           -- (ver al final del paquete, el último BEGIN/END)
           WG_FECHA_INICIO_SESION DATE;
           WG_SECUENCIA NUMBER (10);
           WG_CANT_HIST NUMBER (10);


             -- Función Privada dentro del paquete (sólo uso interno)
             -- Es inaccesible desde afuera porque no está declarada
             -- en la especificación del paquete.
             FUNCTION OBTIENE_CANTIDAD_HISTORICA (USER VARCHAR2)
                       RETURN NUMBER AS
               CANT NUMBER(10);
             BEGIN
                    SELECT COUNT(*) INTO CANT
                            FROM ACCESOS WHERE USUARIO = USER;
                    RETURN CANT+1;
             EXCEPTION
                    WHEN OTHERS THEN RAISE;
             END;


             -- Implementación de la función Alta
             -- (Global, visible desde afuera del paquete)




                                                                                   10
Oracle PL/SQL – programación con Paquetes.

        FUNCTION ALTA RETURN NUMBER AS
        BEGIN
           WG_SECUENCIA := WG_SECUENCIA + 1;
               WG_CANT_HIST := OBTIENE_CANTIDAD_HISTORICA (USER);
           INSERT INTO ACCESOS
              (USUARIO,
                             FECHA_HORA_INICIO_SESION,
                             TERMINAL_SESION,
                             FECHA_HORA_ACCESO,
                             NRO_ACCESO_EN_SESION,
                             TOTAL_HISTORICO)
                         VALUES
              (USER,
                             WG_FECHA_INICIO_SESION,
                             USERENV('TERMINAL'),
                             SYSDATE,
                             WG_SECUENCIA,
                             WG_CANT_HIST);

                RETURN 1;

        EXCEPTION
              WHEN OTHERS THEN RETURN 0;
                         -- si se produce algun error
                         -- retorna cero
        END;


      -- Rutinas de inicialización, se ejecutan sólo la primera
      -- vez que la aplicación referencia al paquete
      BEGIN
        WG_FECHA_INICIO_SESION := SYSDATE;
        WG_SECUENCIA := 0;
      END;
      /

Para ejecutar la función Alta, basta escribir desde el Sql*Plus:

SQL > VAR RES NUMBER;

SQL > EXECUTE :RES := PKG_ACCESOS.ALTA;
SQL> PRINT RES;


y para ver el resultado de la inserción en la tabla:

SQL> SELECT * FROM ACCESOS;


La función devolverá 0 en caso de error o 1 en caso contrario.




                                                                    11
Oracle PL/SQL – Desarrollo de funciones SQL
                                         En esta entrega veremos las reglas que deben
                                           seguir las funciones almacenadas para que
                                                  puedan usarse en una sentencia SQL

     Una de las caractéristicas que hacen sobresalir a Oracle por sobre otros administradores de bases de
datos relacionales es la enorme cantidad de funciones que vienen “de fábrica” para poder usarse
directamente dentro de sentencias Sql. Esta funciones son de conversión de tipos, aritméticas, de manejo
de cadenas de caracteres, de manejo de fechas, etc.
     No obstante, siempre podremos necesitar desarrollar nuestras propias funciones para casos
específicos y poder adicionarlas a la librería preexistente.


Desarrollo de una Función SQL de usuario.
     En principio, una Función SQL de usuario es equivalente a una Función Almacenada, en cuanto a
que está programada en PL/Sql y puede ser invocada mediante la sentencia Exec.
     La diferencia fundamental radica en que una Función SQL puede utilizarse en cualquier sentencia
SQL, ya sea en un Select, un Update, un Insert o un Delete, y tanto en la parte enunciativa de campos
como en la cláusula Where. Entonces, ¿qué es lo que las diferencia? O mejor aún, ¿porqué no podemos
hablar sólo de un tipo de función y usarla según nuestra conveniencia?. La respuesta es que existen
determinadas restricciones para poder usar una Función Almacenada en una sentencia Sql, las que
detallaremos a continuación.


Restricciones
Para ejecutar una sentencia Sql que invoca a una función almacenada, Oracle debe conocer
anticipadamente la posibilidad de que esta función produzca alguna modificación en el estado actual de la
base de datos, para evitar así un resultado inesperado en el retorno de la función.

Para minimizar esta posibilidad, Oracle inhibe a las funciones SQL de:

     •   Modificar datos en tablas, por lo tanto no puede invocar sentencias Insert, Update ni Delete.
     •   Modificar variables globales dentro de un Paquete.
     •   Contener parámetros OUT o IN OUT.
     •   Ser una función de columna o grupo. Las funciones de columna son aquélas que toman como
         entrada toda una columna de datos, como por ejemplo SUM(), o AVG(). Por lo tanto nuestras
         funciones sólo podrán comportarse como funciones de fila.


Un ejemplo
Para nuestro ejemplo desarrollaremos una función que respete las reglas arriba enumeradas. Dentro de un
Paquete PL/Sql crearemos una función de validación de CUIT. Para los que no vivan en la República
Argentina o no estén familiarizados con esta sigla les cuento que CUIT significa Código Unico de
Identificación Tributaria y es utilizado por el organis mo recaudador de impuestos para identificar a las
empresas y profesionales. Este número está compuesto por un prefijo de 2 dígitos, un número de 8
posiciones (que en el caso de las personas es el documento nacional de identidad) y un dígito verificador.
Este dígito verificador se calcula mediante una fórmula de módulo 11.

Adentrémonos ahora en la situación que usaremos en nuestro ejemplo. Tendremos una tabla pecargada
con datos de personas (figura 1), donde consta su CUIT. En dicha carga no se ha utilizado ninguna
validación por lo que deberemos evaluar la validez o no de los datos existentes mediante nuestra función
(figura 2).


                                                                                                 12
Oracle PL/SQL – Desarrollo de funciones SQL
Figura 1 . Tabla de Personas

CREATE TABLE PERSONAS
(
   CODIGO    NUMBER(10)          NOT   NULL,
   NOMBRES   VARCHAR2(50)        NOT   NULL,
   APELLIDOS VARCHAR2(50)        NOT   NULL,
   CUIT      VARCHAR2(11)        NOT   NULL,
   PRIMARY KEY (CODIGO)
);


Figura 2 . Paquete con función de validación

CREATE OR REPLACE PACKAGE Pkg_Valida
AS
   FUNCTION CUIT_VALIDO (XID VARCHAR2) RETURN NUMBER;
END;
/



CREATE OR REPLACE PACKAGE BODY Pkg_Valida
AS
   -- Esta función devuelve 1 en caso de que el
   -- CUIT sea correcto ó 0 en caso contrario
   FUNCTION CUIT_VALIDO (XID VARCHAR2) RETURN NUMBER
   AS
      RES NUMBER;
      I   NUMBER;
      DIG NUMBER;
      NUM NUMBER;
   BEGIN
      -- Primero comprobamos
      -- validaciones sencillas
      IF LENGTH(XID) != 11 OR
         SUBSTR(XID, 1, 2) = '00' THEN
         RETURN 0;
      END IF;

     -- Ahora comprobamos la validez
     -- del dígito verificador
     -- usándo cálculo de módulo 11
     RES := 0;
     FOR I IN 1..10 LOOP
         NUM := TO_NUMBER(SUBSTR(XID, I, 1));
         IF I = 1 OR I = 7 THEN RES := RES + NUM   * 5;
         ELSIF I = 2 OR I = 8 THEN RES := RES +    NUM * 4;
         ELSIF I = 3 OR I = 9 THEN RES := RES +    NUM * 3;
         ELSIF I = 4 OR I = 10 THEN RES := RES +   NUM * 2;
         ELSIF I = 5 THEN RES := RES + NUM * 7;
         ELSIF I = 6 THEN RES := RES + NUM * 6;
         END IF;
     END LOOP;
     DIG := 11 - MOD(RES, 11);
     IF DIG = 11 THEN DIG := 0; END IF;

     -- Ahora retornamos valor según
     -- validez o no del dígito verificador
     IF DIG = TO_NUMBER(SUBSTR(XID, 11, 1)) THEN
          RETURN 1;
     ELSE
          RETURN 0;
     END IF;

   EXCEPTION
     -- En caso de error retornamos 0
     WHEN OTHERS THEN
         RETURN 0;
   END;
END;
/




Lo primero que haremos será chequear el correcto funcionamiento de la función de validación que hemos
creado, que nos devolverá 1 en caso de recibir un número correcto ó 0 en caso contrario. Para chequearla
la ejecutaremos en la forma estándar, es decir, mediante la instrucción Exec. (figura 3)
Vemos que funciona correctamente, ya que le hemos enviado un número válido y nos devuelve un 1.


Figura 3 . Ejecución de la función mediante Exec
SQL> VAR VALIDO NUMBER;
SQL> EXEC :VALIDO := Pkg_Valida.cuit_valido('20224367911');

PL/SQL PROCEDURE successfully completed.

SQL> PRINT VALIDO;

   VALIDO
---------
        1

SQL>




Ahora la ejecutaremos cómo una Función SQL a través de un Select. (figura 4)


Figura 4 . Ejecución de la función como Función SQL dentro de un Select
SQL> SELECT Pkg_Valida.cuit_valido('20224367911')
SQL> AS VALIDO FROM DUAL;

SQL> ORA-06571: function cuit_valido does not guarantee not
     to update database

SQL>




En contra de lo esperado, se ha producido un error. El servidor Oracle nos informa que no puede ejecutar
la función dentro de un SQL porque la misma no garantiza que la base de datos no será modificada.
Pero si analizamos el código de nuestra función, veremos que la misma no viola ninguna de las
restricciones conocidas. Entonces, ¿qué pasó?


La cláusula "Pragma Restrict_References"
Oracle chequea el código de la función buscando posibles causas de conflicto. Para funciones que
encierran cierta complejidad, el Servidor no consigue tener certeza sobre los potenciales “riesgos” de
ejecutarla, y por ende nos envía el mensaje de error que acabamos de ver para informarnos de tal
situación. Entonces nos vemos obligados a forzar al Compilador PL/Sql a chequear las reglas, a fin de que
el Servidor no deba hacerlo al ejecutar la función. En este paso, será el Compilador el que nos acepte o
rechace la función al momento de compilarla.
Pero como ésto sólo puede hacerse dentro del encabezado de un Paquete y no es posible hacerlo para
funciones almacenadas desarrolladas fuera de paquetes, éstas últimas no podrán usarse en sentencias Sql
en caso de que el Servidor no las acepte. Por lo tanto la recomendación es que siempre deberemos usar
Paquetes donde incluir este tipo de funciones.


                                                                                                14
Oracle PL/SQL – Desarrollo de funciones SQL
La clásula Pragma Restrict_References debe ir en el encabezado del Paquete, y siguiendo a la definición
de cada función, siendo la sintaxis la desplegada en la figura 5. En la figura 6 mostramos el significado de
cada argumento de este Pragma.




Figura 5 . Sintaxis de Pragma Restrict_References
PRAGMA RESTRICT_REFERENCES (
    Nombre_de_función, WNDS [, WNPS] [, RNDS] [, RNPS]);




Figura 6 . Significado de cada argumento
WNDS
                         No modifica tablas en la base de datos
Writes no database state

RNDS
                                 No lee tablas en la base de datos
Reads no database state

WNPS                             No cambia valores de variables globales
Writes no package state          de Paquetes


RNPS                             No referencia valores de variables
Reads no package state           globales de Paquetes




El único argumento obligatorio es WNDS, pero siempre es aconsejable definir el máximo nivel de
“pureza” que nuestra función posee, a fin de que el Compilador PL/Sql no rechace innecesariamente a
nuestra función.


Adaptación de nuestro ejemplo
Habiendo visto lo anterior, sabemos ahora que tenemos que agregar este Pragma a nuestro encabezado
de Paquete. Veremos el nuevo encabezado en la figura 7. Introduciremos todos los argumentos, ya que
sabemos que nuestra función los cumple, y de esta manera garantizaremos que el Compilador la acepte.
Luego de introducidos los cambios deberemos recompilar nuevamente el encabezado y el cuerpo del
paquete.

Figura 7 . Nuevo encabezado del Paquete
CREATE OR REPLACE PACKAGE Pkg_Valida
AS
   FUNCTION CUIT_VALIDO (XID VARCHAR2) RETURN NUMBER;
   -- Agregamos aquí el PRAGMA
   PRAGMA RESTRICT_REFERENCES (CUIT_VALIDO, WNDS, WNPS, RNPS);
END;
/




Ejemplos de uso.
Finalmente podremos utilizar nuestra Función Almacenada como una Función SQL. En la figura 8
volveremos a nuestro ejemplo de ejecución simple mediante un Select, pero esta vez vemos que
afortunadamente todo termina bien.
                                                                                     15
Oracle PL/SQL – Desarrollo de funciones SQL
Figura 8 . Ejecución exitosa de la función como función SQL
           dentro de un Select
SQL> SELECT Pkg_Valida.cuit_valido('20224367911')
SQL> AS VALIDO FROM DUAL;

   VALIDO
---------
        1

SQL>




Y por último 2 ejemplos más a fin de ilustrar los posibles usos de nuestra función en un contexto como el
planteado, en donde debemos analizar o mostrar el contenido de una tabla cuyos datos no están
validados.


Figura 9 . Mostramos Código de persona, CUIT y Estado
           calculado del CUIT (válido o inválido)

SQL> SELECT CODIGO, NOMBRES, CUIT,
SQL> DECODE (Pkg_Valida.cuit_valido(CUIT), 1, 'VALIDO',
SQL>                                      0, 'INVALIDO')
SQL> AS ESTADO
SQL> FROM PERSONAS
SQL> ORDER BY CODIGO;


   CODIGO     NOMBRES       CUIT             ESTADO
---------     ----------    -----------      --------
        1     ADAN          11111111111      INVALIDO
        2     EVA           20224367911      VALIDO

SQL>




Figura 10 . Calculamos cuántos CUIT inválidos existen en la tabla


SQL> SELECT COUNT(*) AS INVALIDOS FROM PERSONAS
SQL>   WHERE Pkg_Valida.cuit_valido(CUIT) = 0;


INVALIDOS
---------
        1

SQL>




                                                                                                16
Oracle PL/SQL – SQL dinámico

                                              Una herramienta que nos permite añadir
                                        potencia y versatilidad a nuestras aplicaciones
                                                                    de bases de datos.

     De por sí, Oracle no soporta en forma nativa el uso de operaciones dinámicas en un procedimiento o
función almacenada. Al decir “operaciones dinámicas” me refiero a todo aquéllo que deba ser resuelto
en tiempo de ejecución en lugar de en tiempo de compilación. Por ejemplo, si quisiéramos crear una tabla
dentro de un procedimiento almacenado, no podríamos hacerlo mediante el uso de Pl/Sql estándard.
Veamos primero porqué existe esta limitación y luego cómo Oracle nos permite resolver las cosas para
poder llevar a cabo este tipo de tareas.



Modelo estático – Eficiencia versus flexibilidad
Como ocurre en la mayoría de los lenguajes de programación, en Oracle, antes de que un procedimiento
Pl/Sql pueda ser ejecutado, debe ser compilado. El compilador resuelve toda referencia a objetos del
esquema (tablas, funciones, procedimientos, etc.), buscando sus definiciones en el diccionario de datos de
la base, y asignándoles direcciones a fin de que puedan ser referenciados al momento de ejecutarse el
programa.
Este esquema de resolución de referencias al momento de la compilación, es conocido como modelo
estático y es el que prioriza la eficiencia en la ejecución del programa. Como resultado de la compilación,
Oracle genera un código del tipo p-code que es luego directamente procesado por el entorno Pl/Sql.

Por otro lado, un modelo dinámico sería aquél en donde se efectuarían las resoluciones en tiempo de
ejecución, priorizándose de esta manera la flexibilidad de los procesos que pueden desarrollarse dado que
los objetos del esquema pueden no conocerse o no existir hasta el momento de la ejecución de la línea de
código que los referencia.

Dado que el modelo elegido por Oracle es el estático, donde deben existir al momento de la compilación
todos los objetos que se refencian dentro del programa, es que no podemos, como dijimos, crear una tabla
dentro de un procedimiento almacenado usando las sentencias estándares. Esto parece limitante, y lo es,
pero siempre tenemos una forma de hacer las cosas cuando trabajamos con herramientas que han sido
concebidas para darnos satisfacciones en lugar de hacernos pasar penurias. (... apostaría a que estamos
pensando en la misma empresa generadora de penurias ... )


El paquete DBMS_SQL
La forma en la que podemos agregar dinamismo a nuestros procedimientos y funciones almacenadas, es
mediante el uso del paquete SYS.DBMS_SQL.
A través de las funciones implementadas en el mismo podremos realizar operaciones que involucren la
referencia a objetos que no existen o que no podemos determinar al momento de la compilación.
El “truco” consiste en que estas instrucciones no las escribamos como código Pl/Sql sino como “cadenas
de caracteres”, que podrán ser tanto recibidas como parámetros del procedimiento, o ser definidas o
armadas por el mismo, y posteriormente enviadas a las funciones del paquete Dbms_Sql para su efectivo
proceso.
Mediante esta técnica podremos implementar el ejemplo del que hablamos al comienzo: Crear una tabla
dentro de una procedimiento almacenado (“Listado 1”)

El procedimiento utilizado no tiene nada de complejo. Usando las funciones definidas dentro del paquete
Dbms_Sql creamos y abrimos un cursor especial que se usará en la ejecución, armamos la instrucción y la
enviamos para su interpretación y proceso. Luego, cerramos el cursor.
Algo muy parecido a lo que hacemos en Delphi cuando utilizamos TQuery.ExecSql

Nota: El indicador dbms_sql.v7 es obligatorio y debe usarse en las versiones 7 y 8 de Oracle.




                                                                                                        17
Oracle PL/SQL – SQL dinámico

CREATE OR REPLACE PROCEDURE DBMS_EJEMPLO
AS
   ID INTEGER;
   TABLA VARCHAR2(20);
   CAMPOS VARCHAR2(100);
BEGIN
   TABLA := ‘PRUEBA ’;
   CAMPOS := ‘ (C1 NUMBER(10), C2 VARCHAR2(10)) ’;
   ID := DBMS_SQL.OPEN_CURSOR;

   DBMS_SQL.PARSE(ID, 'CREATE TABLE ' ||
                       TABLA ||
                       CAMPOS, dbms_sql.v7);
   DBMS_SQL.CLOSE_CURSOR(ID);
END;
/


Listado 1 . Un ejemplo simple.


Un ejemplo un poco más elaborado
En el ejemplo del Listado 1, vimos de forma simple cómo procesar una instrucción dinámica dentro de la
base de datos.
Ahora veamos cómo sacarle más provecho a las bondades que nos ofrece esta técnica.


Las famosas reglas del negocio
Como bien sabemos, en una aplicación Cliente/Servidor una de las características más importantes es la
implementación de todas las definiciones que hagan al negocio (sean las de un banco, una fábrica o un
local de venta de ropa) dentro de la base de datos y no en la codificación del programa cliente. Estas
reglas normalmente se refieren a validaciones y restricciones cuya lógica, al estar centralizada en el
servidor de datos, controlará tanto a las transacciones realizadas por nuestras aplicaciones como a las
llevadas a cabo fuera de ellas (por ejemplo mediante herramientas que nos permitan el acceso directo a
los datos).

Para nuestro ejemplo, nos ubicaremos en el siguiente cuadro de situación:

Tenemos una pequeña empresa que se dedica al ensamblado de computadoras (ordenadores, para mis
amigos españoles). A cada parte que participa en el equipo que finalmente resulta armado la llamaremos
“componente”, siendo a la vez cada componente de un “tipo” específico. Es así que podemos tener
componentes del tipo “teclado”, “monitor”, “chip”, “memoria”, etc.
Imaginemos que dependiendo del modelo de computadora, el tipo de componente a utilizar puede variar,
por ejemplo habrá modelos con monitores de 14 pulgadas y 32 MB de RAM, otros con monitores de 17
pulgadas y 64 MB de memoria, y así.
Bien, lo que la empresa ideó a fin de tener un mejor control de su stock, es una codificación de los
componentes de manera tal que el código del mismo no sea arbitrario sino que esté regido por una regla
definida para el tipo al que pertenece el componente. De esta manera no se podrá codificar erróneamente
un componente, evitándose así errores en su clasificación.
Dado que esta definición es una regla del negocio, la implementaremos en la base de datos. En el Listado
2 veremos las definiciones de las tablas de Tipos_Componente y Componentes.



CREATE TABLE TIPOS_COMPONENTE (
   TIPO                NUMBER(10)   NOT NULL,
   DESCRIPCION         VARCHAR2(50) NOT NULL,
   FUNCION_VALIDACION VARCHAR2(30),
   PRIMARY KEY (TIPO)
);


CREATE TABLE COMPONENTES (
   CODIGO        VARCHAR2(10) NOT NULL,
   TIPO          NUMBER(10)   NOT NULL,




                                                                                                     18
Oracle PL/SQL – SQL dinámico
      DESCRIPCION   VARCHAR2(50) NOT NULL,
      PRIMARY KEY (CODIGO),
      FOREIGN KEY (TIPO)
           REFERENCES TIPOS_COMPONENTE
);


Listado 2 . Definiciones de tablas.

Como vemos, las tablas están relacionadas por el tipo de componente tal lo esperado. Pero prestemos
atención a la tabla Tipos_Componente. En ella encontramos un campo, Funcion_Validacion, que es el que
guardará la información acerca de cuál función almacenada validará a los códigos de componentes
ingresados en la tabla Componentes. Porque, precisamente, nuestra idea es desarrollar una función que
implemente las reglas de validación para uno o más tipos de componentes, de manera que simplemente
tengamos que relacionar Función de validación con Tipo de Componente a través de este campo de la
tabla. Y luego, la validación se lanzará automáticamente a través de un Trigger que ejecutará a la función
correspondiente en cada momento que se ingrese o modifique un registro en la tabla Componentes.
En el Listado 3 se muestra un ejemplo de datos ingresados en la tabla Tipos_Componente.



INSERT INTO TIPOS_COMPONENTE
       (TIPO, DESCRIPCION, FUNCION_VALIDACION)
VALUES (1, 'MONITOR', 'MONITOR_VALIDO');


INSERT INTO TIPOS_COMPONENTE
       (TIPO, DESCRIPCION, FUNCION_VALIDACION)
VALUES (2, 'CHIP', 'CHIP_VALIDO');


INSERT INTO TIPOS_COMPONENTE
       (TIPO, DESCRIPCION, FUNCION_VALIDACION)
VALUES (3, 'OTRO', NULL);


COMMIT;

Listado 3 . Tipos de componente y funciones asociadas.

Ahora necesitaremos programar las funciones que realizarán la validación para cada tipo. Según lo
especificado en la tabla, al tipo “monitor” lo validará siempre la función “Monitor_Válido”, al tipo “chip”
la función “Chip_Válido” y el tipo “otro” no será validado. En el Listado 4 crearemos un paquete con las
funciones especificadas. Se han incluído los Pragma necesarios para que estas funciones puedan ser
invocadas como Funciones Sql (tal lo explicado en Mundo Delphi Nº 4, Oracle Pl/Sql - Desarrollo de
funciones SQL).
Para nuestro ejemplo, las funciones de validación serán muy sencillas: verificarán que el tamaño del
código de componente ingresado en la tabla Componentes sea igual a 10, que las 3 primeras posiciones
sean letras (“MON” para el caso de los monitores y “CHI” para el caso de los chips ... bueno, intuyo que
nadie va a sorprenderse por lo ingenioso de ésto, ¿verdad?), y que las siguientes 7 posiciones sean
numéricas y resulten en un valor superior a cero.
Las funciones devolverán 0 en caso de error o 1 en caso contrario.



CREATE OR REPLACE
       PACKAGE PKG_VALIDACIONES_TIPOS
AS
  FUNCTION MONITOR_VALIDO
      (XCODIGO COMPONENTES.CODIGO%Type)
     RETURN NUMBER;
     PRAGMA RESTRICT_REFERENCES
            (MONITOR_VALIDO, WNDS, WNPS, RNPS);


     FUNCTION CHIP_VALIDO
      (XCODIGO COMPONENTES.CODIGO%Type)
     RETURN NUMBER;
     PRAGMA RESTRICT_REFERENCES



                                                                                                        19
Oracle PL/SQL – SQL dinámico
             (CHIP_VALIDO, WNDS, WNPS, RNPS);

END;
/


CREATE OR REPLACE
          PACKAGE BODY PKG_VALIDACIONES_TIPOS
AS
FUNCTION MONITOR_VALIDO
     (XCODIGO COMPONENTES.CODIGO%Type)
       RETURN NUMBER
AS
BEGIN
     IF LENGTH(XCODIGO) != 10 OR
            SUBSTR(XCODIGO, 1, 3) != 'MON' OR
            TO_NUMBER(SUBSTR(XCODIGO, 4, 7)) <= 0 THEN
        RETURN 0;
     ELSE
        RETURN 1;
     END IF;
EXCEPTION
     WHEN OTHERS THEN
          RETURN 0;
END;


FUNCTION CHIP_VALIDO
     (XCODIGO COMPONENTES.CODIGO%Type)
       RETURN NUMBER
AS
BEGIN
     IF LENGTH(XCODIGO) != 10 OR
           SUBSTR(XCODIGO, 1, 3) != 'CHI' OR
           TO_NUMBER(SUBSTR(XCODIGO, 4, 7)) <= 0 THEN
        RETURN 0;
     ELSE
        RETURN 1;
     END IF;
EXCEPTION
     WHEN OTHERS THEN
          RETURN 0;
END;

END;
/

Listado 4 . Paquetes con funciones de validación.
Antes dijimos que habrá un Trigger que se encargue de la tarea de verificar, por cada registro actualizado,
la validez de su código. Ya mismo podríamos implementarlo poniendo una serie de “If Tipo = ... Then
Función ” dentro del Trigger y asunto terminado. Sí, pero nada elegante, sobre todo si hay cientos de
tipos diferentes para validar y la cosa iría peor cada vez que agreguemos tipos nuevos, o querramos que
sea otra la función que valide a un determinado tipo. Al final de cuentas, ¡ya sabemos que podemos
resolverlo con Sql dinámico!.
Vayamos al Listado 5. Allí encontraremos e núcleo de todo este asunto. Se trata de una función de
                                                l
validación de tipos de carácter “general”, que dependiendo del tipo de componente recibido como
parámetro ejecutará la función que corresponda según lo definido en la tabla Tipos_Componente. Con
esta información, nuestra función central armará dinámicamente el nombre del proceso que debe ejecutar,
le agregará como parámetro el código de componente a validar, ejecutará la función, tomará el valor
resultante y lo devolverá al proceso llamador.


CREATE OR REPLACE
    FUNCTION ES_COMPONENTE_VALIDO
       (XTIPO TIPOS_COMPONENTE.TIPO%Type,
        XCODIGO COMPONENTES.CODIGO%Type)
     RETURN NUMBER
AS
        C INTEGER;
        TMP INTEGER;
        nRES NUMBER;
        FX TIPOS_COMPONENTE.FUNCION_VALIDACION%Type;
BEGIN




                                                                                                        20
Oracle PL/SQL – SQL dinámico

    -- obtenemos el nombre de la función que
    -- debemos invocar a fin de
    -- validar el tipo de componente
    SELECT FUNCION_VALIDACION INTO FX
        FROM TIPOS_COMPONENTE
    WHERE TIPO = XTIPO;

   --  Si no la encontramos o es Null
   --  entendemos que no está definida
   --  y salimos
   IF  FX IS NULL THEN
        RAISE NO_DATA_FOUND;
    END IF;

    --   armamos todo el esquema dinámico
    --   para ejecutar la función
    --   pretendida a través de un select
    --   y obtener el resultado de la misma

   -- Abrimos un cursor y tomamos
   -- su manejador (número de identificación)
   C := DBMS_SQL.OPEN_CURSOR;

    -- Armamos la instrucción dinámica a
    -- ejecutar, concatenando el nombre
    -- de, en este caso, la función Sql
    -- que será ejecutada y declarando el
    -- parámetro que le enviaremos.
    -- Y todo ésto se asocia al cursor.
    DBMS_SQL.PARSE (C,
          'SELECT PKG_VALIDACIONES_TIPOS.' ||
           FX || '(:VCODIGO)
           FROM DUAL', DBMS_SQL.V7);

    -- Llenamos el parámetro que enviamos
    -- (el código del artículo/componente)
    DBMS_SQL.BIND_VARIABLE(C, ':VCODIGO', XCODIGO);

    -- Definimos la columna que recibiremos
    -- del Select.
    -- Declaramos que el tipo de dato de la
    -- columna “1” deberá ser del mismo tipo
    -- de dato que la variable nRes
    -- (o sea, numérico)
    DBMS_SQL.DEFINE_COLUMN(C, 1, nRES);

    -- Ejecutamos el cursor y tomamos
    -- la fila que recibimos.
    -- El “TRUE” significa que si se recibe más
    -- de una fila se dispare una excepción.
    TMP := DBMS_SQL.EXECUTE_AND_FETCH(C, TRUE);

    -- Tomamos el valor de la columna “1”
    -- (el resultado de la función ejecutada)
    -- y lo metemos en la variable nRes
    DBMS_SQL.COLUMN_VALUE(C, 1, nRES);

    -- Cerramos el cursor ya que
    -- si no lo hacemos no se desasigna la
    -- memoria utilizada.
    DBMS_SQL.CLOSE_CURSOR(C);

    -- Devolvemos el resultado
    RETURN nRES;

EXCEPTION
     WHEN OTHERS THEN
        -- si hubo una excepción cerramos
        -- el cursor antes de salir
         IF DBMS_SQL.IS_OPEN(C) THEN
            DBMS_SQL.CLOSE_CURSOR(C);
         END IF;
         RETURN 1;
END;
/




                                                      21
Oracle PL/SQL – SQL dinámico

Listado 5 . Función central – Uso de Sql dinámico.

Ya tenemos casi todo listo, sólo nos falta el Trigger. (Listado 6)




 CREATE OR REPLACE TRIGGER TBUI_COMPONENTES
   BEFORE UPDATE OR INSERT
   ON COMPONENTES
   FOR EACH ROW
 DECLARE
   eCODIGO_INVALIDO EXCEPTION;
 BEGIN


   -- enviamos a validar el código,
   -- para ésto llamamos a la función
   -- genérica, enviando los datos del
   -- registro
   IF ES_COMPONENTE_VALIDO
        (:NEW.TIPO,:NEW.CODIGO) = 0 THEN
     RAISE eCODIGO_INVALIDO;
   END IF;

 EXCEPTION
   WHEN eCODIGO_INVALIDO THEN
      RAISE_APPLICATION_ERROR(-20999,
        CODIGO INVALIDO PARA EL TIPO DE COMPONENTE');
      RETURN;
   WHEN OTHERS THEN
      RAISE_APPLICATION_ERROR(-20999,
            'ERROR: ' ||
               TO_CHAR(SQLCODE) || ' - ' ||
                  SUBSTR(SQLERRM, 1, 256));
      RETURN;
 END;
 /




Listado 6 . Trigger sobre tabla Componentes

Ahora, cada vez que se inserte o actualice un registro de la tabla Componentes se disparará
automáticamente el Trigger TBUI_Componentes (las siglas “TBUI” las uso por trigger before update
insert). Luego, el Trigger invocará a la función genérica Es_Componente_Valido, enviándole el tipo y
componente ingresados sobre la fila tratada. En caso de recibir de la función el valor 0 (error), nos enviará
una excepción alertándonos sobre tal situación, impidiéndose así que la fila se grabe en la tabla.

Finalmente, en la Figura 7 podremos ver un ejemplo de lo que pasaría si intentamos ingresar un código de
componente no válido para el tipo especificado (empieza con “SON” en lugar de “MON”).
Utilizaremos para la prueba el SqlPlus.



 SQL> INSERT INTO COMPONENTES (CODIGO, TIPO, DESCRIPCION)
 SQL>         VALUES ('SON0000000', 1, 'SVGA 17"');


 ERROR at line 1:
 ORA-20999: CODIGO INVALIDO PARA EL TIPO DE COMPONENTE
 ORA-06512: at line 10
 ORA-04088: error during execution of trigger ‘TBUI_COMPONENTES'




Figura 7 . Intento de inserción fallido.




                                                                                                          22

Mais conteúdo relacionado

Mais procurados

Oracle pl sql
Oracle pl sqlOracle pl sql
Oracle pl sqlclaudia_m
 
Mejoras en T-SQL para SQL Server 2005
Mejoras en T-SQL para SQL Server 2005Mejoras en T-SQL para SQL Server 2005
Mejoras en T-SQL para SQL Server 2005pabloesp
 
Sesion08 - Cursores (Oracle)
Sesion08 - Cursores (Oracle)Sesion08 - Cursores (Oracle)
Sesion08 - Cursores (Oracle)José Toro
 
Sesión11 - Paquetes (Oracle)
Sesión11 - Paquetes (Oracle)Sesión11 - Paquetes (Oracle)
Sesión11 - Paquetes (Oracle)José Toro
 
Sesion05 - Manipulacion de datos (Oracle)
Sesion05 - Manipulacion de datos (Oracle)Sesion05 - Manipulacion de datos (Oracle)
Sesion05 - Manipulacion de datos (Oracle)José Toro
 
BD_L8_EXP_ROZIC_CAP9_SQL
BD_L8_EXP_ROZIC_CAP9_SQLBD_L8_EXP_ROZIC_CAP9_SQL
BD_L8_EXP_ROZIC_CAP9_SQLdemoiselle
 
Sentencias Sql
Sentencias SqlSentencias Sql
Sentencias Sqlfer951
 
PresentacióN 4
PresentacióN 4PresentacióN 4
PresentacióN 4pokerpc
 
Compiladores1
Compiladores1Compiladores1
Compiladores1naye_142
 
Introducción a Sql
Introducción a SqlIntroducción a Sql
Introducción a Sqlalexmerono
 
Comandos del-ddl-y-del-dml-liz
Comandos del-ddl-y-del-dml-lizComandos del-ddl-y-del-dml-liz
Comandos del-ddl-y-del-dml-lizBolivar Castillo
 

Mais procurados (18)

Oracle pl sql
Oracle pl sqlOracle pl sql
Oracle pl sql
 
Cap I Plsql
Cap I PlsqlCap I Plsql
Cap I Plsql
 
PL/SQL
PL/SQLPL/SQL
PL/SQL
 
Mejoras en T-SQL para SQL Server 2005
Mejoras en T-SQL para SQL Server 2005Mejoras en T-SQL para SQL Server 2005
Mejoras en T-SQL para SQL Server 2005
 
minas
minas minas
minas
 
8448148681
84481486818448148681
8448148681
 
Plsql y paquetes
Plsql y paquetesPlsql y paquetes
Plsql y paquetes
 
Sesion08 - Cursores (Oracle)
Sesion08 - Cursores (Oracle)Sesion08 - Cursores (Oracle)
Sesion08 - Cursores (Oracle)
 
Sesión11 - Paquetes (Oracle)
Sesión11 - Paquetes (Oracle)Sesión11 - Paquetes (Oracle)
Sesión11 - Paquetes (Oracle)
 
Sesion05 - Manipulacion de datos (Oracle)
Sesion05 - Manipulacion de datos (Oracle)Sesion05 - Manipulacion de datos (Oracle)
Sesion05 - Manipulacion de datos (Oracle)
 
BD_L8_EXP_ROZIC_CAP9_SQL
BD_L8_EXP_ROZIC_CAP9_SQLBD_L8_EXP_ROZIC_CAP9_SQL
BD_L8_EXP_ROZIC_CAP9_SQL
 
Sentencias Sql
Sentencias SqlSentencias Sql
Sentencias Sql
 
Funciones en C++
Funciones en C++Funciones en C++
Funciones en C++
 
PresentacióN 4
PresentacióN 4PresentacióN 4
PresentacióN 4
 
Sql 2010
Sql 2010Sql 2010
Sql 2010
 
Compiladores1
Compiladores1Compiladores1
Compiladores1
 
Introducción a Sql
Introducción a SqlIntroducción a Sql
Introducción a Sql
 
Comandos del-ddl-y-del-dml-liz
Comandos del-ddl-y-del-dml-lizComandos del-ddl-y-del-dml-liz
Comandos del-ddl-y-del-dml-liz
 

Semelhante a 44777047 oracle

Conceptos basicos de programacion con pl sql
Conceptos basicos de programacion con pl sqlConceptos basicos de programacion con pl sql
Conceptos basicos de programacion con pl sqlAndrei Hortúa
 
3. introducción a sql 2007
3. introducción a sql 20073. introducción a sql 2007
3. introducción a sql 2007angeliica68
 
MAGIC PL_SQL ORACLE_ Ejemplos de Colecciones.pdf
MAGIC PL_SQL ORACLE_ Ejemplos de Colecciones.pdfMAGIC PL_SQL ORACLE_ Ejemplos de Colecciones.pdf
MAGIC PL_SQL ORACLE_ Ejemplos de Colecciones.pdfLic. Williams Ramos
 
Introduccion A Php
Introduccion A PhpIntroduccion A Php
Introduccion A Phputs
 
Introduccion A Php
Introduccion A PhpIntroduccion A Php
Introduccion A Phputs
 
Introduccion A Php
Introduccion A PhpIntroduccion A Php
Introduccion A Phputs
 
Programacion en java
Programacion en javaProgramacion en java
Programacion en javaANGELA FREIRE
 
Lenguaje estructurado sql
Lenguaje estructurado sqlLenguaje estructurado sql
Lenguaje estructurado sqlDiego Sánchez
 
13 Guía_Fundamentos de Base de Datos.docx
13 Guía_Fundamentos de Base de Datos.docx13 Guía_Fundamentos de Base de Datos.docx
13 Guía_Fundamentos de Base de Datos.docxLeydyVeronicaDelgado
 
13 Guía_Fundamentos de Base de Datos (1).docx
13 Guía_Fundamentos de Base de Datos (1).docx13 Guía_Fundamentos de Base de Datos (1).docx
13 Guía_Fundamentos de Base de Datos (1).docxLeydyVeronicaDelgado
 
Base de datos Objeto-Relacional.
Base de datos Objeto-Relacional.Base de datos Objeto-Relacional.
Base de datos Objeto-Relacional.Juan Raul Vergara
 
Transact sql
Transact sqlTransact sql
Transact sqljoan
 
U8- BBDD - El lenguaje PLSQL operadores y estructuras de control.pdf
U8- BBDD - El lenguaje PLSQL operadores y estructuras de control.pdfU8- BBDD - El lenguaje PLSQL operadores y estructuras de control.pdf
U8- BBDD - El lenguaje PLSQL operadores y estructuras de control.pdfayoubbenjaddi5
 

Semelhante a 44777047 oracle (20)

Conceptos basicos de programacion con pl sql
Conceptos basicos de programacion con pl sqlConceptos basicos de programacion con pl sql
Conceptos basicos de programacion con pl sql
 
PLSQL y paquetes
PLSQL y paquetesPLSQL y paquetes
PLSQL y paquetes
 
02 sql reference
02   sql reference02   sql reference
02 sql reference
 
Programacion C#
Programacion C#Programacion C#
Programacion C#
 
3. introducción a sql 2007
3. introducción a sql 20073. introducción a sql 2007
3. introducción a sql 2007
 
MAGIC PL_SQL ORACLE_ Ejemplos de Colecciones.pdf
MAGIC PL_SQL ORACLE_ Ejemplos de Colecciones.pdfMAGIC PL_SQL ORACLE_ Ejemplos de Colecciones.pdf
MAGIC PL_SQL ORACLE_ Ejemplos de Colecciones.pdf
 
Introduccion A Php
Introduccion A PhpIntroduccion A Php
Introduccion A Php
 
Introduccion A Php
Introduccion A PhpIntroduccion A Php
Introduccion A Php
 
Introduccion A Php
Introduccion A PhpIntroduccion A Php
Introduccion A Php
 
Sql
SqlSql
Sql
 
Programacion en java
Programacion en javaProgramacion en java
Programacion en java
 
Lenguaje estructurado sql
Lenguaje estructurado sqlLenguaje estructurado sql
Lenguaje estructurado sql
 
Capitulo 2
Capitulo 2Capitulo 2
Capitulo 2
 
Pl
PlPl
Pl
 
13 Guía_Fundamentos de Base de Datos.docx
13 Guía_Fundamentos de Base de Datos.docx13 Guía_Fundamentos de Base de Datos.docx
13 Guía_Fundamentos de Base de Datos.docx
 
13 Guía_Fundamentos de Base de Datos (1).docx
13 Guía_Fundamentos de Base de Datos (1).docx13 Guía_Fundamentos de Base de Datos (1).docx
13 Guía_Fundamentos de Base de Datos (1).docx
 
SELECT BASICO _
SELECT BASICO _  SELECT BASICO _
SELECT BASICO _
 
Base de datos Objeto-Relacional.
Base de datos Objeto-Relacional.Base de datos Objeto-Relacional.
Base de datos Objeto-Relacional.
 
Transact sql
Transact sqlTransact sql
Transact sql
 
U8- BBDD - El lenguaje PLSQL operadores y estructuras de control.pdf
U8- BBDD - El lenguaje PLSQL operadores y estructuras de control.pdfU8- BBDD - El lenguaje PLSQL operadores y estructuras de control.pdf
U8- BBDD - El lenguaje PLSQL operadores y estructuras de control.pdf
 

44777047 oracle

  • 1. Oracle PL/SQL Autor: Eduardo Bottini ebottini@c-s-i.com.ar Prohibida la reproducción total o parcial sin permiso explícito del autor.
  • 2. Oracle PL/SQL – Variables de tipo Cursor. Oracle nos permite utilizar dentro de sus procedimientos almacenados variables que referencian a cursores de datos. Estas variables son punteros y llevan el nombre de Ref Cursor. Delphi, por su parte, accede a éstas en forma nativa mediante el tipo de dato ftCursor. A partir de este número de Mundo Delphi comenzamos con una serie de artículos que tratan sobre las características más potentes del lenguaje PL/SQL de Oracle. En esta edición, donde ahondaremos en los detalles del tema del título principal, veremos también algunos de estos atributos tales como el manejo de excepciones y la programación utilizando paquetes (packages), temas éstos que profundizaremos en próximas entregas. El tipo Ref Cursor Las variables de tipo cursor son referencias a cursores, más precisamente son punteros a un área de trabajo donde Oracle almacena los datos que resultan de una selección de múltiples registros. Estos punteros almacenan la dirección de memoria del objeto apuntado (y no el objeto en sí, tal como lo hace un cursor explícito) y por lo tanto pueden pasarse como parámetros de entrada/salida entre procedimientos almacenados o funciones de PL/SQL residentes en el servidor, y programas clientes escritos en otros lenguajes como Delphi, Pro*C, etc. Este pasaje de parámetros puede realizarse en ambos sentidos (Cliente/Servidor/Cliente) a fin de poder trabajar en cualquiera de estos entornos accediendo simultáneamente al mismo área de trabajo donde residen los datos de la consulta realizada. Esto equivale a decir que podemos compartir entre procedimientos y funciones escritas en diferentes lenguajes y ejecutándose en diferentes máquinas el resultado de una consulta residente en el servidor, sin importar quién la haya generado. Para crear este tipo de variables debe definirse primero un tipo Ref Cursor y luego declarar la variable de este tipo. Por ejemplo: TYPE tRc_tipo IS REF CURSOR; Res tRc_tipo; En este pequeño ejemplo hemos declarado Res como una variable de tipo cursor. En PL/SQL, cualquier puntero posee el tipo de dato REF (que es la abreviatura de Reference) y la clase de objeto a la que apunta. De allí que un Ref Cursor sea un puntero a un Cursor. Tipos de Ref Cursor según la clase de resultado al que referencian Dependiendo de la especificación o no del formato de datos a los que apunta una variable de tipo cursor, se las puede clasificar en 2 tipos: Tipo restrictivo: Se debe especificar un tipo de resultado asociado al formato del registro que devolverá el Select. En este caso el compilador de PL/SQL controla que el resultado de la consulta coincida con el tipo de dato asociado. Por ejemplo, si asociamos el resultado con un %RowType (tipo de registro que representa a una fila de una tabla) sólo podremos utilizar la variable cursor con una consulta que devuelva un registro exactamente igual a ese %RowType. Tipo no Restrictivo: A partir de la versión 7.3 de Oracle no es necesario especificar un tipo de resultado, pudiéndose de esta manera asociar un puntero a cualquier clase de consulta. Esta forma es mucho más flexible ya que podremos declarar los punteros una vez y utilizarlos en cualquier momento para manipular cualquier consulta. Al tipo de resultado apuntando por un Ref Cursor no restrictivo se lo denomina Variant Record. Ejemplos de definición, declaración y uso de Ref Cursor Para todos los ejemplos que veremos en este artículo utilizaremos un “modelo de datos” de dos tablas: Libros y Autores Veamos a continuación el esquema de estas tablas en formato de comandos de creación de las mismas junto con sus índices, llaves primarias y foráneas. 1
  • 3. Oracle PL/SQL – Variables de tipo Cursor. CREATE TABLE AUTORES ( CODIGO_AUTOR NUMBER(5) NOT NULL, NOMBRE VARCHAR2(100) NOT NULL, FECHA_NACIMIENTO DATE NULL, PRIMARY KEY (CODIGO_AUTOR) ); CREATE INDEX XIE1AUTORES ON AUTORES (NOMBRE); CREATE TABLE LIBROS ( CODIGO NUMBER(5) NOT NULL, NOMBRE VARCHAR2(100) NOT NULL, FECHA_EDICION DATE NOT NULL, PRESTADO VARCHAR2(1) NOT NULL CHECK (PRESTADO IN ('S', 'N')), CANT_PAGINAS NUMBER(5) NULL, CODIGO_AUTOR NUMBER(5) NOT NULL, PRIMARY KEY (CODIGO), FOREIGN KEY (CODIGO_AUTOR) REFERENCES AUTORES ); CREATE INDEX XIE1LIBROS ON LIBROS (FECHA_EDICION); CREATE INDEX XIE2LIBROS ON LIBROS (NOMBRE); En el siguiente ejemplo detallaremos cómo definir las dos clases de Ref Cursor que vimos anteriormente. En el primer caso, REFCURS_AUTORES es de tipo restrictivo ya que las variables declaradas bajo este tipo de dato deberán apuntar obligatoriamente a cursores de un tipo de dato equivalente. En este ocasión el tipo de dato coincide con el formato de registro de la tabla Autores (RowType). Si se intentara utilizar una variable de este tipo para apuntar a un cursor no coincidente con la definición de la misma, el compilador de PL/SQL nos indicará el error. El segundo tipo de Ref Cursor, REFCURS, corresponde a uno no restrictivo y tal como veremos en los ejemplos puede utilizarse para apuntar a cualquier tipo de cursor de datos. Para realizar la definición de ambos tipos utilizaremos un encabezado de Paquete. Esto nos permitirá que nuestra definición de tipos sea global y podamos utilizarla en cualquier procedimiento o función almacenada dentro del servidor. CREATE OR REPLACE PACKAGE PKG_REFCURS AS TYPE REFCURS_AUTORES IS REF CURSOR RETURN AUTORES%ROWTYPE; TYPE REFCURS IS REF CURSOR; END; / Una vez definidos los tipos podemos declarar las variables y utilizarlas. En el siguiente ejemplo crearemos una función que utiliza el tipo restrictivo de variable para retornar un puntero que referencia a un cursor cuya fuente de datos es una selección de la tabla Autores. CREATE OR REPLACE FUNCTION FRC_TRAE_AUTORES RETURN PKG_REFCURS.REFCURS_AUTORES AS -- Declaración de la variable de tipo cursor, utilizando el tipo restrictivo res PKG_REFCURS.REFCURS_AUTORES; BEGIN OPEN res FOR SELECT * FROM AUTORES; -- Retorno del puntero al resultado del Select RETURN res; END; / 2
  • 4. Oracle PL/SQL – Variables de tipo Cursor. Luego de creado, para ejecutarlo desde el SqlPlus de Oracle debe primero declararse dentro de este entorno una variable de tipo RefCursor que nos servirá como repositorio del puntero devuelto por la función. Para declarar la variable, que nos servirá también para el resto de los ejemplos, debe escribirse: VAR R RefCursor; Para ejecutar la función, escribimos: EXEC :R := FRC_TRAE_AUTORES; Para ver el resultado de la consulta debemos mostrar el contenido del puntero R. Para esto se utiliza el comando PRINT, de la siguiente forma: PRINT R; Otra forma de programar el ejemplo anterior es mediante un procedimiento almacenado en lugar de una función. En este caso veremos cómo debe recibirse la variable de tipo cursor en forma de parámetro de entrada/salida (IN OUT) ya que a través de la misma se recibirá/retornará el valor del puntero. Esta variable debe encontrase previamente declarada en el programa llamador (en este caso el SqlPlus, nuestra ya declarada variable R). CREATE OR REPLACE PROCEDURE PRC_TRAE_AUTORES (RES IN OUT PKG_REFCURS.REFCURS_AUTORES) AS BEGIN OPEN RES FOR SELECT * FROM AUTORES; RETURN; END; / Para ejecutarlo, se debe llamar al procedimiento almacenado enviando como parámetro la variable R, y luego mostramos el contenido de la misma con el comando PRINT. EXEC PRC_TRAE_AUTORES (:R) ; PRINT R; Ahora mostraremos un ejemplo de uso del tipo no restrictivo. A esta nueva función le pasaremos como parámetro una señal que indicará sobre cual tabla queremos que realice la consulta: Autores o Libros. Al ser la variable Res del tipo Ref Cursor no restrictivo puede apuntar tanto a uno como a otro cursor indistintamente, dependiendo de la señal A_O_L que le es enviada a la función. CREATE OR REPLACE FUNCTION FRC_TRAE_AUTORES_O_LIBROS (A_O_L IN VARCHAR2) RETURN PKG_REFCURS.REFCURS AS -- Declaración de la variable de tipo cursor, usando el tipo no restrictivo res PKG_REFCURS.REFCURS; BEGIN IF (A_O_L = ‘A’) THEN OPEN res FOR SELECT * FROM AUTORES; ELSIF (A_O_L = ‘L’) THEN OPEN res FOR SELECT * FROM LIBROS; ELSE 3
  • 5. Oracle PL/SQL – Variables de tipo Cursor. OPEN res FOR SELECT null FROM DUAL; END IF; -- Dependiendo de la señal A_O_L , la variable Res apuntará al query de Autores o Libros. RETURN res; END; / Para ejecutarlo: EXEC :R := FRC_TRAE_AUTORES_O_LIBROS('L'); Ó EXEC :R := FRC_TRAE_AUTORES_O_LIBROS('A'); Y luego: PRINT R; Dependiendo del parámetro enviado, R nos mostrará el resultado de la selección de la tabla Autores o de la tabla Libros. Conveniencia del uso de variables de tipo cursor Probablemente a esta altura del artículo el lector se esté preguntando cuáles son las ventajas de trabajar con este esquema de manipulación de datos. Ventajas pueden citarse muchas, como ser más potencia y flexibilidad en la programación, mayor claridad y calidad del código, etc. Pero sin duda la mayor ventaja es la posibilidad de crear aplicaciones Cliente/Servidor realmente robustas y que puedan obtener el máximo provecho de todas las capacidades que nos ofrece un motor de base de datos como Oracle a través de su poderoso lenguaje PL/SQL. A fin de ejemplificar el concepto, voy a detallar dos características importantes. Existen más y las iremos viendo en próximas entregas, en la medida que vayamos profundizando en las poderosas herramientas que nos ofrece Oracle. Encapsulamiento: Las consultas quedan centralizadas en los procedimientos y funciones almacenadas en lugar de tenerlas codificadas en la aplicación cliente. La inteligencia, complejidad y métodos para obtener la información (donden se involucran las “reglas del negocio”) residen de esta manera en el servidor de datos, ocultando todo detalle de funcionamiento a las aplicaciones clientes. Como ventaja adicional podemos agregar que se reduce sensiblemente el tráfico en la red al recibir el programa cliente la información completamente elaborada y lista para mostrar o imprimir. Y por supuesto, también se simplifica notablemente la tarea de mantenimiento de las aplicaciones. Seguridad mejorada: Los usuarios sólo necesitan tener permisos de ejecución de los procedimientos y funciones que realizan las consultas, no siendo necesario que tengan permisos de acceso a las tablas que estos procedimientos consultan. De esta manera se evitan acc esos indebidos o no deseados a la información por fuera de las restricciones que imponen nuestras aplicaciones. Un ejemplo completo de función PL/SQL que devuelve un Ref Cursor Como ejemplo final veremos una función que realiza una consulta de libros y autores, con filtros determinados por los parámetros que recibe. Estos parámetros son el nombre del autor, o parte del mismo, y una fecha de edición de libros que será tomada como fecha límite base para seleccionar los datos. Como detalle, y a diferencia de los anteriores ejemplos, podemos ver un manejo básico de errores en el bloque Exception y una sencilla forma de armar en tiempo de ejecución una cláusula Like agregando 4
  • 6. Oracle PL/SQL – Variables de tipo Cursor. comodines al parámetro recibido. Finalmente veremos cómo ejecutar esta función desde otro procedimiento PL/SQL y desde un programa Delphi. CREATE OR REPLACE FUNCTION FRC_TRAE_LIBROS (AUTOR AUTORES.NOMBRE%TYPE, FECHA_DESDE LIBROS.FECHA_EDICION%TYPE) RETURN PKG_REFCURS.REFCURS AS -- Declaración de variables locales. -- Res es de tipo no restrictivo res PKG_REFCURS.REFCURS; -- Esta variable se utiliza para armar la cláusula Like en tiempo -- de ejecución para buscar por nombre de autor. cadena_auxiliar VARCHAR2(150); BEGIN -- Comienzo del bloque de código cadena_auxiliar := '%' || RTRIM (LTRIM (AUTOR) ) || '%'; OPEN res FOR SELECT A.CODIGO AS CODIGO_LIBRO, A.NOMBRE AS NOMBRE_LIBRO, A.FECHA_EDICION, A.PRESTADO, B.NOMBRE AS NOMBRE_AUTOR, B.FECHA_NACIMIENTO FROM LIBROS A, AUTORES B WHERE A.FECHA_EDICION >= fecha_desde AND A.CODIGO_AUTOR = B.CODIGO_AUTOR AND B.NOMBRE LIKE cadena_auxiliar; -- Retorno del puntero al cursor obtenido RETURN res; EXCEPTION – Comienzo del bloque de manejo de excepciones WHEN OTHERS THEN RAISE_APPLICATION_ERROR (-20999, TO_CHAR(SQLCODE) || ': Se produjo un error en la búsqueda de libros - ' || SUBSTR(SQLERRM, 1, 256) ); CLOSE res; RETURN res; -- En caso de que se produzca cualquier excepción -- devolvemos un puntero a un cursor cerrado. END; / Ejecución y acceso desde un bloque PL/SQL El siguiente bloque de código PL/SQL ejecuta la función FRC_TRAE_LIBROS, mostrando el resultado de la consulta generada y devuelta a través del Ref Cursor que retorna esta función. Nuestro criterio de selección será obtener los datos de libros del autor Stephen King (de quien sólo enviaremos la sub-cadena King) y cuya fecha de edición sea posterior al 1-1-1970. Nota: Antes de ser ejecutado este código, en el SqlPlus debe escribirse: SET SERVEROUTPUT ON. Esta sentencia activa la funcionalidad del DBMS_OUTPUT.PUT_LINE (que es el equivalente PL/SQL al writeln de Pascal) DECLARE -- declaración de un tipo de registro donde guardar cada fila de la consulta TYPE tRecRes IS RECORD ( codigo_libro LIBROS.CODIGO%TYPE, nombre_libro LIBROS.NOMBRE%TYPE, fecha_edicion LIBROS.FECHA_EDICION%TYPE, prestado LIBROS.PRESTADO%TYPE, nombre_autor AUTORES.NOMBRE%TYPE, fecha_nacimiento AUTORES.FECHA_NACIMIENTO%TYPE ); 5
  • 7. Oracle PL/SQL – Variables de tipo Cursor. -- Definición de la variable de tipo cursor Res PKG_REFCURS.REFCURS; -- Definición de la variable de tipo registro RecRes tRecRes; BEGIN -- Llamamos a la función que abre la consulta y nos devuelve un puntero a la misma -- A esta función le enviamos como parámetros parte del nombre del autor y una fecha -- de edición base, con el fin de filtrar la consulta. Res := FRC_TRAE_LIBROS (‘KING’, TO_DATE(‘01011970’, ‘ddmmyyyy’)); -- Recorremos el cursor y mostramos todas sus filas por pantalla LOOP FETCH Res INTO RecRes; EXIT WHEN Res%NOTFOUND; DBMS_OUTPUT.PUT_LINE (‘Nombre Libro: ’ || RecRes.nombre_libro); DBMS_OUTPUT.PUT_LINE (‘Fecha Edición: ’ || TO_CHAR (RecRes.fecha_edicion, ‘dd-mm-yyyy’)); DBMS_OUTPUT.PUT_LINE (‘Código Libro: ’ || TO_CHAR (RecRes.codigo_libro)); DBMS_OUTPUT.PUT_LINE (‘Prestado (s/n): ’ || RecRes.prestado); DBMS_OUTPUT.PUT_LINE (‘Nombre Autor: ’ || RecRes.nombre_autor); DBMS_OUTPUT.PUT_LINE (‘F. Nac. Autor: ’ || TO_CHAR (RecRes.fecha_nacimiento, ‘dd-mm-yyyy’)); DBMS_OUTPUT.PUT_LINE (‘------------------------------------------------------‘); END LOOP; END; / Ejecución y acceso desde Delphi Desde nuetros programas Delphi podemos ejecutar un procedimiento almacenado o función Oracle de las carácterísticas de FRC_TRAE_LIBROS utilizando el componente TStoredProc. El resultado de la función, un Ref Cursor, será recibido en un parámetro Result del tipo ftCursor. Un importante detalle al que debe prestarse atención es que el TStoredProc debe ser ejecutado utilizando la sentencia Open en lugar de ExecSql debido a que precisamente lo que obtenemos como resultado de la ejecución es un DataSet, tal como si se tratara de la apertura de un TQuery. En los ejemplos que se adjuntan con la revista (el POracle_RC) se incluye tanto el código que sigue a continuación como un ejemplo de uso del componente TstoredProc en tiempo de diseño y con salida de la información a una grilla. El ejemplo está codificado en Delphi 4 C/S y tiene las mismas características de filtrado y presentación de la información que el ejemplo inmediato anterior en PL/SQL. Procedure TFOracle_RC.BtnTestClick(Sender: TObject); Var sp: TStoredProc; Begin Try sp := TStoredProc.Create(nil); With sp do Begin DatabaseName := base.DatabaseName; // base es el nombre de un TDatabase StoredProcName := 'FRC_TRAE_LIBROS'; Params.clear; Params.CreateParam(ftString, 'AUTOR', ptInput); Params.CreateParam(ftDateTime, 'FECHA_DESDE', ptInput); Params.CreateParam(ftCursor, 'Result', ptResult); ParamByName('AUTOR').AsString := 'KING'; ParamByName('FECHA_DESDE').AsDateTime := strtodatetime('01/01/1970'); 6
  • 8. Oracle PL/SQL – Variables de tipo Cursor. Open; // Usamos OPEN en lugar de ExecProc // y luego tomamos los valores con FieldByName. While not EOF do Begin Showmessage( 'Nombre Libro: ' + Fieldbyname('NOMBRE_LIBRO').AsString + #10#13 + 'Fecha Edición: ' + FormatDateTime('dd/mm/yyyy', Fieldbyname('FECHA_EDICION').AsDateTime) + #10#13 + 'Código Libro: ' + Fieldbyname('CODIGO_LIBRO').AsString + #10#13 + 'Prestado (s/n): ' + Fieldbyname('PRESTADO').AsString + #10#13 + 'Nombre Autor: ' + Fieldbyname('NOMBRE_AUTOR').AsString + #10#13 + 'F.Nac. Autor: ' + FormatDateTime('dd/mm/yyyy', Fieldbyname('FECHA_NACIMIENTO').AsDateTime)); Next; End; Close; End; Finally sp.Free; End; End; 7
  • 9. Oracle PL/SQL – programación con Paquetes. Además de brindarnos múltiples elementos que nos permiten desarrollar una aplicación robusta, Oracle nos ofrece la posibilidad de programar en forma modular, clara y eficiente. Siguiendo con nuestro tutorial sobre elementos avanzados de programación Oracle PL/Sql, en esta oportunidad veremos cómo embeber procedimientos, funciones, definiciones de tipos de datos y declaraciones de variables en una misma estructura que los agrupe y relacione lógicamente. Esta estructura se denomina Package (Paquete) y su uso nos permite no sólo mejorar la calidad de diseño de nuestras aplicaciones sino también optimizar el desempeño de las mismas. Packages. Como vimos, un Paquete es un objeto PL/Sql que agrupa lógicamente otros objetos PL/Sql relacionados entre sí, encapsulándolos y convirtiéndolos en una unidad dentro de la base de datos. Los Paquetes están divididos en 2 partes: especificación (obligatoria) y cuerpo (no obligatoria). La especificación o encabezado es la interfaz entre el Paquete y las aplicaciones que lo utilizan y es allí donde se declaran los tipos, variables, constantes, excepciones, cursores, procedimientos y funciones que podrán ser invocados desde fuera del paquete. En el cuerpo del paquete se implementa la especificación del mismo. Entonces, el formato de un Paquete es el siguiente: CREATE [OR REPLACE] PACKAGE nombre AS -- especificación (parte visible) -- declaración de tipos y variables públicas -- especificación de procedimientos y funciones END [nombre]; CREATE [OR REPLACE] PACKAGE BODY nombre AS -- cuerpo (parte oculta) -- declaración de tipos y variables privadas -- cuerpo de procedimientos y funciones [BEGIN -- rutinas de ejecución inicial] END [nombre]; La especificación contiene las declaraciones públicas, o sea, la declaración de elementos que son visibles a las aplicaciones que tienen acceso a ese mismo esquema dentro de la base de datos. El cuerpo contiene los detalles de implementación y declaraciones privadas, manteniéndose todo ésto oculto a las aplicaciones externas, siguiendo el conocido concepto de “caja negra”. Dicho de otra manera, sólo las declaraciones hechas en la especificación del paquete son visibles y accesibles desde fuera del paquete (por otras aplicaciones o procedimientos almacenados) quedando los detalles de implementación del cuerpo del paquete totalmente ocultos e inaccesibles para el exterior. Para acceder a los elementos declarados en un paquete basta con anteceder el nombre del objecto a referenciar con el nombre del paquete donde está declarado y un punto, de esta manera: Paquete.Objeto donde Objeto puede ser un tipo, una variable, un cursor, un procedimiento o una función declarados dentro del paquete. Esta notación es válida tanto para referenciar desde procedimientos o triggers externos al paquete como desde aplicaciones escritas en otros lenguajes o mismo desde herramientas como el Sql*Plus. Para referenciar objetos desde adentro del mismo paquete donde han sido declarados no es necesario especificar el nombre del paquete y pueden (deberían) ser referenciados directamente por su nombre. Finalmente y siguiendo a la parte declarativa del cuerpo de un paquete puede, opcionalmente, incluirse la sección de inicialización del paquete. En esta sección pueden, por ejemplo, inicializarse variables que 8
  • 10. Oracle PL/SQL – programación con Paquetes. utiliza el paquete tal como veremos en el ejemplo final. La sección de inicialización se ejecuta sólo la primera vez que una aplicación referencia a un paquete, ésto es, se ejecuta sólo una vez por sesión. Ventajas del uso de Paquetes. Dentro de las ventajas que ofrece el uso de paquetes podemos citar que: Permite modularizar el diseño de nuestra aplicación El uso de Paquetes permite encapsular elementos relacionados entre sí (tipos, variables, procedimientos, funciones) en un único módulo PL/Sql que llevará un nombre que identifique la funcionalidad del conjunto. Otorga flexibilidad al momento de diseñar la aplicación En el momento de diseñar una aplicación todo lo que necesitaremos inicialmente es la información de interfaz en la especificación del paquete. Puede codificarse y compilarse la especificación sin su cuerpo para posibilitar que otros sub-programas que referencian a estos elementos declarados puedan compilarse sin errores. De esta manera podremos armar un “prototipo” de nuestro sistema antes de codificar el detalle del mismo. Permite ocultar los detalles de implementación Pueden especificarse cuáles tipos, variables y sub-programas dentro del paquete son públicos (visibles y accesibles por otras aplicaciones y sub-programas fuera del paquete) o privados (ocultos e inaccesibles fuera del paquete). Por ejemplo, dentro del paquete pueden existir procedimientos y funciones que serán invocados por otros programas, así como también otras rutinas de uso interno del paquete que no tendrán posibilidad de ser accedidas fuera del mismo. Esto asegura que cualquier cambio en la definición de estas rutinas internas afectará sólo al paquete donde se encuentran, simplificando el mantenim iento y protegiendo la integridad del conjunto. Agrega mayor funcionalidad a nuestro desarrollo Las definiciones públicas de tipos, variables y cursores hechas en la especificación de un paquete persisten a lo largo de una sesión. Por lo tanto pueden ser compartidas por todos los sub-programas y/o paquetes que se ejecutan en ese entorno durante esa sesión. Por ejemplo, puede utilizarse esta técnica (en dónde sólo se define una especificación de paquete y no un cuerpo) para mantener tipos y variables globales a todo el sistema. Un ejemplo concreto de ésto fue visto en el número 1 de Mundo Delphi, en el artículo que trata sobre “Ref Cursores”, donde precisamente utilizamos una especificación de paquete para declarar un tipo REF CURSOR global a toda la aplicación. Introduce mejoras al rendimiento En relación a su ejecución, cuando un procedimiento o función que está definido dentro de un paquete es llamado por primera vez, todo el paquete es ingresado a memoria. Por lo tanto, posteriores llamadas al mismo u otros sub-programas dentro de ese paquete realizarán un acceso a memoria en lugar de a disco. Esto no sucede con procedimientos y funciones estándares. Permite la “Sobrecarga de funciones” (Overloading). PL/Sql nos permite que varios procedimientos o funciones almacenadas, declaradas dentro de un mismo paquete, tengan el mismo nombre. Esto nos es muy útil cuando necesitamos que los sub-programas puedan aceptar parámetros que contengan diferentes tipos de datos en diferentes instancias. En este caso Oracle ejecutará la “versión” de la función o procedimiento cuyo encabezado se corresponda con la lista de parámetros recibidos. Un ejemplo. A continuación veremos un ejemplo de los conceptos explicados anteriormente. El objetivo de esta tabla, Accesos, es llevar un registro por cada acceso inválido que realice un usuario en cada sesión, y un histórico total acumulativo a fines de facilitar la lectura de la historia. Supondremos que la función Alta será invocada por una aplicación que supervisa esta tarea de control. 9
  • 11. Oracle PL/SQL – programación con Paquetes. Alta es la única función pública del paquete. Veamos la definición de la tabla: CREATE TABLE ACCESOS ( USUARIO VARCHAR2(15) NOT NULL, FECHA_HORA_INICIO_SESION DATE NOT NULL, TERMINAL_SESION VARCHAR2(15) NOT NULL, FECHA_HORA_ACCESO DATE NOT NULL, NRO_ACCESO_EN_SESION NUMBER(10) NOT NULL, TOTAL_HISTORICO NUMBER(10) NOT NULL ); -- -- Usuario: Usuario Oracle. -- Fecha_Hora_Inicio_Sesion: Momento en que comienza la sesión actual. -- Terminal_Sesion: Terminal desde donde se encuentra conectado -- el usuario. -- Fecha_Hora_Acceso: Momento en que se produce el acceso inválido. -- Nro_Acceso_En_Sesion: Cuenta el número de acceso inválido -- en la sesión actual. -- Total_Historico: Cuenta el total de accesos inválidos que se -- registran históricamente para este usuario. y ahora el Paquete: -- Especificación del paquete -- Sólo se declara aquí el único elemento que será visible -- a las aplicaciones que invoquen este paquete. CREATE OR REPLACE PACKAGE PKG_ACCESOS AS FUNCTION ALTA RETURN NUMBER; END; / -- Cuerpo del paquete CREATE OR REPLACE PACKAGE BODY PKG_ACCESOS AS -- Variables globales al paquete, pero inaccesibles desde afuera. -- Su valor se asigna en la inicialización del paquete. -- (ver al final del paquete, el último BEGIN/END) WG_FECHA_INICIO_SESION DATE; WG_SECUENCIA NUMBER (10); WG_CANT_HIST NUMBER (10); -- Función Privada dentro del paquete (sólo uso interno) -- Es inaccesible desde afuera porque no está declarada -- en la especificación del paquete. FUNCTION OBTIENE_CANTIDAD_HISTORICA (USER VARCHAR2) RETURN NUMBER AS CANT NUMBER(10); BEGIN SELECT COUNT(*) INTO CANT FROM ACCESOS WHERE USUARIO = USER; RETURN CANT+1; EXCEPTION WHEN OTHERS THEN RAISE; END; -- Implementación de la función Alta -- (Global, visible desde afuera del paquete) 10
  • 12. Oracle PL/SQL – programación con Paquetes. FUNCTION ALTA RETURN NUMBER AS BEGIN WG_SECUENCIA := WG_SECUENCIA + 1; WG_CANT_HIST := OBTIENE_CANTIDAD_HISTORICA (USER); INSERT INTO ACCESOS (USUARIO, FECHA_HORA_INICIO_SESION, TERMINAL_SESION, FECHA_HORA_ACCESO, NRO_ACCESO_EN_SESION, TOTAL_HISTORICO) VALUES (USER, WG_FECHA_INICIO_SESION, USERENV('TERMINAL'), SYSDATE, WG_SECUENCIA, WG_CANT_HIST); RETURN 1; EXCEPTION WHEN OTHERS THEN RETURN 0; -- si se produce algun error -- retorna cero END; -- Rutinas de inicialización, se ejecutan sólo la primera -- vez que la aplicación referencia al paquete BEGIN WG_FECHA_INICIO_SESION := SYSDATE; WG_SECUENCIA := 0; END; / Para ejecutar la función Alta, basta escribir desde el Sql*Plus: SQL > VAR RES NUMBER; SQL > EXECUTE :RES := PKG_ACCESOS.ALTA; SQL> PRINT RES; y para ver el resultado de la inserción en la tabla: SQL> SELECT * FROM ACCESOS; La función devolverá 0 en caso de error o 1 en caso contrario. 11
  • 13. Oracle PL/SQL – Desarrollo de funciones SQL En esta entrega veremos las reglas que deben seguir las funciones almacenadas para que puedan usarse en una sentencia SQL Una de las caractéristicas que hacen sobresalir a Oracle por sobre otros administradores de bases de datos relacionales es la enorme cantidad de funciones que vienen “de fábrica” para poder usarse directamente dentro de sentencias Sql. Esta funciones son de conversión de tipos, aritméticas, de manejo de cadenas de caracteres, de manejo de fechas, etc. No obstante, siempre podremos necesitar desarrollar nuestras propias funciones para casos específicos y poder adicionarlas a la librería preexistente. Desarrollo de una Función SQL de usuario. En principio, una Función SQL de usuario es equivalente a una Función Almacenada, en cuanto a que está programada en PL/Sql y puede ser invocada mediante la sentencia Exec. La diferencia fundamental radica en que una Función SQL puede utilizarse en cualquier sentencia SQL, ya sea en un Select, un Update, un Insert o un Delete, y tanto en la parte enunciativa de campos como en la cláusula Where. Entonces, ¿qué es lo que las diferencia? O mejor aún, ¿porqué no podemos hablar sólo de un tipo de función y usarla según nuestra conveniencia?. La respuesta es que existen determinadas restricciones para poder usar una Función Almacenada en una sentencia Sql, las que detallaremos a continuación. Restricciones Para ejecutar una sentencia Sql que invoca a una función almacenada, Oracle debe conocer anticipadamente la posibilidad de que esta función produzca alguna modificación en el estado actual de la base de datos, para evitar así un resultado inesperado en el retorno de la función. Para minimizar esta posibilidad, Oracle inhibe a las funciones SQL de: • Modificar datos en tablas, por lo tanto no puede invocar sentencias Insert, Update ni Delete. • Modificar variables globales dentro de un Paquete. • Contener parámetros OUT o IN OUT. • Ser una función de columna o grupo. Las funciones de columna son aquélas que toman como entrada toda una columna de datos, como por ejemplo SUM(), o AVG(). Por lo tanto nuestras funciones sólo podrán comportarse como funciones de fila. Un ejemplo Para nuestro ejemplo desarrollaremos una función que respete las reglas arriba enumeradas. Dentro de un Paquete PL/Sql crearemos una función de validación de CUIT. Para los que no vivan en la República Argentina o no estén familiarizados con esta sigla les cuento que CUIT significa Código Unico de Identificación Tributaria y es utilizado por el organis mo recaudador de impuestos para identificar a las empresas y profesionales. Este número está compuesto por un prefijo de 2 dígitos, un número de 8 posiciones (que en el caso de las personas es el documento nacional de identidad) y un dígito verificador. Este dígito verificador se calcula mediante una fórmula de módulo 11. Adentrémonos ahora en la situación que usaremos en nuestro ejemplo. Tendremos una tabla pecargada con datos de personas (figura 1), donde consta su CUIT. En dicha carga no se ha utilizado ninguna validación por lo que deberemos evaluar la validez o no de los datos existentes mediante nuestra función (figura 2). 12
  • 14. Oracle PL/SQL – Desarrollo de funciones SQL Figura 1 . Tabla de Personas CREATE TABLE PERSONAS ( CODIGO NUMBER(10) NOT NULL, NOMBRES VARCHAR2(50) NOT NULL, APELLIDOS VARCHAR2(50) NOT NULL, CUIT VARCHAR2(11) NOT NULL, PRIMARY KEY (CODIGO) ); Figura 2 . Paquete con función de validación CREATE OR REPLACE PACKAGE Pkg_Valida AS FUNCTION CUIT_VALIDO (XID VARCHAR2) RETURN NUMBER; END; / CREATE OR REPLACE PACKAGE BODY Pkg_Valida AS -- Esta función devuelve 1 en caso de que el -- CUIT sea correcto ó 0 en caso contrario FUNCTION CUIT_VALIDO (XID VARCHAR2) RETURN NUMBER AS RES NUMBER; I NUMBER; DIG NUMBER; NUM NUMBER; BEGIN -- Primero comprobamos -- validaciones sencillas IF LENGTH(XID) != 11 OR SUBSTR(XID, 1, 2) = '00' THEN RETURN 0; END IF; -- Ahora comprobamos la validez -- del dígito verificador -- usándo cálculo de módulo 11 RES := 0; FOR I IN 1..10 LOOP NUM := TO_NUMBER(SUBSTR(XID, I, 1)); IF I = 1 OR I = 7 THEN RES := RES + NUM * 5; ELSIF I = 2 OR I = 8 THEN RES := RES + NUM * 4; ELSIF I = 3 OR I = 9 THEN RES := RES + NUM * 3; ELSIF I = 4 OR I = 10 THEN RES := RES + NUM * 2; ELSIF I = 5 THEN RES := RES + NUM * 7; ELSIF I = 6 THEN RES := RES + NUM * 6; END IF; END LOOP; DIG := 11 - MOD(RES, 11); IF DIG = 11 THEN DIG := 0; END IF; -- Ahora retornamos valor según -- validez o no del dígito verificador IF DIG = TO_NUMBER(SUBSTR(XID, 11, 1)) THEN RETURN 1; ELSE RETURN 0; END IF; EXCEPTION -- En caso de error retornamos 0 WHEN OTHERS THEN RETURN 0; END;
  • 15. END; / Lo primero que haremos será chequear el correcto funcionamiento de la función de validación que hemos creado, que nos devolverá 1 en caso de recibir un número correcto ó 0 en caso contrario. Para chequearla la ejecutaremos en la forma estándar, es decir, mediante la instrucción Exec. (figura 3) Vemos que funciona correctamente, ya que le hemos enviado un número válido y nos devuelve un 1. Figura 3 . Ejecución de la función mediante Exec SQL> VAR VALIDO NUMBER; SQL> EXEC :VALIDO := Pkg_Valida.cuit_valido('20224367911'); PL/SQL PROCEDURE successfully completed. SQL> PRINT VALIDO; VALIDO --------- 1 SQL> Ahora la ejecutaremos cómo una Función SQL a través de un Select. (figura 4) Figura 4 . Ejecución de la función como Función SQL dentro de un Select SQL> SELECT Pkg_Valida.cuit_valido('20224367911') SQL> AS VALIDO FROM DUAL; SQL> ORA-06571: function cuit_valido does not guarantee not to update database SQL> En contra de lo esperado, se ha producido un error. El servidor Oracle nos informa que no puede ejecutar la función dentro de un SQL porque la misma no garantiza que la base de datos no será modificada. Pero si analizamos el código de nuestra función, veremos que la misma no viola ninguna de las restricciones conocidas. Entonces, ¿qué pasó? La cláusula "Pragma Restrict_References" Oracle chequea el código de la función buscando posibles causas de conflicto. Para funciones que encierran cierta complejidad, el Servidor no consigue tener certeza sobre los potenciales “riesgos” de ejecutarla, y por ende nos envía el mensaje de error que acabamos de ver para informarnos de tal situación. Entonces nos vemos obligados a forzar al Compilador PL/Sql a chequear las reglas, a fin de que el Servidor no deba hacerlo al ejecutar la función. En este paso, será el Compilador el que nos acepte o rechace la función al momento de compilarla. Pero como ésto sólo puede hacerse dentro del encabezado de un Paquete y no es posible hacerlo para funciones almacenadas desarrolladas fuera de paquetes, éstas últimas no podrán usarse en sentencias Sql en caso de que el Servidor no las acepte. Por lo tanto la recomendación es que siempre deberemos usar Paquetes donde incluir este tipo de funciones. 14
  • 16. Oracle PL/SQL – Desarrollo de funciones SQL La clásula Pragma Restrict_References debe ir en el encabezado del Paquete, y siguiendo a la definición de cada función, siendo la sintaxis la desplegada en la figura 5. En la figura 6 mostramos el significado de cada argumento de este Pragma. Figura 5 . Sintaxis de Pragma Restrict_References PRAGMA RESTRICT_REFERENCES ( Nombre_de_función, WNDS [, WNPS] [, RNDS] [, RNPS]); Figura 6 . Significado de cada argumento WNDS No modifica tablas en la base de datos Writes no database state RNDS No lee tablas en la base de datos Reads no database state WNPS No cambia valores de variables globales Writes no package state de Paquetes RNPS No referencia valores de variables Reads no package state globales de Paquetes El único argumento obligatorio es WNDS, pero siempre es aconsejable definir el máximo nivel de “pureza” que nuestra función posee, a fin de que el Compilador PL/Sql no rechace innecesariamente a nuestra función. Adaptación de nuestro ejemplo Habiendo visto lo anterior, sabemos ahora que tenemos que agregar este Pragma a nuestro encabezado de Paquete. Veremos el nuevo encabezado en la figura 7. Introduciremos todos los argumentos, ya que sabemos que nuestra función los cumple, y de esta manera garantizaremos que el Compilador la acepte. Luego de introducidos los cambios deberemos recompilar nuevamente el encabezado y el cuerpo del paquete. Figura 7 . Nuevo encabezado del Paquete CREATE OR REPLACE PACKAGE Pkg_Valida AS FUNCTION CUIT_VALIDO (XID VARCHAR2) RETURN NUMBER; -- Agregamos aquí el PRAGMA PRAGMA RESTRICT_REFERENCES (CUIT_VALIDO, WNDS, WNPS, RNPS); END; / Ejemplos de uso. Finalmente podremos utilizar nuestra Función Almacenada como una Función SQL. En la figura 8 volveremos a nuestro ejemplo de ejecución simple mediante un Select, pero esta vez vemos que afortunadamente todo termina bien. 15
  • 17. Oracle PL/SQL – Desarrollo de funciones SQL Figura 8 . Ejecución exitosa de la función como función SQL dentro de un Select SQL> SELECT Pkg_Valida.cuit_valido('20224367911') SQL> AS VALIDO FROM DUAL; VALIDO --------- 1 SQL> Y por último 2 ejemplos más a fin de ilustrar los posibles usos de nuestra función en un contexto como el planteado, en donde debemos analizar o mostrar el contenido de una tabla cuyos datos no están validados. Figura 9 . Mostramos Código de persona, CUIT y Estado calculado del CUIT (válido o inválido) SQL> SELECT CODIGO, NOMBRES, CUIT, SQL> DECODE (Pkg_Valida.cuit_valido(CUIT), 1, 'VALIDO', SQL> 0, 'INVALIDO') SQL> AS ESTADO SQL> FROM PERSONAS SQL> ORDER BY CODIGO; CODIGO NOMBRES CUIT ESTADO --------- ---------- ----------- -------- 1 ADAN 11111111111 INVALIDO 2 EVA 20224367911 VALIDO SQL> Figura 10 . Calculamos cuántos CUIT inválidos existen en la tabla SQL> SELECT COUNT(*) AS INVALIDOS FROM PERSONAS SQL> WHERE Pkg_Valida.cuit_valido(CUIT) = 0; INVALIDOS --------- 1 SQL> 16
  • 18. Oracle PL/SQL – SQL dinámico Una herramienta que nos permite añadir potencia y versatilidad a nuestras aplicaciones de bases de datos. De por sí, Oracle no soporta en forma nativa el uso de operaciones dinámicas en un procedimiento o función almacenada. Al decir “operaciones dinámicas” me refiero a todo aquéllo que deba ser resuelto en tiempo de ejecución en lugar de en tiempo de compilación. Por ejemplo, si quisiéramos crear una tabla dentro de un procedimiento almacenado, no podríamos hacerlo mediante el uso de Pl/Sql estándard. Veamos primero porqué existe esta limitación y luego cómo Oracle nos permite resolver las cosas para poder llevar a cabo este tipo de tareas. Modelo estático – Eficiencia versus flexibilidad Como ocurre en la mayoría de los lenguajes de programación, en Oracle, antes de que un procedimiento Pl/Sql pueda ser ejecutado, debe ser compilado. El compilador resuelve toda referencia a objetos del esquema (tablas, funciones, procedimientos, etc.), buscando sus definiciones en el diccionario de datos de la base, y asignándoles direcciones a fin de que puedan ser referenciados al momento de ejecutarse el programa. Este esquema de resolución de referencias al momento de la compilación, es conocido como modelo estático y es el que prioriza la eficiencia en la ejecución del programa. Como resultado de la compilación, Oracle genera un código del tipo p-code que es luego directamente procesado por el entorno Pl/Sql. Por otro lado, un modelo dinámico sería aquél en donde se efectuarían las resoluciones en tiempo de ejecución, priorizándose de esta manera la flexibilidad de los procesos que pueden desarrollarse dado que los objetos del esquema pueden no conocerse o no existir hasta el momento de la ejecución de la línea de código que los referencia. Dado que el modelo elegido por Oracle es el estático, donde deben existir al momento de la compilación todos los objetos que se refencian dentro del programa, es que no podemos, como dijimos, crear una tabla dentro de un procedimiento almacenado usando las sentencias estándares. Esto parece limitante, y lo es, pero siempre tenemos una forma de hacer las cosas cuando trabajamos con herramientas que han sido concebidas para darnos satisfacciones en lugar de hacernos pasar penurias. (... apostaría a que estamos pensando en la misma empresa generadora de penurias ... ) El paquete DBMS_SQL La forma en la que podemos agregar dinamismo a nuestros procedimientos y funciones almacenadas, es mediante el uso del paquete SYS.DBMS_SQL. A través de las funciones implementadas en el mismo podremos realizar operaciones que involucren la referencia a objetos que no existen o que no podemos determinar al momento de la compilación. El “truco” consiste en que estas instrucciones no las escribamos como código Pl/Sql sino como “cadenas de caracteres”, que podrán ser tanto recibidas como parámetros del procedimiento, o ser definidas o armadas por el mismo, y posteriormente enviadas a las funciones del paquete Dbms_Sql para su efectivo proceso. Mediante esta técnica podremos implementar el ejemplo del que hablamos al comienzo: Crear una tabla dentro de una procedimiento almacenado (“Listado 1”) El procedimiento utilizado no tiene nada de complejo. Usando las funciones definidas dentro del paquete Dbms_Sql creamos y abrimos un cursor especial que se usará en la ejecución, armamos la instrucción y la enviamos para su interpretación y proceso. Luego, cerramos el cursor. Algo muy parecido a lo que hacemos en Delphi cuando utilizamos TQuery.ExecSql Nota: El indicador dbms_sql.v7 es obligatorio y debe usarse en las versiones 7 y 8 de Oracle. 17
  • 19. Oracle PL/SQL – SQL dinámico CREATE OR REPLACE PROCEDURE DBMS_EJEMPLO AS ID INTEGER; TABLA VARCHAR2(20); CAMPOS VARCHAR2(100); BEGIN TABLA := ‘PRUEBA ’; CAMPOS := ‘ (C1 NUMBER(10), C2 VARCHAR2(10)) ’; ID := DBMS_SQL.OPEN_CURSOR; DBMS_SQL.PARSE(ID, 'CREATE TABLE ' || TABLA || CAMPOS, dbms_sql.v7); DBMS_SQL.CLOSE_CURSOR(ID); END; / Listado 1 . Un ejemplo simple. Un ejemplo un poco más elaborado En el ejemplo del Listado 1, vimos de forma simple cómo procesar una instrucción dinámica dentro de la base de datos. Ahora veamos cómo sacarle más provecho a las bondades que nos ofrece esta técnica. Las famosas reglas del negocio Como bien sabemos, en una aplicación Cliente/Servidor una de las características más importantes es la implementación de todas las definiciones que hagan al negocio (sean las de un banco, una fábrica o un local de venta de ropa) dentro de la base de datos y no en la codificación del programa cliente. Estas reglas normalmente se refieren a validaciones y restricciones cuya lógica, al estar centralizada en el servidor de datos, controlará tanto a las transacciones realizadas por nuestras aplicaciones como a las llevadas a cabo fuera de ellas (por ejemplo mediante herramientas que nos permitan el acceso directo a los datos). Para nuestro ejemplo, nos ubicaremos en el siguiente cuadro de situación: Tenemos una pequeña empresa que se dedica al ensamblado de computadoras (ordenadores, para mis amigos españoles). A cada parte que participa en el equipo que finalmente resulta armado la llamaremos “componente”, siendo a la vez cada componente de un “tipo” específico. Es así que podemos tener componentes del tipo “teclado”, “monitor”, “chip”, “memoria”, etc. Imaginemos que dependiendo del modelo de computadora, el tipo de componente a utilizar puede variar, por ejemplo habrá modelos con monitores de 14 pulgadas y 32 MB de RAM, otros con monitores de 17 pulgadas y 64 MB de memoria, y así. Bien, lo que la empresa ideó a fin de tener un mejor control de su stock, es una codificación de los componentes de manera tal que el código del mismo no sea arbitrario sino que esté regido por una regla definida para el tipo al que pertenece el componente. De esta manera no se podrá codificar erróneamente un componente, evitándose así errores en su clasificación. Dado que esta definición es una regla del negocio, la implementaremos en la base de datos. En el Listado 2 veremos las definiciones de las tablas de Tipos_Componente y Componentes. CREATE TABLE TIPOS_COMPONENTE ( TIPO NUMBER(10) NOT NULL, DESCRIPCION VARCHAR2(50) NOT NULL, FUNCION_VALIDACION VARCHAR2(30), PRIMARY KEY (TIPO) ); CREATE TABLE COMPONENTES ( CODIGO VARCHAR2(10) NOT NULL, TIPO NUMBER(10) NOT NULL, 18
  • 20. Oracle PL/SQL – SQL dinámico DESCRIPCION VARCHAR2(50) NOT NULL, PRIMARY KEY (CODIGO), FOREIGN KEY (TIPO) REFERENCES TIPOS_COMPONENTE ); Listado 2 . Definiciones de tablas. Como vemos, las tablas están relacionadas por el tipo de componente tal lo esperado. Pero prestemos atención a la tabla Tipos_Componente. En ella encontramos un campo, Funcion_Validacion, que es el que guardará la información acerca de cuál función almacenada validará a los códigos de componentes ingresados en la tabla Componentes. Porque, precisamente, nuestra idea es desarrollar una función que implemente las reglas de validación para uno o más tipos de componentes, de manera que simplemente tengamos que relacionar Función de validación con Tipo de Componente a través de este campo de la tabla. Y luego, la validación se lanzará automáticamente a través de un Trigger que ejecutará a la función correspondiente en cada momento que se ingrese o modifique un registro en la tabla Componentes. En el Listado 3 se muestra un ejemplo de datos ingresados en la tabla Tipos_Componente. INSERT INTO TIPOS_COMPONENTE (TIPO, DESCRIPCION, FUNCION_VALIDACION) VALUES (1, 'MONITOR', 'MONITOR_VALIDO'); INSERT INTO TIPOS_COMPONENTE (TIPO, DESCRIPCION, FUNCION_VALIDACION) VALUES (2, 'CHIP', 'CHIP_VALIDO'); INSERT INTO TIPOS_COMPONENTE (TIPO, DESCRIPCION, FUNCION_VALIDACION) VALUES (3, 'OTRO', NULL); COMMIT; Listado 3 . Tipos de componente y funciones asociadas. Ahora necesitaremos programar las funciones que realizarán la validación para cada tipo. Según lo especificado en la tabla, al tipo “monitor” lo validará siempre la función “Monitor_Válido”, al tipo “chip” la función “Chip_Válido” y el tipo “otro” no será validado. En el Listado 4 crearemos un paquete con las funciones especificadas. Se han incluído los Pragma necesarios para que estas funciones puedan ser invocadas como Funciones Sql (tal lo explicado en Mundo Delphi Nº 4, Oracle Pl/Sql - Desarrollo de funciones SQL). Para nuestro ejemplo, las funciones de validación serán muy sencillas: verificarán que el tamaño del código de componente ingresado en la tabla Componentes sea igual a 10, que las 3 primeras posiciones sean letras (“MON” para el caso de los monitores y “CHI” para el caso de los chips ... bueno, intuyo que nadie va a sorprenderse por lo ingenioso de ésto, ¿verdad?), y que las siguientes 7 posiciones sean numéricas y resulten en un valor superior a cero. Las funciones devolverán 0 en caso de error o 1 en caso contrario. CREATE OR REPLACE PACKAGE PKG_VALIDACIONES_TIPOS AS FUNCTION MONITOR_VALIDO (XCODIGO COMPONENTES.CODIGO%Type) RETURN NUMBER; PRAGMA RESTRICT_REFERENCES (MONITOR_VALIDO, WNDS, WNPS, RNPS); FUNCTION CHIP_VALIDO (XCODIGO COMPONENTES.CODIGO%Type) RETURN NUMBER; PRAGMA RESTRICT_REFERENCES 19
  • 21. Oracle PL/SQL – SQL dinámico (CHIP_VALIDO, WNDS, WNPS, RNPS); END; / CREATE OR REPLACE PACKAGE BODY PKG_VALIDACIONES_TIPOS AS FUNCTION MONITOR_VALIDO (XCODIGO COMPONENTES.CODIGO%Type) RETURN NUMBER AS BEGIN IF LENGTH(XCODIGO) != 10 OR SUBSTR(XCODIGO, 1, 3) != 'MON' OR TO_NUMBER(SUBSTR(XCODIGO, 4, 7)) <= 0 THEN RETURN 0; ELSE RETURN 1; END IF; EXCEPTION WHEN OTHERS THEN RETURN 0; END; FUNCTION CHIP_VALIDO (XCODIGO COMPONENTES.CODIGO%Type) RETURN NUMBER AS BEGIN IF LENGTH(XCODIGO) != 10 OR SUBSTR(XCODIGO, 1, 3) != 'CHI' OR TO_NUMBER(SUBSTR(XCODIGO, 4, 7)) <= 0 THEN RETURN 0; ELSE RETURN 1; END IF; EXCEPTION WHEN OTHERS THEN RETURN 0; END; END; / Listado 4 . Paquetes con funciones de validación. Antes dijimos que habrá un Trigger que se encargue de la tarea de verificar, por cada registro actualizado, la validez de su código. Ya mismo podríamos implementarlo poniendo una serie de “If Tipo = ... Then Función ” dentro del Trigger y asunto terminado. Sí, pero nada elegante, sobre todo si hay cientos de tipos diferentes para validar y la cosa iría peor cada vez que agreguemos tipos nuevos, o querramos que sea otra la función que valide a un determinado tipo. Al final de cuentas, ¡ya sabemos que podemos resolverlo con Sql dinámico!. Vayamos al Listado 5. Allí encontraremos e núcleo de todo este asunto. Se trata de una función de l validación de tipos de carácter “general”, que dependiendo del tipo de componente recibido como parámetro ejecutará la función que corresponda según lo definido en la tabla Tipos_Componente. Con esta información, nuestra función central armará dinámicamente el nombre del proceso que debe ejecutar, le agregará como parámetro el código de componente a validar, ejecutará la función, tomará el valor resultante y lo devolverá al proceso llamador. CREATE OR REPLACE FUNCTION ES_COMPONENTE_VALIDO (XTIPO TIPOS_COMPONENTE.TIPO%Type, XCODIGO COMPONENTES.CODIGO%Type) RETURN NUMBER AS C INTEGER; TMP INTEGER; nRES NUMBER; FX TIPOS_COMPONENTE.FUNCION_VALIDACION%Type; BEGIN 20
  • 22. Oracle PL/SQL – SQL dinámico -- obtenemos el nombre de la función que -- debemos invocar a fin de -- validar el tipo de componente SELECT FUNCION_VALIDACION INTO FX FROM TIPOS_COMPONENTE WHERE TIPO = XTIPO; -- Si no la encontramos o es Null -- entendemos que no está definida -- y salimos IF FX IS NULL THEN RAISE NO_DATA_FOUND; END IF; -- armamos todo el esquema dinámico -- para ejecutar la función -- pretendida a través de un select -- y obtener el resultado de la misma -- Abrimos un cursor y tomamos -- su manejador (número de identificación) C := DBMS_SQL.OPEN_CURSOR; -- Armamos la instrucción dinámica a -- ejecutar, concatenando el nombre -- de, en este caso, la función Sql -- que será ejecutada y declarando el -- parámetro que le enviaremos. -- Y todo ésto se asocia al cursor. DBMS_SQL.PARSE (C, 'SELECT PKG_VALIDACIONES_TIPOS.' || FX || '(:VCODIGO) FROM DUAL', DBMS_SQL.V7); -- Llenamos el parámetro que enviamos -- (el código del artículo/componente) DBMS_SQL.BIND_VARIABLE(C, ':VCODIGO', XCODIGO); -- Definimos la columna que recibiremos -- del Select. -- Declaramos que el tipo de dato de la -- columna “1” deberá ser del mismo tipo -- de dato que la variable nRes -- (o sea, numérico) DBMS_SQL.DEFINE_COLUMN(C, 1, nRES); -- Ejecutamos el cursor y tomamos -- la fila que recibimos. -- El “TRUE” significa que si se recibe más -- de una fila se dispare una excepción. TMP := DBMS_SQL.EXECUTE_AND_FETCH(C, TRUE); -- Tomamos el valor de la columna “1” -- (el resultado de la función ejecutada) -- y lo metemos en la variable nRes DBMS_SQL.COLUMN_VALUE(C, 1, nRES); -- Cerramos el cursor ya que -- si no lo hacemos no se desasigna la -- memoria utilizada. DBMS_SQL.CLOSE_CURSOR(C); -- Devolvemos el resultado RETURN nRES; EXCEPTION WHEN OTHERS THEN -- si hubo una excepción cerramos -- el cursor antes de salir IF DBMS_SQL.IS_OPEN(C) THEN DBMS_SQL.CLOSE_CURSOR(C); END IF; RETURN 1; END; / 21
  • 23. Oracle PL/SQL – SQL dinámico Listado 5 . Función central – Uso de Sql dinámico. Ya tenemos casi todo listo, sólo nos falta el Trigger. (Listado 6) CREATE OR REPLACE TRIGGER TBUI_COMPONENTES BEFORE UPDATE OR INSERT ON COMPONENTES FOR EACH ROW DECLARE eCODIGO_INVALIDO EXCEPTION; BEGIN -- enviamos a validar el código, -- para ésto llamamos a la función -- genérica, enviando los datos del -- registro IF ES_COMPONENTE_VALIDO (:NEW.TIPO,:NEW.CODIGO) = 0 THEN RAISE eCODIGO_INVALIDO; END IF; EXCEPTION WHEN eCODIGO_INVALIDO THEN RAISE_APPLICATION_ERROR(-20999, CODIGO INVALIDO PARA EL TIPO DE COMPONENTE'); RETURN; WHEN OTHERS THEN RAISE_APPLICATION_ERROR(-20999, 'ERROR: ' || TO_CHAR(SQLCODE) || ' - ' || SUBSTR(SQLERRM, 1, 256)); RETURN; END; / Listado 6 . Trigger sobre tabla Componentes Ahora, cada vez que se inserte o actualice un registro de la tabla Componentes se disparará automáticamente el Trigger TBUI_Componentes (las siglas “TBUI” las uso por trigger before update insert). Luego, el Trigger invocará a la función genérica Es_Componente_Valido, enviándole el tipo y componente ingresados sobre la fila tratada. En caso de recibir de la función el valor 0 (error), nos enviará una excepción alertándonos sobre tal situación, impidiéndose así que la fila se grabe en la tabla. Finalmente, en la Figura 7 podremos ver un ejemplo de lo que pasaría si intentamos ingresar un código de componente no válido para el tipo especificado (empieza con “SON” en lugar de “MON”). Utilizaremos para la prueba el SqlPlus. SQL> INSERT INTO COMPONENTES (CODIGO, TIPO, DESCRIPCION) SQL> VALUES ('SON0000000', 1, 'SVGA 17"'); ERROR at line 1: ORA-20999: CODIGO INVALIDO PARA EL TIPO DE COMPONENTE ORA-06512: at line 10 ORA-04088: error during execution of trigger ‘TBUI_COMPONENTES' Figura 7 . Intento de inserción fallido. 22