Oracle
Oracle
Autor:
Eduardo Bottini
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.
.
PRIMARY KEY (CODIGO_AUTOR)
.
);
CREATE INDEX XIE1AUTORES ON AUTORES (NOMBRE);
.
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 d
eclaradas bajo este
tipo de dato deberán apuntar obligatoriamente a cursores de un tipo de dato equiva
lente. 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 definic
ió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 c
omo 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 procedimi
ento o función
almacenada dentro del servidor.
.
TYPE REFCURS_AUTORES IS REF CURSOR RETURN AUTORES%ROWTYPE;
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.
AS
BEGIN
OPEN RES FOR SELECT * FROM AUTORES;
RETURN;
END;
/
Ó
.
EXEC :R := FRC_TRAE_AUTORES_O_LIBROS('A');
.
Y luego:
PRINT R;
(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;
.
DECLARE
nombre_autor AUTORES.NOMBRE%TYPE,
fecha_nacimiento AUTORES.FECHA_NACIMIENTO%TYPE
);
Oracle PL/SQL Variables de tipo Cursor.
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 pantall
a
LOOP
FETCH Res INTO RecRes;
EXIT WHEN Res%NOTFOUND;
DBMS_OUTPUT.PUT_LINE ( Nombre Libro: || RecRes.nombre_libro);
6
Oracle PL/SQL Variables de tipo Cursor.
Oracle PL/SQL Variables de tipo Cursor.
sp.Free;
End;
End;
.
Oracle PL/SQL programación con Paquetes.
Oracle PL/SQL programación con Paquetes.
Siguiendo con nuestro tutorial sobre elementos avanzados de programación O
racle 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ógi
camente. Esta
estructura se denomina Package (Paquete) y su uso nos permite no sólo mejorar la c
alidad 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 ob
jetos 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, proce
dimientos 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];
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 trigge
rs externos al paquete como
desde aplicaciones escritas en otros lenguajes o mismo desde herramientas como e
l Sql*Plus.
Para referenciar objetos desde adentro del mismo paquete donde han sido declarad
os no es necesario
especificar el nombre del paquete y pueden (deberían) ser referenciados directamen
te por su nombre.
Finalmente y siguiendo a la parte declarativa del cuerpo de un paquete puede, op
cionalmente, incluirse la
sección de inicialización del paquete. En esta sección pueden, por ejemplo, inicializa
rse variables que
Oracle PL/SQL programación con Paquetes.
Oracle PL/SQL programación con Paquetes.
utiliza el paquete tal como veremos en el ejemplo final. La sección de inicializac
ión se ejecuta sólo la
primera vez que una aplicación referencia a un paquete, ésto es, se ejecuta sólo una v
ez por sesión.
.
y ahora el Paquete:
-- Especificación del paquete-- Sólo se declara aquí el único elemento que será visible
.
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 refe
rencia al paquete
BEGIN
WG_FECHA_INICIO_SESION := SYSDATE;
WG_SECUENCIA := 0;
END;
/
.
Para ejecutar la función Alta, basta escribir desde el Sql*Plus:
Modificar datos en tablas, por lo tanto no puede invocar sentencias Insert, Upda
te 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 c
omo
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 enu
meradas. 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ód
igo 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úm
ero 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 ta
bla 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 me
diante nuestra función
(figura 2).
12
Oracle PL/SQL Desarrollo de funciones SQL
Oracle PL/SQL Desarrollo de funciones SQL
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)
);
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;
RES := 0;
END LOOP;
DIG := 11 -MOD(RES, 11);
IF DIG = 11 THEN DIG := 0; END IF;
EXCEPTION
SQL>
SQL>
AS
FUNCTION CUIT_VALIDO (XID VARCHAR2) RETURN NUMBER;
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
Oracle PL/SQL Desarrollo de funciones SQL
SQL> SELECT Pkg_Valida.cuit_valido('20224367911')
SQL> AS VALIDO FROM DUAL;
VALIDO
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 d
atos 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;
INVALIDOS
SQL>
16
Oracle PL/SQL SQL dinámico
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.CLOSE_CURSOR(ID);
END;
);
REFERENCES TIPOS_COMPONENTE
);
COMMIT;
FUNCTION MONITOR_VALIDO
(XCODIGO COMPONENTES.CODIGO%Type)
RETURN NUMBER;
PRAGMA RESTRICT_REFERENCES
FUNCTION CHIP_VALIDO
(XCODIGO COMPONENTES.CODIGO%Type)
RETURN NUMBER;
PRAGMA RESTRICT_REFERENCES
Oracle PL/SQL SQL dinámico
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;
/
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
Oracle PL/SQL SQL dinámico
-- 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;
C := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE (C,
'SELECT PKG_VALIDACIONES_TIPOS.' ||
FX || '(:VCODIGO)
FROM DUAL', DBMS_SQL.V7);
DBMS_SQL.DEFINE_COLUMN(C, 1, nRES);
DBMS_SQL.COLUMN_VALUE(C, 1, nRES);
DBMS_SQL.CLOSE_CURSOR(C);
-- Devolvemos el resultado
RETURN nRES;
EXCEPTION
WHEN OTHERS THEN
IF DBMS_SQL.IS_OPEN(C) THEN
DBMS_SQL.CLOSE_CURSOR(C);
END IF;
RETURN 1;
END;
/
Oracle PL/SQL SQL dinámico
Oracle PL/SQL SQL dinámico
Ya tenemos casi todo listo, sólo nos falta el Trigger. (Listado 6)
CREATE OR REPLACE TRIGGER TBUI_COMPONENTES
ON COMPONENTES
FOR EACH ROW
DECLARE
eCODIGO_INVALIDO EXCEPTION;
BEGIN
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;
/
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'