90
Introducción a PL/SQL Autor: Héctor Tévar Septiembre 2008

Tutorial PL SQL - Dic 2012

Embed Size (px)

Citation preview

Page 1: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

0

Introducción a

PL/SQL

Autor: Héctor Tévar

Septiembre 2008

Page 2: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

1

SQL – Structured Query Language En sistemas de base de datos relacionales los datos son representados usando tablas (relaciones). Una consulta emitida contra la base de datos nos devuelve una tabla. Las tablas tienen la estructura siguiente:

Columna1 Columna2 Columna N ........................... ......................... ......................... ..............................

Una tabla es únicamente identificada por su nombre y consiste en filas que contienen la información almacenada, cada fila que contiene exactamente una tupla (o registro). Una tabla puede tener una o varias columnas. Una columna esta compuesta de un nombre de columna y un tipo de datos, y esta describe un atributo de la tupla. La estructura de una tabla, también llamada esquema de relación, es definida por sus atributos. El tipo de información que se almacena en una tabla es definido por los tipos de datos de los atributos durante la creación de la misma. SQL usa los términos de tabla, fila, y columna para establecer relaciones (tupla, y atributo, respectivamente). Una tabla puede tener hasta 254 columnas que pueden tener tipos de datos diferentes o iguales y conjuntos de valores (dominios), respectivamente. Los dominios posibles son datos alfanuméricos (cadenas), números y formatos de fecha. Oracle ofrece los siguientes tipos de datos básicos: Tipos de datos: CHAR:

• Tienen una longitud fija. • Almacena de 1 a 255. • Si se introduce una cadena de menos longitud que la definida se rellenara con blancos a la

derecha hasta quedar completada. • Si se introduce una cadena de mayor longitud que la fijada nos dará un error.

VARCHAR:

• Almacena cadenas de longitud variable. • La longitud máxima es de 2000 caracteres. • Si se introduce una cadena de menor longitud que la que esta definida, se almacena con esa

longitud y no se rellenara con blancos ni con ningún otro carácter a la derecha hasta completar la longitud definida.

• Si se introduce una cadena de mayor longitud que la fijada, nos dará un error NUMBER:

• Se almacenan tanto enteros como decimales. • Number (precisión, escala) • Ejemplo:

X=number (7,2) X=155'862 à Error ya que solo puede tomar 2 decimales

Page 3: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

2

X= 155'86 à Bien Nota: El rango máximo va de 1 a 38. LONG:

• No almacena números de gran tamaño, sino cadenas de caracteres de hasta 2 GB DATE:

• Almacena la fecha. Se almacena de la siguiente forma: Siglo/Año/Mes/Día/Hora/Minutos/Segundos RAW:

• Almacena cadenas de Bytes (gráficos, sonidos…) LONGRAW:

• Como el anterior pero con mayor capacidad. ROWID:

• Posición interna de cada una de las columnas de las tablas.

Sentencias de consultas de datos Select: Select [ALL | Distinct] [expresión_columna1, expresión_columna2, …., | *] From [nombre1, nombre_tabla1, …, nombre_tablan] {[Where condición] [Order By expresión_columna [Desc | Asc]…]}; Vamos a explicar como leer la consulta anterior y así seguir la pauta para todas las demás. Cuando ponemos ignifica que debemos la que va dentro debe existir, y si además ponemos | significa que deberemos elegir un valor de los que ponemos y no mas de uno. En cambio si ponemos {} significa que lo que va dentro de las llaves puede ir o no, es decir es opcional y se pondrá según la consulta. Nota: En el select el valor por defecto entre ALL y DISTINCT es ALL.

• Alias = El nuevo nombre que se le da a una tabla. Se pondrá entre comillas • Order By = Ordena ascendentemente (Asc) (valor por defecto) o descendentemente (Desc). • All = Recupera todas las filas de la tabla aunque estén repetidas. • Distinct = Solo recupera las filas que son distintas. • Desc Emple; = Nos da un resumen de la tabla y sus columnas. En este caso de la tabla Emple. • Not Null= Si aparece en una lista de una columna significa que la columna no puede tener

valores nulos. • Null= Si está nulo.

Nota: Nótese que cada consulta de SQL que hagamos hemos de terminarla con un punto y coma ";".

Page 4: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

3

Varios ejemplos para verlo mas claro:

SELECT EMPNO, ENAME, JOB, DEPTNO FROM EMP WHERE DEPTNO = 10 ORDER BY EMPNO;

Este ejemplo mostrar el número de empleado (empno), el nombre (ename), El tipo de trabajo que realiza (job), y El numero de departamento al que pertenece. Seleccionara todos los datos de la tabla empleados donde (Where) el numero de departamento (depto) sea igual a 10 y se ordenara (order by) por número de empleado. Notemos también que no pone ni 'Distinct' ni 'All'. Por defecto generara la sentencia con ALL.

SELECT * FROM EMP WHERE DEPTNO = 10 ORDER BY EMPNO;

Este ejemplo muestra todos los campos de la tabla emp donde (Where) donde El numero de departamento Es 10 y lo ordena por nombre de empleado. Al no poner nada se presupone que es ascendentemente (Asc).

SELECT * FROM EMP WHERE DEPTNO = 10 AND JOB = 'MANAGER’ ORDER BY ENAME DESC, DEPTNO ASC;

En este ejemplo selecciona todos los campos de la tabla empleados donde (Where) el número Del departamente sea igual a 10 y El trabajo sea igual a 'NAMAGER'. Por ultimo los ordena por 'ENAME' descendentemente y por numero de departamento ascendentemente. Tipos de operadores. Operadores aritméticos:

+ Suma - Resta * Multiplicación / División

Operadores de comparación y lógicos:

!= Distinto >= Mayor o igual que <= Menor o igual que = Igual que Like Se utiliza para unir cadenas de caracteres % Representa cualquier cadena de caracteres de 0 o más caracteres _ Representa un único carácter cualquiera Not Negación And Y, a and b, Cierto si son ciertas a y b Or O, a or b, Cierto si a o b o ambas son ciertas

Page 5: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

4

Ejemplos: Obtenemos los datos de los empleados cuyo nombre empieza con una "S":

SELECT * FROM EMP WHERE ENAME LIKE 'S%';

Obtenemos aquellos nombres que tengan una "L" en la segunda posición:

SELECT ENAME FROM EMP WHERE ENAME LIKE '_L%';

Obtenemos aquellos nombres que empiezan por "A" y tiene una "E" en su interior:

SELECT ENAME FROM EMP WHERE ENAME LIKE 'A%E%';

Comprobación con conjuntos de valores:

• In= permite saber si una expresión pertenece o no a un conjunto de valores. • Between= permite saber si una expresión esta o no entre esos valores:

Ejemplo:

SELECT ENAME FROM EMP WHERE SAL IN (1600, 2450);

Selecciona los nombres de los empleados donde El salario esta entre 1600 dólares y 2450.

SELECT ENAME FROM EMP WHERE SAL NOT BETWEEN 1600 AND 2450;

Selecciona los nombres de los empleados donde el salario de estos no este entre (Not Between) 1600 y 2450. Subconsultas: Consulta que se hace sobre los datos que nos da otra consulta. Su formato es: SELECT______ FROM________ WHERE CONDICION OPERADOR (SELECT ______ FROM ___________ WHERE CONDICION OPERADOR); Ejemplo: Obtenemos los nombrs de los empleados que trabajan en El mismo departamento que "TURNER":

SELECT ENAME FROM EMP WHERE DEPTNO = (SELECT DEPTNO FROM EMP WHERE ENAME LIKE 'TURNER');

Page 6: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

5

Funciones de valores simples: ABS(n)= Devuelve el valor absoluto de (n). CEIL(n)=Obtiene el valor entero inmediatamente superior o igual a "n". FLOOT(n) = Devuelve el valor entero inmediatamente inferior o igual a "n". MOD (m, n)= Devuelve el resto resultante de dividir "m" entre "n". NVL (valor, expresión)= Sustituye un valor nulo por otro valor. POWER (m, exponente)= Calcula la potencia de un numero. ROUND (numero [, m])= Redondea números con el numero de dígitos de precisión indicados. SIGN (valor)= Indica el signo del "valor". SQRT(n)= Devuelve la raíz cuadrada de "n". TRUNC (numero, [m])= Trunca números para que tengan una cierta cantidad de dígitos de precisión. VARIANCE (valor)= Devuelve la varianza de un conjunto de valores. Funciones de grupos de valores: AVG(n)= Calcula el valor medio de "n" ignorando los valores nulos.

SELECT DEPTNO, AVG (SAL) "SALARIO MEDIO" FROM EMP GROUP BY DEPTNO;

COUNT (* | Expresión)= Cuenta el numero de veces que la expresión evalúa algún dato con valor no nulo. La

select count(*) "Numero de Empleados" from EMP; opción "*" cuenta todas las filas seleccionadas. MAX (expresión)= Calcula el máximo. MIN (expresión)= Calcula el mínimo.

select DEPTNO, min(SAL), max(SAL) from EMP group by DEPTNO;

SUM (expresión)= Obtiene la suma de los valores de la expresión.

select sum(SAL) from EMP where DEPTNO = 30;

GREATEST (valor1, valor2…)= Obtiene el mayor valor de la lista. LEAST (valor1, valor2…)= Obtiene el menor valor de la lista. Funciones que devuelven valores de caracteres: CHR(n) = Devuelve el carácter cuyo valor en binario es equivalente a "n". CONCAT (cad1, cad2)= Devuelve "cad1" concatenada con "cad2". LOWER (cad)= Devuelve la cadena "cad" en minúsculas. UPPER (cad)= Devuelve la cadena "cad" en mayúsculas. INITCAP (cad)= Convierte la cadena "cad" a tipo titulo. LPAD (cad1, n[,cad2])= Añade caracteres a la izquierda de la cadena hasta que tiene una cierta longitud. RPAD (cad1, n[,cad2])= Añade caracteres a la derecha de la cadena hasta que tiene una cierta longitud. LTRIM (cad [,set])= Suprime un conjunto de caracteres a la izquierda de la cadena. RTRIM (cad [,set])= Suprime un conjunto de caracteres a la derecha de la cadena. REPLACE (cad, cadena_busqueda [, cadena_sustitucion])= Sustituye un carácter o caracteres de una cadena con 0 o mas caracteres.

Page 7: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

6

SUBSTR (cad, m [,n])= Obtiene parte de una cadena. TRANSLATE (cad1, cad2, cad3)= Convierte caracteres de una cadena en caracteres diferentes, según un plan de sustitución marcado por el usuario. Funciones que devuelven valores numéricos: ASCII(cad)= Devuelve el valor ASCII de la primera letra de la cadena "cad". INSTR (cad1, cad2 [, comienzo [,m]])= Permite una búsqueda de un conjunto de caracteres en una cadena pero no suprime ningún carácter después. LENGTH (cad)= Devuelve el numero de caracteres de cad. Funciones para el manejo de fechas: SYSDATE= Devuelve la fecha del sistema. ADD_MONTHS (fecha, n)= Devuelve la fecha "fecha" incrementada en "n" meses. LASTDAY (fecha)= Devuelve la fecha del último día del mes que contiene "fecha". MONTHS_BETWEEN (fecha1, fecha2)= Devuelve la diferencia en meses entre las fechas "fecha1" y "fecha2". NEXT_DAY (fecha, cad)= Devuelve la fecha del primer día de la semana indicado por "cad" después de la fecha indicada por "fecha". Funciones de conversión: TO_CHAR= Transforma un tipo DATE ó NUMBER en una cadena de caracteres.

Select concat('$',to_char(SAL)) from EMP;

TO_DATE= Transforma un tipo NUMBER ó CHAR en DATE.

Select to_date(HIREDATE) from EMP;

TO_NUMBER= Transforma una cadena de caracteres en NUMBER. Agrupación de elementos. Group by y Having: La sentencia "Select" posibilita agrupar uno o más conjuntos de filas. El agrupamiento se lleva a cabo mediante la cláusula "GROUP BY" por las columnas especificadas y en el orden especificado. Formato: SELECT… FROM… GROUP BY COLUMNA1, COLUMNA2, COLUMNAN… HAVING CONDICION GROUP BY …

select DEPTNO, min(SAL), max(SAL) from EMP group by DEPTNO;

Los datos seleccionados en la sentencia "Select" que lleva el "Group By" deben ser:

• Una constante. • Una función de grupo (SUM, COUNT, AVG…) • Una columna expresada en el Group By.

Page 8: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

7

La cláusula Group By sirve para calcular propiedades de uno o más conjuntos de filas. Si se selecciona más de un conjunto de filas, Group By controla que las filas de la tabla original sean agrupadas en un temporal. La cláusula Having se emplea para controlar cual de los conjuntos de filas se visualiza. Se evalúa sobre la tabla que devuelve el Group By. No puede existir sin Group By. Having es similar al Where, pero trabajo con grupos de filas; pregunta por una característica de grupo, es decir, pregunta por los resultados de las funciones de grupo, lo cual Where no pude hacer.

Select DEPTNO, min(SAL), max(SAL) from emp where job like 'CLERK' group by DEPTNO having count(*)>1;

Esta sentencia selecciona El mínimo salario y El máximo de la tabla empleados teniendo en cuenta que solo buscará aquellos trabajos que sean de tipo CLERK y en donde haya más de uno. Combinación externa (outer joins): Nos permite seleccionar algunas filas de una tabla aunque estas no tengan correspondencia con las filas de la otra tabla con la que se combina. Formato: SELECT TABLA1.COLUMNA1, TABLA1.COLUMNA2, TABLA2.COLUMNA1, TABLA2.COLUMNA2 FROM TABLA1, TABLA2 WHERE TABLA1.COLUMNA1 = TABLA2.COLUMNA1 (+); Esto selecciona todas las filas de la tabla "tabla1" aunque no tengan correspondencia con las filas de la tabla "tabla2", se utiliza el símbolo +. El resto de columnas de la tabla "tabla2" se rellena con NULL.

SELECT EMP.ENAME, EMP.SAL, BONUS.COMM FROM EMP, BONUS WHERE EMP.ENAME = BONUS.ENAME (+);

Union, intersec y minus: Permite combinar los resultados de varios "Select" para obtener un único resultado. Formato: SELECT… FROM… WHERE… OPERADOR_DE_CONJUNTO SELECT…FROM…WHERE… UNION= Combina los resultados de dos consultas. Las filas duplicadas que aparecen se reducen a una fila única.

select EMPNO, ENAME from EMP union select EMPNO, ENAME from EMP2;

UNION ALL= Como la anterior pero aparecerán nombres duplicados.

select EMPNO, ENAME from EMP union all select EMPNO, ENAME from EMP2;

INTERSEC= Devuelve las filas que son iguales en ambas consultas. Todas las filas duplicadas serán eliminadas.

Page 9: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

8

Select * from emp intersect select * from emp2;

MINUS= Devuelve aquellas filas que están en la primera "Select" y no están en la segunda "Select". Las filas duplicadas del primer conjunto se reducirán a una fila única antes de que empiece la comparación con el otro conjunto.

select _ from EMP minus select _ from EMP2;

Reglas para la utilización de operadores de conjunto: • Las columnas de las dos consultas se relacionan en orden, de izquierda a derecha. • Los nombres de columna de la primera sentencia "Select" no tiene porque ser los mismos que los

nombres de la segunda. • Los "Select" necesitan tener el mismo numero de columnas. • Los tipos de datos deben coincidir, aunque la longitud no tiene que ser la misma.

Insert, Update y Delete: Insert: Se añaden filas de datos en una tabla: INSERT INTO NOMBRETABLA [(COL [,COL]…)] VALUES (VALOR [,VALOR]…); Nombretabla= Es la tabla en la que se van a insertar las filas. Propiedades: que sepas que mi manera manera manera

• Si las columnas no se especifican en la cláusula Insert se consideran, por defecto, todas las columnas de la tabla.

• Las columnas a las que damos valores se identifican por su nombre. • La asociación columna valor es posicional. • Los valores que se dan a las columnas deben coincidir con el tipo de dato definido en la

columna. • Los valores constantes de tipo carácter han de ir encerrados entre comillas simples (' ') (los de

tipo fecha también). Con Select: Se añaden tantas filas como devuelva la consulta: INSERT INTO NOMBRETABLA [(COL [,COL]…)] SELECT {COLUMNA [, COLUMNA]… | *} FROM NOMBRETABLA2 [CLAUSULAS DE SELECT]; Insert into EMP2 (EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM, DEPTNO) Select * from emp where ENAME LIKE 'TURNER' OR ENAME LIKE 'ADAMS'; Update:

Page 10: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

9

Actualiza los valores de las columnas para una o varias filas de una tabla: UPDATE NOMBRETABLA SET COLUMNA1= VALOR1, …, COLUMNAN= VALORN WHERE CONDICION; Set= Indica las columnas que se van a actualizar y sus valores.

UPDATE EMP2 SET ENAME='MARIA JOSE' WHERE ENAME LIKE 'MARIA'; Con Select: Cuando la subconsulta (orden select) forma parte de SET, debe seleccionar el mismo numero de columnas, (con tipos de datos adecuados) que los que hay entre paréntesis al lado de SET. UPDATE NOMBRETABLA SET COLUMNA= VALOR1, COLUMNA2= VALOR2, … WHERE COLUMNA3= (SELECT…) Ó UPDATE NOMBRETABLA SET (COLUMNA1, COLUMNA2, …)= (SELECT …) WHERE CONDICION;

UPDATE EMP2 SET HIREDATE = (Select HIREDATE FROM EMP WHERE ENAME LIKE 'TURNER') WHERE ENAME LIKE 'MARIA JOSE';

Delete: Elimina una o varias filas de una tabla: DELETE [FROM] NOMBRETABLA WHERE CONDICION; DELETE FROM EMP2 WHERE ENAME LIKE 'TURNER'; Definición de claves primarias y restricciones: Rollback: Permite ir hasta el último COMMIT hecho o en su defecto hasta el comienzo de las órdenes con lo que estas no se ejecutan. Commit: Cuando ejecutamos ordenes estas no son creadas en la tabla hasta que ponemos este orden, por tanto los cambios realizados se perderán si al salir del programa no realizamos esta acción. Puede programarse para que lo haga automáticamente. Algunas ordenes que lleven COMMIT implícito:

• QUIT • EXIT • CONNECT • DISCONNECT • CREATE TABLE

Page 11: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

10

• CREATE VIEW • GRANT • REVOQUE • DROP TABLE • DROP VIEW • ALTER • AUDIT • NO AUDIT

Creación de una tabla: Su primer carácter debe ser alfabético y el resto pueden ser letras, números y el carácter subrayado. CREATE TABBLE NOMBRETABLA (COLUMNA1 TIPO_DATO {NOT NULL}, COLUMNA2 TIPO_DATO {NOT NULL}, … ) TABLESPACE ESPACIO_DE_TABLA; create table EMP3 ( EMPNO NUMBER(4) NOT NULL, ENAME VARCHAR(10) NOT NULL, JOB VARCHAR2(9), MGR NUMBER(4), HIREDATE DATE, SAL NUMBER(7,2), COMM NUMBER (7,2), DEPTNO NUMBER(2)) TABLESPACE SYSTEM; Características:

• Las definiciones individuales de columnas se separan mediante comas. • No se pone coma después de la última definición de columna. • Las mayúsculas y minúsculas son indiferentes.

Los usuarios pueden consultar las tablas creadas por medio de la vista USER_TABLES. Integridad de datos: La integridad hace referencia al hecho de que los datos de la base de datos han de ajustarse a restricciones antes de almacenarse en ella. Una restricción de integridad será: Una regla que restringe el rango de valores para una o más columnas en la tabla. Restricciones en create table: Usamos la cláusula CONSTRAINT, que puede restringir una sola columna o un grupo de columnas de una misma tabla. Hay dos modos de especificar restricciones:

• Como parte de la definición de columnas. • Al final, una vez especificados todas las columnas.

Formato: CREATE TABLE NOMBRE_TABLA (COLUMNA1 TIPO_DE_DATO {CONSTRAINT NOMBRE_RESTRICCION} {NOT NULL}

Page 12: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

11

{UNIQUE} {PRIMARY KEY} {DEFAULT VALOR} {REFERENCES NOMBRETABLA [(COLUMNA, [,COLUMNA]) {ON DELETE CASCADE}} {CHECK CONDICION}, COLUMNA2... ) {TABLESPACE ESPACIO_DE_TABLA} ; CREATE TABLE NOMBRE_TABLA (COLUMNA1 TIPO_DATO , COLUMNA2 TIPO_DATO, COLUMNA3 TIPO_DATO, .. {CONSTRAINT NOMBRERESTRICCION} [{UNIQUE} | {PRIMARY KEY} (COLUMNA [, COLUMNA])], {CONSTRAINT NOMBRERESTRICCION} {FOREIGN KEY (COLUMNA [, COLUMNA]) REFERENCES NOMBRETABLA {(COLUMNA [, COLUMNA]) {ON DELETE CASCADE}}, {CONSTRINT NOMBRERESTRICCIONI} {CHECK (CONDICION)} … )[TABLESPACE ESPACIO_DE_TABLA]; Clave primaria: Primary key Es una columna o un conjunto de columnas que identifican unívocamente a cada fila. Debe ser única, no nula y obligatoria. Como máximo, podemos definir una clave primaria por tabla. Esta clave se puede referenciar por una columna o columnas. Cuando se crea una clave primaria, automáticamente se crea un índice que facilita el acceso a la tabla. Formato de restricción de columna: CREATE TABLE NOMBRE_TABLA (COL1 TIPO_DATO [CONSTRAINT NOMBRE_RESTRICCION] PRIMARY KEY COL2 TIPO_DATO … )[TABLESPACE ESPACIO_DE_TABLA]; create table tabla1 ( PNO number(3) constraint prj_pk primary key, PNAME varchar2(60) unique, PMGR number(4) not null, PERSONS number(5), BUDGET number(8,2) not null, PSTART date, PEND date) TABLESPACE SYSTEM; Formato de restricción de tabla: CREATE TABLE NOMBER_TABLA (COL1 TIPO_DATO, COL2 TIPO_DATO,

Page 13: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

12

… [CONSTRAINT NOMBRERESTRICCION] PRIMARY KEY (COLUMNA [,COLUMNA]), … )[TABLESPACE ESPACIO_DE_TABLA]; create table EMP4( ENAME varchar2(30) constraint check_name check(ENAME = upper(ENAME) ), SAL number(5,2) constraint check_sal check(SAL >= 500), DEPTNO number(3) constraint check_deptno check(DEPTNO between 10 and 100) ); Claves ajenas: Foreign Key: Esta formada por una o varias columnas que están asociadas a una clave primaria de otra o de la misma tabla. Se pueden definir tantas claves ajenas como se precise, y pueden estar o no en la misma tabla que la clave primaria. El valor de la columna o columnas que son claves ajenas debe ser: NULL o igual a un valor de la clave referenciada (regla de integridad referencial). Formato de restricción de columna: CREATE TABLE NOMBRE_TABLA (COLUMNA1 TIPO_DATO [CONSTRAINT NOMBRERESTRICCION] REFERENCES NOMBRETABLA [(COLUMNA)] [ON DELETE CASCADE] … )[TABLESPACE ESPACIO_DE_TABLA]; Formato de restricción de tabla: CREATE TABLE NOMBRE_TABLA (COLUMNA1 TIPO_DATO, COLUMNA2 TIPO_DATO, … [CONTRAINT NOMBRERESTRICCION] FOREIGN KEY (COLUMNA [,COLUMNA]) REFERENCES NOMBRETABLA [(COLUMNA [, COLUMNA])] [ON DELETE CASCADE], )[TABLESPACE ESPACIO_DE_TABLA]; Ejemplo:

create table DEPARTAMENTOS( DEPTNO number(3) constraint dept_pk primary key, PMGR number(4) not null constraint fk_dept references EMP);

create table EMPLEADOS( EMPNO number(4) constraint emp_pk primary key, DEPTNO number(3) constraint dept_fk references DEPARTAMENTOS(DEPTNO) );

Page 14: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

13

Notas:

• En la cláusula REFERENCES indicamos la tabla a la cual remite la clave ajena. • Hay que crear primero una tabla y después aquella que le hace referencia. • Hay que borrar primero la tabla que hace referencia a otra tabla y después la tabla que no hace

referencia. • Borrado en cascada (ON DELETE CASCADE): Si borramos una fila de una tabla maestra, todas

las filas de la tabla detalle cuya clave ajena sea referenciada se borraran automáticamente. La restricción se declara en la tabla detalle. El mensaje "n filas borradas" solo indica las filas borradas de la tabla maestra.

NOT NULL: Significa que la columna no puede tener valores nulos. DEFAULT: Le proporcionamos a una columna un valor por defecto cuando el valor de la columna no se especifica en la cláusula INSERT. En la especificación DEFAULT es posible incluir varias expresiones: constantes, funciones SQL y variables UID y SYSDATE. Verificación de restricciones: CHECK: Actúa como una cláusula where. Puede hacer referencia a una o más columnas, pero no a valores de otras filas. En una cláusula CHECK no se pueden incluir subconsultas ni las pseudoconsultas SYSDATE, UID y USER. Nota: La restricción NOT NULL es similar a CHECK (NOMBRE_COLUMNA IS NOT NULL) UNIQUE: Evita valores repetidos en la misma columna. Puede contener una o varias columnas. Es similar a la restricción PRIMARY KEY, salvo que son posibles varias columnas UNIQUE definidas en una tabla. Admite valores NULL. Al igual que en PRIMARY KEY, cuando se define una restricción UNIQUE se crea un índice automáticamente. Vistas del diccionario de datos para las restricciones: Contienen información general las siguientes: USER_CONSTRAINTS: Definiciones de restricciones de tablas propiedad del usuario. ALL_CONSTRAINTS: Definiciones de restricciones sobre tablas a las que puede acceder el usuario. DBA_CONSTRAINTS: Todas las definiciones de restricciones sobre todas las tablas. Creación de una tabla con datos recuperados en una consulta: CREATE TABLE: permite crear una tabla a partir de la consulta de otra tabla ya existente. La nueva tabla contendrá los datos obtenidos en la consulta. Se lleva a cabo esta acción con la cláusula AS colocada al final de la orden CREATE TABLE. CREATE TABLE NOMBRETABLA (COLUMNA [,COLUMNA] )[TABLESPACE ESPACIO_DE_TABLA] AS CONSULTA;

Page 15: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

14

CREATE TABLE COPIA_EMP AS SELECT * FROM EMP WHERE ENAME LIKE 'SCOTT'; No es necesario especificar tipos ni tamaño de las consultas, ya que vienen determinadas por los tipos y los tamaños de las recuperadas en la consulta. La consulta puede tener una subconsulta, una combinación de tablas o cualquier sentencia select valida. Las restricciones CON NOMBRE no se crean en una tabla desde la otra, solo se crean aquellas restricciones que carecen de nombre. Creación de una tabla especificando cantidad de espacio y tablespace:

create table EMP5( ENAME varchar2(30) constraint check_name check(ENAME = upper(ENAME) ), SAL number(5,2) constraint check_sal check(SAL >= 500), DEPTNO number(3) constraint check_deptno check(DEPTNO between 10 and 100) ) storage (initial 1M next 400k minextents 1 maxextents 20 pctincrease 50) TABLESPACE SYSTEM;

Creamos una tabla con tres columnas y la ubicaremos en un segmento de 1M de espacio, que cuando este lleno que se incremente con 400K y que las próximas veces que se rebase ester espacio, se incremente en un 50% sobre El tamaño que tenía. Este segmento es una parte del tablespace SYSTEM

Supresión de tablas: DROP TABLE: suprime una tabla de la base de datos. Cada usuario puede borrar sus propias tablas, pero solo el administrador o algún usuario con el privilegio "DROP ANY TABLE" puede borrar las tablas de otro usuario. Al suprimir una tabla también se suprimen los índices y los privilegios asociados a ella. Las vistas y los sinónimos creados a partir de esta tabla dejan de funcionar pero siguen existiendo en la base de datos por tanto deberíamos eliminarlos. Ejemplo: DROP TABLE [USUARIO].NOMBRETABLA [CASCADE CONSTRAINTS]; DROP TABLE COPIA_EMP; TRUNCATE: permite suprimir todas las filas de una tabla y liberar el espacio ocupado para otros usos sin que reaparezca la definición de la tabla de la base de datos. Una orden TRUNCATE no se puede anular, como tampoco activa disparadores DELETE. TRUNCATE TABLE [USUARIO.]NOMBRETABLA [{DROP | REUSE} STORAGE]; TRUNCATE TABLE SCOTT.EMP4; TRUNCATE TABLE SCOTT.EMP4 REUSE STORAGE; -- Para reutilizar el espacio ocupado. TRUNCATE TABLE SCOTT.EMP4 DROP STORAGE; -- Para despreciar el espacio ocupado. Modificación de tablas: Se modifican las tablas de dos formas: Cambiando la definición de una columna (MODIFY) ó añadiendo

Page 16: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

15

una columna a una tabla existente (ADD): Formato: ALTER TABLE NOMBRETABLA {[ADD (COLUMNA [,COLUMNA]…)] [MODIFY (COLUMNA [,COLUMNA]…)] [ADD CONSTRAINT RESTRICCION] [DROP CONSTRAINT RESTRICCION]}; ADD= Añade una columna o mas al final de una tabla. ALTER TABLE EMP5 ADD DESCUENTOS number(5); MODIFY= Modifica una o mas columnas existentes en la tabla.

ALTER TABLE EMP5 MODIFY DESCUENTOS number(3);

DROP COLUMN = Eleimina una columna de la tabla ALTER TABLE EMP5 DROP COLUMN DESCUENTOS; ADD CONSTRAINT= Añade una restricción a la definición de la tabla.

ALTER TABLE EMP5 ADD CONSTRAINT ck_comm2 CHECK (SAL<=1000);

DROP CONSTRAINT= Elimina una restricción de la tabla.

ALTER TABLE EMP5 DROP CONSTRAINT ck_comm2; A la hora de añadir una columna a una tabla hay que tener en cuenta: • Si la columna no esta definida como NOT NULL se le puede añadir en cualquier momento.

• Si la columna esta definida como NOT NULL se pueden seguir estos pasos:

1. Se añade una columna sin especificar NOT NULL. 2. Se da valor a la columna para cada una de las filas. 3. Se modifica la columna NOT NULL.

Al modificar una columna de duna tabla se han de tener en cuenta:

• Se puede aumentar la longitud de una columna en cualquier momento.

• Es posible aumentar o disminuir el numero de posiciones decimales en una columna de tipo NUMBER.

• Si la columna es NULL en todas las filas de la tabla, se puede disminuir la longitud y modificar el tipo de dato

• La opción MODIFY… NOT NULL solo será posible cuando la tabla no contenga ninguna fila con valor nulo en la columna que se modifica.

Creación y uso de vistas No contienen información por si mismas, sino que están basadas en las que contienen otras tablas y refleja los datos de estas. Si se suprime una tabla la vista asociada se invalida. Formato:

Page 17: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

16

CREATE [OR REPLACE] VIEW NOMBREVISTA [(COLUMNA [,COLUMNA])] AS CONSULTA;

create view DEPT20 (ENAME, JOB, SALARIO_ANUAL) as select ENAME, JOB, SAL*12 from EMP where DEPTNO = 20;

AS CONSULTA= Determina las columnas y las tablas que aparecerán en la vista. [OR REPLACE]= Crea de nuevo la vista si ya existía.

create OR REPLACE VIEW DEPT20 (ENAME, SALARIO_ANUAL) as select ENAME, SAL*12 from EMP where DEPTNO = 20;

Para consultar la vista creada, USER_VIEWS:

SELECT VIEW_NAME FROM USER_VIEWS; Para llamar a la vista que hemos creado; SELECT * FROM DEPT20; Nota: al borrar las tablas, las vistas de esas tablas no se borran y quedan inutilizadas. Borrado de vistas Se hace con DROP VIEW. Formato: DROP VIEW DEP20; Operaciones sobre vistas Se pueden realizar las mismas operaciones que se hacen sobre las tablas. Restricciones:

• Actualización si una vista esta basada en una sola tabla, se pueden modificar las filas de la vista. • La modificación de la vista cambia la tabla sobre la que esta definida. • Borrado de filas a través de una vista= Para borrar filas de una tabla a través de una vista, esta se

debe crear: • Con filas de una sola tabla. • Sin utilizar la cláusula GROUP BY ni DISTINCT. • Sin usar funciones de grupo o referencias a pseudocolumnas.

• Actualización de filas a través de una vista: Para actualizar filas en una tabla a través de una vista, esta ha de estar definida según las restricciones anteriores y , además, ninguna de las columnas que se va a actualizar se habrá definido como una expresión.

• Inserción de filas a través de una vista: Para insertar filas en una tabla a través de una vista se han de tener en cuenta todas las restricciones anteriores y, además, todas las columnas obligatorias de la tabla asociada deben estar presentes en la vista.

• Manejo de expresiones y de funciones en vistas: Se pueden crear vistas usando funciones, expresiones en columnas y consultas avanzadas pero únicamente se parean consultar estas vistas. También podemos modificar filas siempre y cuando la columna que se va a modificar no sea la columna expresad en forma de cálculo o con funciones.

Nota: No es posible insertar filas si las columnas de la vista contiene cálculos o funciones. Cambios de nombre

Page 18: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

17

RENAME cambia el nombre de una tabla, vista o sinónimo. El nuevo nombre no puede ser una palabra reservada ni el nombre de un objeto que tenga creado el usuario. Las restricciones de integridad, los índices y los permisos dados al objeto se transfieren automáticamente al nuevo objeto. RENAME NOMBRE_ANTERIOR TO NOMBRE_NUEVO; RENAME EMP5 TO MIS_EMPLEADOS; Gestión de tablespaces Un tablespace es una unidad lógica de almacenamiento d datos representada físicamente por uno o más archivos de datos. Se recomienda no mezclar datos de diferentes aplicaciones en un mismo tablespace. Para crear un tablespace CRATE TABLESPACE NOMBRETABLESPACE DATAFILE 'NOMBREARCHIVO' [SIZE ENTERO [K | M] [REUSE] [DEFAULT STORAGE (INITIAL TAMAÑO MINEXTENTS TAMAÑO MAXEXTENTS TAMAÑO PCTINCREASE VALOR )] [ONLINE | OFFLINE]; REUSE= Reutiliza el archivo si ya existe o lo crea si no existe. DEFAULT STORAGE= Define el almacenamiento por omisión para todos los objetos que se creen en este espacio de la tabla. Fija la cantidad de espacio si no se especifica en la sentencia CREATE TABLE. Ejemplos:

create tablespace tablespace1 datafile 'fichero.dat' size 60M;

create tablespace tablespace2 datafile 'c:\fichero.dat' size 1M default storage ( initial 10K next 10K minextents 8 maxextents 200 pctincrease 0);

create tablespace tablespace2 datafile 'c:\fich1.dat' size 50 M autoextend off, 'c:\fich2.dat' size 50 M autoextend off, 'c:\fich3.dat' size 100 M autoextend on maxsize 200 M default storage ( initial 100 K next 100K

Page 19: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

18

minextents 1 maxextents 200 pctincrease 0);

Modificación de tablespaces ALTER TABLESPACE NOMBRETABLESPACE {[ADD DATAFILE 'NOMBREARCHIVO' [SIZE ENTERO [K | M] [REUSE] [AUTOEXTEND ON… | OFF] ] [REANME DATAFILE 'ARCHIVO' [, 'ARCHIVO']… TO 'ARCHIVO' [, 'ARCHIVO']] [DEFAULT STORAGE CLAUSULAS_ALMACENAMIENTO] [ONLINE | OFFLINE] }; ADD_DATAFILE= Añade al tablespace uno o varios archivos. AUTOEXTEND= Activa o desactiva el crecimiento automático de los archivos de datos del tablespace. Cuando un tablespace se llena podemos usar esta opción para que el tamaño del archivo o archivos de datos asociados crezca automáticamente. Autoextend off: desactiva el crecimiento automático. RENAME_DATAFILE= Cambia el nombre de un archivo existente del tablespace. Este cambio se tiene que hacer desde el sistema operativo y, después, ejecutar la orden SQL. Ejemplos:

alter tablespace TABLESPACE_1 add datafile 'c:\DATAFILE_2.dat' size 100 k autoextend on next 100 k maxsize 1000 k; alter database datafile 'prueba0001.dat' resize 1 M; alter database datafile 'c:\DATAFILE_2.DAT' RESIZE 10m;

Nota: Para unir bloques contiguos en los datafiles de un tablespace lanzaremos la siguiente instrucción alter tablespace TSprueba1 coalesce; Borrado de tablespaces DROP TABLESPACE NOMBRETABLESPACE [INCLUDING CONTENTS]; INCLUDING CONTENTS= Permite borrar un tablespace que tenga datos. Sin esta opción solo se puede suprimir un tablespace vacío. Se recomienda poner el talespace offline entes de borrarlo para asegurarse de que no haya sentencias SQL que estén accediendo a datos del tablesapace, en cuyo caso no seria posible borrarlo.

Page 20: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

19

Cuando se borra un tablespace los archivos asociados no se borran del sistema operativo, por lo que tendremos que borrarlos de forma manual. Ejemplos: DROP TABLESPACE Tablespace1;

drop tablespace Tablespace1

including contents cascade constraints;

DROP TABLESPACE Tablespace1 INCLUDING CONTENTS; DROP TABLESPACE Tablespace1 INCLUDING CONTENTS AND DATAFILES; DROP TABLESPACE Tablespace1 INCLUDING CONTENTS AND DATAFILES CASCADE CONSTRAINTS;

Optimización de consultas SQL Distintas formas de optimizar las consultas realizadas en SQL. El lenguaje SQL es no procedimental, es decir, en las sentencias se indica que queremos conseguir y no como lo tiene que hacer el interprete para conseguirlo. Esto es pura teoría, pues en la práctica a todos los gestores de SQL hay que especificar sus propios truquitos para optimizar el rendimiento. Por tanto, muchas veces no basta con especificar una sentencia SQL correcta, sino que además, hay que indicarle como tiene que hacerlo si queremos que el tiempo de respuesta sea el mínimo. En este apartado veremos como mejorar el tiempo de respuesta de nuestro interprete ante unas determinadas situaciones: Diseño de las tablas

• Normaliza las tablas, al menos hasta la tercera forma normal, para asegurar que no hay duplicidad de datos y se aprovecha al máximo el almacenamiento en las tablas. Si hay que desnormalizar alguna tabla piensa en la ocupación y en el rendimiento antes de proceder.

• Los primeros campos de cada tabla deben ser aquellos campos requeridos y dentro de los requeridos primero se definen los de longitud fija y después los de longitud variable.

• Ajusta al máximo el tamaño de los campos para no desperdiciar espacio. • Es muy habitual dejar un campo de texto para observaciones en las tablas. Si este campo se va a

utilizar con poca frecuencia o si se ha definido con gran tamaño, por si acaso, es mejor crear una nueva tabla que contenga la clave primaria de la primera y el campo para observaciones.

Gestión y elección de los índices Los índices son campos elegidos arbitrariamente por el constructor de la base de datos que permiten la búsqueda a partir de dicho campo a una velocidad notablemente superior. Sin embargo, esta ventaja se ve contrarrestada por el hecho de ocupar mucha más memoria (el doble más o menos) y de requerir para su inserción y actualización un tiempo de proceso superior. Evidentemente, no podemos indexar todos los campos de una tabla extensa ya que doblamos el tamaño de la base de datos. Igualmente, tampoco sirve de mucho el indexar todos los campos en una tabla pequeña ya que las selecciones pueden efectuarse rápidamente de todos modos.

Page 21: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

20

Un caso en el que los índices pueden resultar muy útiles es cuando realizamos peticiones simultáneas sobre varias tablas. En este caso, el proceso de selección puede acelerarse sensiblemente si indexamos los campos que sirven de nexo entre las dos tablas. Los índices pueden resultar contraproducentes si los introducimos sobre campos triviales a partir de los cuales no se realiza ningún tipo de petición ya que, además del problema de memoria ya mencionado, estamos ralentizando otras tareas de la base de datos como son la edición, inserción y borrado. Es por ello que vale la pena pensárselo dos veces antes de indexar un campo que no sirve de criterio para búsquedas o que es usado con muy poca frecuencia por razones de mantenimiento. Campos a Seleccionar

• En la medida de lo posible hay que evitar que las sentencias SQL estén embebidas dentro del código de la aplicación. Es mucho más eficaz usar vistas o procedimientos almacenados por que el gestor los guarda compilados. Si se trata de una sentencia embebida el gestor debe compilarla antes de ejecutarla.

• Seleccionar exclusivamente aquellos que se necesiten • No utilizar nunca SELECT * por que el gestor debe leer primero la estructura de la tabla antes de

ejecutar la sentencia • Si utilizas varias tablas en la consulta especifica siempre a que tabla pertenece cada campo, le

ahorras al gestor el tiempo de localizar a que tabla pertenece el campo. En lugar de SELECT Nombre, Factura FROM Clientes, Facturacion WHERE IdCliente = IdClienteFacturado, usa: SELECT Clientes.Nombre, Facturacion.Factura WHERE Clientes.IdCliente = Facturacion.IdClienteFacturado.

Campos de Filtro

• Se procurará elegir en la cláusula WHERE aquellos campos que formen parte de la clave del fichero por el cual interrogamos. Además se especificarán en el mismo orden en el que estén definidos en la clave.

• Interrogar siempre por campos que sean clave. • Si deseamos interrogar por campos pertenecientes a índices compuestos es mejor utilizar todos

los campos de todos los índices. Supongamos que tenemos un índice formado por el campo NOMBRE y el campo APELLIDO y otro índice formado por el campo EDAD. La sentencia WHERE NOMBRE='Juan' AND APELLIDO Like '%' AND EDAD = 20 sería más optima que WHERE NOMBRE = 'Juan' AND EDAD = 20 por que el gestor, en este segundo caso, no puede usar el primer índice y ambas sentencias son equivalentes por que la condición APELLIDO Like '%' devolvería todos los registros.

Orden de las Tablas Cuando se utilizan varias tablas dentro de la consulta hay que tener cuidado con el orden empleado en la cláusula FROM. Si deseamos saber cuantos alumnos se matricularon en el año 1996 y escribimos: FROM Alumnos, Matriculas WHERE Alumno.IdAlumno = Matriculas.IdAlumno AND Matriculas.Año = 1996 el gestor recorrerá todos los alumnos para buscar sus matriculas y devolver las correspondientes. Si escribimos FROM Matriculas, Alumnos WHERE Matriculas.Año = 1996 AND Matriculas.IdAlumno = Alumnos.IdAlumnos, el gestor filtra las matrículas y después selecciona los alumnos, de esta forma tiene que recorrer menos registros. Consultas básicas La sintaxis básica de una consulta de selección es la siguiente: SELECT Campos FROM Tabla

Page 22: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

21

En donde campos es la lista de campos que se deseen recuperar y tabla es el origen de los mismos, por ejemplo: SELECT ENAME, JOB FROM EMP Esta sentencia devuelve un conjunto de resultados con el campo nombre y teléfono de la tabla clientes. Devolver Literales En determinadas ocasiones nos puede interesar incluir una columna con un texto fijo en una consulta de selección, por ejemplo, supongamos que tenemos una tabla de empleados y deseamos recuperar sus nombre y El sueldo semanal basado en El mensual

SELECT EMP.ENAME, 'Saldo semanal: ', EMP.SAL / 4

FROM EMP WHERE EMP.JOB = 'MANAGER' ; Ordenar los registros Adicionalmente se puede especificar el orden en que se desean recuperar los registros de las tablas mediante la cláusula ORDER BY Lista de Campos. En donde Lista de campos representa los campos a ordenar. Ejemplo: SELECT HIREDATE, ENAME, SAL FROM EMP ORDER BY ENAME; Esta consulta devuelve los campos CodigoPostal, Nombre, Telefono de la tabla Clientes ordenados por el campo Nombre. Se pueden ordenar los registros por mas de un campo, como por ejemplo: SELECT ENAME, JOB FROM EMP ORDER BY ENAME, JOB Incluso se puede especificar el orden de los registros: ascendente mediante la cláusula (ASC - se toma este valor por defecto) ó descendente (DESC) SELECT ENAME, JOB FROM EMP ORDER BY ENAME ASC, JOB DESC Consultas con Predicado El predicado se incluye entre la cláusula y el primer nombre del campo a recuperar, los posibles predicados son: Predicado Descripción * Devuelve todos los campos de la tabla ROWNUM Devuelve un determinado número de registros de la tabla DISTINCT Omite los registros cuyos campos seleccionados coincidan totalmente * Si no se incluye ninguno de los predicados se asume ALL. El Motor de base de datos selecciona todos los registros que cumplen las condiciones de la instrucción SQL y devuelve todos y cada uno de sus campos.

Page 23: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

22

No es conveniente abusar de este predicado ya que obligamos al motor de la base de datos a analizar la estructura de la tabla para averiguar los campos que contiene, es mucho más rápido indicar el listado de campos deseados. SELECT * FROM EMP ROWNUM Devuelve un cierto número de registros que entran entre al principio o al final de un rango especificado por una cláusula ORDER BY. Supongamos que queremos recuperar los 6 primeros empleados: SELECT Empno, Ename, Job, Mgr, Hiredate, Sal FROM Emp WHERE ROWNUM < 6; DISTINCT Omite los registros que contienen datos duplicados en los campos seleccionados. Para que los valores de cada campo listado en la instrucción SELECT se incluyan en la consulta deben ser únicos. Por ejemplo, varios empleados listados en la tabla Empleados pueden tener el mismo apellido. Si dos registros contienen López en el campo ENAME, la siguiente instrucción SQL devuelve un único registro: Select DISTINCT ENAME FROM EMP; Con otras palabras el predicado DISTINCT devuelve aquellos registros cuyos campos indicados en la cláusula SELECT posean un contenido diferente. El resultado de una consulta que utiliza DISTINCT no es actualizable y no refleja los cambios subsiguientes realizados por otros usuarios. ALIAS En determinadas circunstancias es necesario asignar un nombre a alguna columna determinada de un conjunto devuelto, otras veces por simple capricho o porque estamos recuperando datos de diferentes tablas y resultan tener un campo con igual nombre. Para resolver todas ellas tenemos la palabra reservada AS que se encarga de asignar el nombre que deseamos a la columna deseada. Tomado como referencia el ejemplo anterior podemos hacer que la columna devuelta por la consulta, en lugar de llamarse apellido (igual que el campo devuelto) se llame Empleado. En este caso procederíamos de la siguiente forma: AS no es una palabra reservada de ANSI, existen diferentes sistemas de asignar los alias en función del motor de bases de datos. En ORACLE para asignar un alias a un campo hay que hacerlo de la siguiente forma: SELECT ENAME AS NOMBRE FROM EMP; También podemos asignar alias a las tablas dentro de la consulta de selección, en esta caso hay que tener en cuenta que en todas las referencias que deseemos hacer a dicha tabla se ha de utilizar el alias en lugar del nombre. Esta técnica será de gran utilidad más adelante cuando se estudien las vinculaciones entre tablas. Por ejemplo: Select departamentos.deptno num_departamento, empleados.ename nombre_empleado

from dept departamentos, emp empleados; Para asignar alias a las tablas en ORACLE y SQL-SERVER los alias se asignan escribiendo el nombre de la tabla, dejando un espacio en blanco y escribiendo el Alias (se asignan dentro de la cláusula FROM).

Page 24: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

23

Operadores condicionales Antes de comenzar el desarrollo de este apartado hay que recalcar dos detalles de vital importancia. El primero de ellos es que cada vez que se desee establecer una condición referida a un campo de texto la condición de búsqueda debe ir encerrada entre comillas simples; la segunda hace referencia a las fechas. No existe una sintaxis que funcione en todos los sistemas, por lo que se hace necesario particularizarlas según el banco de datos: Banco de Datos Sintaxis SQL-SERVER Fecha = #mm-dd-aaaa# ORACLE Fecha = to_date('YYYYDDMM','aaaammdd',) ACCESS Fecha = #mm-dd-aaaa#

SQL-SERVER Fecha = #05-18-1969# ó Fecha = 19690518

ORACLE Fecha = to_date('YYYYDDMM', '19690518') ACCESS Fecha = #05-18-1969#

Referente a los valores lógicos True o False cabe destacar que no son reconocidos en ORACLE, ni en este sistema de bases de datos ni en SQL-SERVER existen los campos de tipo "SI/NO" de ACCESS; en estos sistemas se utilizan los campos BIT que permiten almacenar valores de 0 ó 1. Internamente, ACCESS, almacena en estos campos valores de 0 ó -1, así que todo se complica bastante, pero aprovechando la coincidencia del 0 para los valores FALSE, se puede utilizar la sintaxis siguiente que funciona en todos los casos: si se desea saber si el campo es falso "... CAMPO = 0" y para saber los verdaderos "CAMPO <> 0". Operadores Lógicos Los operadores lógicos soportados por SQL son: AND, OR, XOR, Eqv, Imp, Is y Not. A excepción de los dos últimos todos poseen la siguiente sintaxis: <expresión1> operador <expresión2> En donde expresión1 y expresión2 son las condiciones a evaluar, el resultado de la operación varía en función del operador lógico. La tabla adjunta muestra los diferentes posibles resultados: <expresión1> Operador <expresión2> Resultado Verdad AND Falso Falso Verdad AND Verdad Verdad Falso AND Verdad Falso Falso AND Falso Falso Verdad OR Falso Verdad Verdad OR Verdad Verdad Falso OR Verdad Verdad Falso OR Falso Falso Verdad XOR Verdad Falso Verdad XOR Falso Verdad Falso XOR Verdad Verdad Falso XOR Falso Falso Verdad Eqv Verdad Verdad Verdad Eqv Falso Falso Falso Eqv Verdad Falso Falso Eqv Falso Verdad Verdad Imp Verdad Verdad Verdad Imp Falso Falso

Page 25: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

24

Verdad Imp Null Null Falso Imp Verdad Verdad Falso Imp Falso Verdad Falso Imp Null Verdad Null Imp Verdad Verdad Null Imp Falso Null Null Imp Null Null Si a cualquiera de las anteriores condiciones le anteponemos el operador NOT el resultado de la operación será el contrario al devuelto sin el operador NOT. El último operador denominado Is se emplea para comparar dos variables de tipo objeto <Objeto1> Is <Objeto2>. este operador devuelve verdad si los dos objetos son iguales. SELECT * FROM Emp WHERE sal > 2500 AND sal < 3000; SELECT * FROM Emp WHERE (SAL > 2500 AND SAL < 3000) OR JOB = 'CLERK' ; SELECT * FROM Emp WHERE NOT JOB = 'MANAGER' SELECT * FROM Emp WHERE (Sal >3000 AND sal < 5000) OR (JOB = 'SALESMAN' AND COMM = '1400') Intervalos de Valores Para indicar que deseamos recuperar los registros según el intervalo de valores de un campo emplearemos el operador Between cuya sintaxis es: campo [Not] Between valor1 And valor2 (la condición Not es opcional) En este caso la consulta devolvería los registros que contengan en "campo" un valor incluido en el intervalo valor1, valor2 (ambos inclusive). Si anteponemos la condición Not devolverá aquellos valores no incluidos en el intervalo. SELECT * FROM EMP WHERE SAL NOT Between 2000 And 3000 El Operador Like Se utiliza para comparar una expresión de cadena con un modelo en una expresión SQL. Su sintaxis es: expresión Like modelo En donde expresión es una cadena modelo o campo contra el que se compara expresión. Se puede utilizar el operador Like para encontrar valores en los campos que coincidan con el modelo especificado. Por modelo puede especificar un valor completo (Ana María), o se puede utilizar una cadena de caracteres comodín como los reconocidos por el sistema operativo para encontrar un rango de valores (Like An%). El operador Like se puede utilizar en una expresión para comparar un valor de un campo con una expresión de cadena. Por ejemplo, si introduce Like C% en una consulta SQL, la consulta devuelve todos los valores de campo que comiencen por la letra C. En una consulta con parámetros, puede hacer que el usuario escriba el modelo que se va a utilizar. NOTA:Crearemos una columna nueva (ALTER TABLE EMP2 ADD col_especial varchar2(50);) El ejemplo siguiente devuelve los datos que comienzan con la letra P seguido de cualquier letra entre A y F y de tres dígitos:

Page 26: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

25

Like 'P[A-F]###' Este ejemplo devuelve los campos cuyo contenido empiece con una letra de la A a la D seguidas de cualquier cadena. Like '[A-D]*' En la tabla siguiente se muestra cómo utilizar el operador Like para comprobar expresiones con diferentes modelos.

ORACLE

Ejemplo Descripción LIKE 'A%' Todo lo que comience por A LIKE '_NG' Todo lo que comience por cualquier carácter y luego siga NG LIKE '[AF]%' Todo lo que comience por A ó F LIKE '[A-F]%' Todo lo que comience por cualquier letra comprendida entre la A y la F LIKE '[A^B]%' Todo lo que comience por A y la segunda letra no sea una B

En determinado motores de bases de datos, esta cláusula, no reconoce el asterisco como carácter comodín y hay que sustituirlo por el carácter tanto por ciento (%). El Operador In Este operador devuelve aquellos registros cuyo campo indicado coincide con alguno de los en una lista. Su sintaxis es: expresión [Not] In(valor1, valor2, . . .) SELECT * FROM EMP2 WHERE JOB In ('CLERK', 'PRESIDENT') ; La cláusula WHERE La cláusula WHERE puede usarse para determinar qué registros de las tablas enumeradas en la cláusula FROM aparecerán en los resultados de la instrucción SELECT. Después de escribir esta cláusula se deben especificar las condiciones expuestas en los apartados anteriores. Si no se emplea esta cláusula, la consulta devolverá todas las filas de la tabla. WHERE es opcional, pero cuando aparece debe ir a continuación de FROM.

select ENAME, DNAME from EMP E, DEPT D where E.DEPTNO = D.DEPTNO;

select ENAME, E.DEPTNO, DNAME from EMP E, DEPT D where E.DEPTNO = D.DEPTNO and JOB = 'SALESMAN';

select E1.ENAME, E2.ENAME from EMP E1, EMP E2 where E1.MGR = E2.EMPNO;

select ENAME, SAL from EMP where EMPNO in (select PMGR from PROJECT where PSTART < ’31-DEC-90’) and DEPTNO =20;

select * from EMP where DEPTNO in (select DEPTNO from DEPT where LOC = 'BOSTON');

select * from EMP E1 where DEPTNO in (select DEPTNO from EMP E where E.EMPNO = E1.MGR);

select * from EMP where SAL >= any (select SAL from EMP where DEPTNO = 30)

select * from EMP where SAL > all (select SAL from EMP where DEPTNO = 30)

Page 27: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

26

and DEPTNO = 10;

and DEPTNO <> 30;

select * from DEPT where not exists (select * from EMP where DEPTNO = DEPT.DEPTNO);

SELECT * FROM Emp WHERE (Sal >3000 AND sal < 5000) OR (JOB = 'SALESMAN' AND COMM = '1400')

El predicado EXISTS (con la palabra reservada NOT opcional) se utiliza en comparaciones de verdad/falso para determinar si la subconsulta devuelve algún registro. Supongamos que deseamos recuperar todos aquellos empleados que esten asociados a algún departamento: SELECT emp.ename, emp.job FROM EMP WHERE EXISTS ( SELECT * FROM dept WHERE emp.deptno = dept.deptno ) Esta consulta es equivalente a esta otra: SELECT emp.ename, emp.job FROM emp WHERE deptno IN ( SELECT dept.deptno FROM dept); Para insertar Registros de otra Tabla: En este caso la sintaxis es: INSERT INTO Tabla [IN base_externa] (campo1, campo2, , campoN) SELECT TablaOrigen.campo1, TablaOrigen.campo2,,TablaOrigen.campoN FROM Tabla Origen En este caso se seleccionarán los campos 1,2,..., n de la tabla origen y se grabarán en los campos 1,2,.., n de la Tabla. La condición SELECT puede incluir la cláusula WHERE para filtrar los registros a copiar. Si Tabla y Tabla Origen poseen la misma estructura podemos simplificar la sintaxis a: INSERT INTO Tabla SELECT Tabla Origen.* FROM Tabla Origen De esta forma los campos de Tabla Origen se grabarán en Tabla, para realizar esta operación es necesario que todos los campos de Tabla Origen estén contenidos con igual nombre en Tabla. Con otras palabras que Tabla posea todos los campos de Tabla Origen (igual nombre e igual tipo). En este tipo de consulta hay que tener especial atención con los campos contadores o autonuméricos puesto que al insertar un valor en un campo de este tipo se escribe el valor que contenga su campo homólogo en la tabla origen, no incrementándose como le corresponde. Se puede utilizar la instrucción INSERT INTO para agregar un registro único a una tabla, utilizando la sintaxis de la consulta de adición de registro único tal y como se mostró anteriormente. En este caso, su código especifica el nombre y el valor de cada campo del registro. Debe especificar cada uno de los campos del registro al que se le va a asignar un valor así como el valor para dicho campo. Cuando no se especifica dicho campo, se inserta el valor predeterminado o Null. Los registros se agregan al final de la tabla.

Page 28: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

27

También se puede utilizar INSERT INTO para agregar un conjunto de registros pertenecientes a otra tabla o consulta utilizando la cláusula SELECT... FROM como se mostró anteriormente en la sintaxis de la consulta de adición de múltiples registros. En este caso la cláusula SELECT especifica los campos que se van a agregar en la tabla destino especificada. La tabla destino u origen puede especificar una tabla o una consulta. Si la tabla destino contiene una clave principal, hay que asegurarse que es única, y con valores no nulos; si no es así, no se agregarán los registros. Si se agregan registros a una tabla con un campo Contador, no se debe incluir el campo Contador en la consulta. Se puede emplear la cláusula IN para agregar registros a una tabla en otra base de datos. Se pueden averiguar los registros que se agregarán en la consulta ejecutando primero una consulta de selección que utilice el mismo criterio de selección y ver el resultado. Una consulta de adición copia los registros de una o más tablas en otra. Las tablas que contienen los registros que se van a agregar no se verán afectadas por la consulta de adición. En lugar de agregar registros existentes en otra tabla, se puede especificar los valores de cada campo en un nuevo registro utilizando la cláusula VALUES. Si se omite la lista de campos, la cláusula VALUES debe incluir un valor para cada campo de la tabla, de otra forma fallará INSERT. Ejemplos Esta consulta crea una tabla nueva llamada oldemp con dos campos iguales que la tabla emp y copia aquellos registros cuyo campo hiredate sea menor que 31-Diciembre-1960 create table OLDEMP ( ENO number(4) not null, HDATE date); insert into OLDEMP (ENO, HDATE) select EMPNO, HIREDATE from EMP where HIREDATE > '31-12-60'; insert into emp (empno) values (999); o bien, insert into emp values (999,'Pedro','MANAGER',NULL,NULL,NULL,NULL,NULL); UPDATE Crea una consulta de actualización que cambia los valores de los campos de una tabla especificada basándose en un criterio específico. Su sintaxis es: UPDATE Tabla SET Campo1=Valor1, Campo2=Valor2, CampoN=ValorN WHERE Criterio UPDATE es especialmente útil cuando se desea cambiar un gran número de registros o cuando éstos se encuentran en múltiples tablas UPDATE EMP set JOB = 'MANAGER', DEPTNO = 20, SAL = SAL +1000 where ENAME = 'JONES'; Si en una consulta de actualización suprimimos la cláusula WHERE todos los registros de la tabla señalada serán actualizados.

Page 29: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

28

UPDATE EMP SET sal = sal*10 ; update EMP set SAL = SAL + 1.15 where DEPTNO in (10,30); update EMP set SAL = (select min(SAL) from EMP where JOB = 'MANAGER') where JOB = 'SALESMAN' and DEPTNO = 30; DELETE Podemos borrar una fila utilizando la sintaxis del comando DELETE: delete from <tabla> [where <condición>]; DELETE FROM emp2 where DEPTNO = 20; O borrar todos los registros DELETE FROM emp2; PL/SQL (Introducción)

Introducción a PL/SQL PL/SQL Es un lenguaje estructurado formado por bloques. Es decir, sus unidades básicas (procedimientos, funciones y bloques anónimos), hacen que PL/SQL se estructure en bloques lógicos, los cuales a su vez pueden contener otros subbloques. La estructura de un bloque se muestra en el siguiente esquema: [DECLARE --- decalraciones] BEGIN --- sentencias de programa [EXCEPCIONES --- manejadrores] END; Se pueden anidar otros bloques dentro de las partes BEGIN y EXCEPCIONES de cada código PL/SQL. PL/SQL nos permite declarar constantes y variables y utilizarlas en sentencias o en cualquier otro sitio donde se puedan utilizar (funciones, procedimientos, triggers..). Para declarar variables en PL/SQL utilizamos cualquiera de los tipos de datos de SQL, tales como CHAR, DATE, NUMBER o cualquier tipo de dato PL/SQL como por ejemplo BOOLEAN o BINARY o INTEGER. Por ejemplo, crearemos una variable llamada mi_numero que guardará números de cuatro dígitos y una variable llamada almacén para guardar valores TRUE o FALSE. La declaración Es la siguiente: Mi_numero NUMBER(4); Almacen BOOLEAN;

Page 30: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

29

Para asignar valores a estas variables existen tres maneras. La primera Es mediante El uso Del operador ‘ := ‘, tasa := precio * tasa_rango; id_valido := FALSE; bonus := salario_actual* 0.10; La segunda manner Es asignando valores a las varibles mediante datos obtenidos de la base de datos: SELECT sal * 0.10 INTO bonus FROM emp WHERE empno = emp_id; (Selecciona la columna sal de la tabla EMP, multiplicando cada fila de esa columna por 0.10, le asigna El valor a la variable bonus donde El numero de empleado sea igual al número de la variable emp_id) La tercera manera de asignar valores a las variables Es mediante El paso de parámetros OUT o IN OUT a un subprograma. En El ejemplo siguiente se muestra un parámetro IN OUT que permite pasar valores a un subprograma que devuelve otros valores actualizados en función a los que se le pasaron DECLARE mi_salario REAL(7,2); PROCEDURE ajustar_salario (emp_id INT, salario IN OUT REAL) IS ... BEGIN SELECT AVG(sal) INTO mi_salario FROM emp; ajustar_salario(7788, mi_salario); -- asina un nuevo valor a mi salario La declaración de constantes en PL/SQL Es muy parecida a la de las variables, la única diferencia estriba en El uso de la palabra reservada CONSTANT como se muestra en El ejemplo: Limite_credito CONSTANT REAL := 5000.00; Oracle utiliza lo que se denominan ares de trabajo para ejecutar sentencias SQL o procedimientos almacenados. Existen los denominados cursores que permiten ponerles un nombre y acceder a información almacenada. Por ejemplo, para consultas que devuelven más de una fila, se puede declarar un cursor para procesar las filas de manera individual. Un ejemplo de la declaración de un cursor se muestra más abajo: DECLARE CURSOR cursor_1 IS SELECT empno, ename, job FROM emp WHERE deptno = 20; El conjunto de filas que devuelve este cursor se denomina Resultset o conjunto de resultados. Para recorrer este conjunto de resultados utilizamos los bucles. En El siguiente ejemplo, recorremos las filas mediante El bucle FOR que implícitamente declara El puntero emp_reg DECLARE salario NUMBER(7,2) := 0; CURSOR cursor_1 IS SELECT ename, sal, hiredate, deptno FROM emp; BEGIN FOR emp_reg IN cursor_1 LOOP salario := salario + emp_reg.sal; INSERT into temp (job,sal,ename) values ('Nulo',salario,emp_reg.ename); END LOOP; END; Si nos fijamos, para referirnos a un campo en concreto, utilizamos El punto para separar nombre de tabla y columna. Las variables y cursores de PL/SQL tienen lo que se denominan atributos. Los atributos no son más que propiedades que permiten referenciar el tipo de dato y estructura de un item si necesidad de tener que repetir su definición. Las tablas y columnas de una base de datos, tienen atributos similares, que se pueden utilizar para hacer un fácil mantenimiento de la base de datos.

Page 31: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

30

Para utilizar los atributos, utilizamos el símbolo %. El atributo %TYPE nos devuelve El tipo de dato de una variable o de una columna de una tabla. Utilizar este tipo de atributo nos puede ser util por dos motivos. El primero, Es que no Es necesario recordar El tipo de dato de una variable en concreto o de una columna y El segundo motivo Es que no nos Es necesario cambiar los tipos de datos de nuestras variables si modificamos la estructura de nuestras tablas o sus tipos de datos (esto lo hace solo PL/SQL en tiempo de ejecución). Mi_titulo libros.title%TYPE; En PL/SQL los registros se utilizan para agrupar datos. Un registro esta compuesto por un número de campos relacionados en donde los valores se pueden almacenar. El atributo %ROWTYPE, nos devuelve un tipo de registro que representa un fila en una tabla. Este registro puede almacenar una fila entera de datos seleccionada de una tabla o extraída de un cursor. Las columnas de una fila y sus correspondientes campos en un registro, tienen los mismos nombres y tipos de datos. En El siguiente ejemplo se declara un registro denominado dept_fila. Sus campos tienen los mismos nombres y los mismos tipos de datos que que las columnas en la tabla DEPT. DECLARE dept_fila dept%ROWTYPE; Para asignar valor a esta variable utilizamos El punto que separa nombres de tabla de nombres de columna como se muestra en El ejemplo: mi_deptno := dept_fila.deptno; Si se declara un cursor que obtenga los campos (salario, fecha, etc...) de un empleado, se podría utilizar el atributo %ROWTYPE, de manera que almacene la misma información que se recupera de las tablas de la base de datos sin necesidad de conocer los nombres de los campos ni sus tipos de datos: DECLARE CURSOR cursor_1 IS SELECT ename, sal, hiredate, job FROM emp; emp_fila c1%ROWTYPE; -- declara un variable que alojara los datos de la misma -- manera que están almacenados en cada registro del cursor Estructuras de control Posiblemente sea una de las partes más importantes de PL/SQL, no solo porque nos permiten manipular datos de Oracle sino también porque nos permite procesar datos utilizando sentencias condicionales , iterativas y control de flujo secuencial tales como IF-THEN-ELSE, CASE, FOR-LOOP, WHILE-LOOP, EXIT-WHEN y GOTO. A veces nos Es necesario tomar acciones alternativas en función de ciertas circunstancias. La cláusula IF-THEN-ELSE nos permite ejecutar una secuencia de manera condicional. Si la cláusula IF define una condición, la cláusula THEN le dice al programa que hacer si se cumple dicha condición o bien la cláusula ELSE si no se cumple. En el siguiente ejemplo realizamos una transacción bancaria, antes de retirar dinero de una cuenta primero verificamos si hay suficiente, en caso contrario, escribimos en una tabla temporal un avis que nos dice que no se ha podido realizar El pago DECLARE saldo_disponible NUMBER(11,2); cuenta_no CONSTANT NUMBER(4) := 3; debito CONSTANT NUMBER(5,2) := 500.00; BEGIN SELECT bal INTO saldo_disponible FROM cuentas WHERE cuenta_id = cuenta_no FOR UPDATE OF bal; IF saldo_disponible >= debito THEN

Page 32: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

31

UPDATE cuentas SET bal = bal – debito WHERE cuenta_id = cuenta_no; ELSE INSERT INTO temp VALUES (cuenta_no, saldo_disponible, ’saldo insuficiente’); END IF; COMMIT; END; Realizaremos un procedimiento que comprueba el saldo de un empleado, si es menor de 4000, entonces se lo incrementaremos en esta cantidad, si no , escribiremos en una tabla temporal el empleado no recibe aumento. DECLARE valor CONSTANT NUMBER(7,2) := 4000; nombre CONSTANT VARCHAR2(15) := 'SCOTT'; salario NUMBER (7,2); BEGIN Select sal INTO salario from emp where ename LIKE nombre; IF salario < valor THEN BEGIN UPDATE emp SET sal = valor where ename like nombre; -- Para hacer que se muestre una salida por pantalla -- es necesario que la salida por consola este habilitada -- hay dos formas de hacerlo -- * SET SERVEROUTPUT ON; -- * dbms_output.enable(buffer_size => NULL); DBMS_OUTPUT.PUT_LINE('El salario del empleado ' || nombre || ' ha sido aumentado.'); END; ELSE INSERT INTO temp VALUES ('Sin aumento',salario,nombre); END IF; END; Si necesitamos elegir entre varios valores, podemos utilizar la clausura CASE. La expresión CASE evalúa una condición y realiza una acción (que puede ser un bloque entero de PL/SQL). Fijémonos en el ejemplo siguiente: DECLARE numero CONSTANT NUMBER (10) := 3; BEGIN CASE WHEN numero = 1 THEN DBMS_OUTPUT.PUT_LINE('El numero es 1'); WHEN numero = 2 THEN BEGIN DBMS_OUTPUT.PUT_LINE('El numero es 2'); END; WHEN numero = 3 THEN DBMS_OUTPUT.PUT_LINE('El numero es 3'); ELSE DBMS_OUTPUT.PUT_LINE('El numero no es ni 1 ni 2 ni 3'); END CASE; END; En este ejemplo creamos una constante a la que le asignamos el valor 3, el bloque de código irá comparando ese valor con una lista de valores definidos en el propio código, si alguno de ellos es igual, mandará un mensaje diciéndonos en que número coincide. Otro ejemplo más, crearemos un procedimiento que calculará el valor del área de un circulo, de una rectángulo o de un cuadrado dependiendo de la opción que desee el usuario. El procedimiento, solamente compara que tipo de área tiene que calcular y devolverá un resultado con El área de la figura que El usuario haya elegido: DECLARE forma VARCHAR(10) := &forma;

Page 33: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

32

lado CONSTANT NUMBER (3) := 5; radio CONSTANT DECIMAL(10,5):= 14.5; ancho CONSTANT NUMBER (10) := 20; area DECIMAL(10,5); BEGIN CASE WHEN forma = 'cuadrado' THEN area := lado * lado; WHEN forma = 'circulo' THEN BEGIN area := 3.14 * (radio * radio); END; WHEN forma = 'rectangulo' THEN area := lado * ancho; ELSE DBMS_OUTPUT.PUT_LINE('No hay area para esa figura'); END CASE; DBMS_OUTPUT.PUT_LINE('El area del ' || forma || ' es igual a : '|| area); END; Normas de las estrucutras de bloque Antes de profundizar en la sintaxis de PL/SQL, Es recomendable tener en cuenta una serie de pequeñas normas a seguir cuando escribamos código contra nuestra base de datos. - Toda unidad de PL/SQL debe de constituir un bloque, como mínimo, debe de estar encerrado entre la

cláusula BEGIN y END. - Las sentrencias SELECT en los bloques de PL/SQL son sentencias SQL embebidas, por lo que deben

de retornar solamente una fila. Las sentencias SELECT que no devuelvan ninguna fila o más de una generarán un error. Si se desea trabajar con sentencias SELECT que devuelvan más de una fila, lo tendremos que hacer a través de un cursor.

- Si las variables u objetos son definidas para ser usadas en un bloque distinto Del que son declaradas, Es necesario declararlas dentro de una sección DECLARE.

- Si incluimos una sección EXCEPTION, las sentencias contenidas dentro de ella, solo serán procesadas si la condición a la que se refieren ocurre. El bloque de ejecución se termina después de que una rutina de manejo de excepciones es ejecutada.

- El ámbito de un objeto, define donde Es visible, Es la zona Del programa en donde podemos utilizar dicho objeto o variable.

- El ámbito de un objeto o variable queda determinado por El bloque de código en donde ha sido declarado y solo estará disponible mientras no se termine la ejecución Del bloque que lo contiene.

Registros de PL/SQL Un registro de PL/SQL Es una varibale que contiene una colección de campos. Cada campo Es direccionable individualmente y sus nombres de campo pueden ser referenciados tanto en asiganciones como en expresiones. Los campos en un registro pueden tener tipos de datos y tamaños diferentes. Para declarar un registro basado en una colección de columnas de una base de datos podemos utilizar El atributo %ROWTYPE. Por ejemplo: DECLARE REC_EMP EMP%ROWTYPE; REC_EMP2 EMP%ROWTYPE; BEGIN Select * into REC_EMP from EMP where ename='SCOTT'; REC_EMP2 := REC_EMP; END; Para utilizar los campos de un registro podemos hacer lo siguiente:

Page 34: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

33

SET SERVEROUTPUT ON; DECLARE REC_EMP EMP%ROWTYPE; REC_EMP2 EMP%ROWTYPE; BEGIN Select * into REC_EMP from EMP where ename='SCOTT'; REC_EMP2 := REC_EMP; IF REC_EMP2.SAL > 4000 THEN dbms_output.put_line('El empleado '|| REC_EMP2.ENAME ||' gana más de 4000'); ELSE dbms_output.put_line('El empleado '|| REC_EMP2.ENAME ||' No gana más de 4000'); END IF; END; Asignaciones en PL/SQL Se pueden asignar valores a las varibales en cualquier parte de PL/SQL dentro de un bloque o incluso en la propia declaración de la variable: NUM1 := NUM1 + NUM2 + (NUM3 * 3); VALOR := 1; STR1 := 'El valor de la varibale VALOR = '|| TO_CHAR(SENT); CHR1 := 'ABCDEFG'; FLAG1 := TRUE; FLAG1 := FALSE; Expresiones condicionales Como se dijo en El apartado anterior, una de las partes más importantes de PL/SQL son las expresiones condicionales mediante las cuales se puede hacer que nuestro programa ejecute un bloque de código u otro en función a una o unas condiciones que ocurran durante la ejecución Del mismo. En El siguiente ejemplo se pregunta si El valor de una variable Es menor que 10, si Es así, envíamos un mensaje por pantalla. DECLARE x NUMBER(3) := 9; BEGIN IF x < 10 THEN dbms_output.put_line('X Es menor que 10'); END IF; END; Podríamos querer bifurcar la condición y añadir un nuevo bloque de código si la X fuera mayor, tengamos en cuenta que en nuestro ejemplo, solamente se ejecuta un bloque de código si X Es menor que 10. Veamos El siguiente ejemplo: DECLARE x NUMBER(3) := 10; BEGIN IF x < 10 THEN dbms_output.put_line('X es menor que 10'); ELSE

Page 35: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

34

dbms_output.put_line('X es mayor que 10'); END IF; END; También podríamos preguntar por ciertos valores de la X, supongamos que queremos interponer una condición si X Es menor que 10 o mayor o incluso igual a 10, o igual a cualquier otro número. Para ello utilizamos la cláusula ELSIF, que Es equivalente a un ELSE IF. El siguiente código muestra un ejemplo Del uso de ELSIF. DECLARE x NUMBER(3) := 47; BEGIN IF x < 10 THEN dbms_output.put_line('X es menor que 10'); ELSIF x = 10 THEN dbms_output.put_line('X es igual a 10'); ELSIF x < 100 THEN dbms_output.put_line('X esta entre 11 y 99'); ELSE dbms_output.put_line('X es mayor que 99'); END IF; END; Bucles en PL/SQL En PL/SQL existen varios tipos de bucles. Los bucles nos permiten iterar sobre un conjunto de sentencias, hasta que se cumpla una ondición tal como haber alcanzado El número de vueltas impuesto o que se cumpla la condición de terminación Del bucle. Los bucles de PL/SQL no difieren mucho de aquellos que se podrían encontrar en cualquier otro lenguaje procedural. A continuación mostraremos los tipos de bucles que podemos utilizar en PL/SQL y sus diferencias. El bucle LOOP – END LOOP El bucle más básico de los que podemos encontrar en PL/SQl Es aquel que consiste de una serie de sentencias contenidas entre las cláusulas LOOP y END LOOP. Todo bucle necesita de una condición para su finalización. La manera en la que podemos terminar las iteraciones de un bucle como este es a través de la claúsula EXIT, Esta clausula, sale del LOOP principal y retorna a la siguiente sentencia despues de END LOOP. Veamos los siguientes ejemplo: CREATE TABLE tabla (col VARCHAR2(5)); DECLARE i NUMBER := 1; BEGIN LOOP i := i + 1; INSERT INTO tabla VALUES (i); IF i > 99 THEN EXIT; END IF; END LOOP; END; / El bucle WHILE – END LOOP

Page 36: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

35

El bucle WHILE nos permite iterar sobre un conjunto de sentencias, basandose en una condiión que se evalua en cada iteración. La condición Es evaluada al principio Del bucle, si Es falsa, entonces no se realizará ninguna iteración. Is la condición de fin Del bucle no Es modificada en algun momento dentro Del bucle durante las iteraciones El bucle no terminará nunca. Se puede inluir una o varias clausulas EXIT si Es necesario que El bucle se rompa antes de que en alguna de sus iteraciones se cumpla la condición definalización Del mismo. TRUNCATE TABLE tabla; DECLARE i INTEGER := 999; BEGIN WHILE i < 1100 LOOP i := i + 1; INSERT INTO tabla VALUES (i); END LOOP; END; / SELECT * FROM tabla; El bucle FOR – END LOOP El bucle FOR funciona exactamente igual que en cualquier otro lenguaje procedural, lo único que Es necesario destacar Es su sintaxis: --incrementando FOR <variable> IN <numero_comienzo> .. <numero_fin> LOOP <sentencias> END LOOP; --decrementando FOR <variable> IN REVERSE <numero_comienzo> .. <numero_fin> LOOP <code here> END LOOP; Ejemplos: -- incrementando TRUNCATE TABLE tabla; DECLARE i INTEGER := 0; BEGIN FOR i IN 2000 .. 2100 LOOP INSERT INTO tabla VALUES (i); END LOOP; END; / Select * from tabla; --decrementando TRUNCATE TABLE tabla; DECLARE I INTEGER := 0; BEGIN FOR i IN REVERSE 3000 .. 3100 LOOP

Page 37: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

36

INSERT INTO tabla VALUES (i); END LOOP; END; / SELECT * FROM tabla; Cursores de PL/SQL Cursores implicitos Cuando una sentencia de SQL Es lanzada, El servidor de base de datos abre un área de memoria en la que El comando Es parseado y ejecutado. Este área se denomina cursor. Cuando la parte ejecutable de un bloque PL/SQL alcanza un comando SQL, ester crea un cursor implícito que tiene El identificador SQL, PL/SQl maneja ester cursor por nosotros. PL/SQL nos ofrece varios atributos que nos permiten evaluar que ocurre cuando ester cursor fue utilizado por última vez. Los atributos de ester cursor se muestran a continuación. %ROWCOUNT El número de filas procesadas por la sentencia SQL.. %FOUND TRUE si como mínimo una fila fue procesada. %NOTFOUND TRUE si ninguna fila fue procesada

%ISOPEN TRUE si el cursor esta abierto o FALSE si el cursor no ha sido abierto o no ha sido cerrado. Este atributo solo se utiliza con cursores explícitos.

Observemos primero el siguiente ejemplo: DECLARE filas_borradas INTEGER; BEGIN DELETE FROM tabla; filas_borradas := SQL%ROWCOUNT; dbms_output.put_line(TO_CHAR(filas_borradas)); END; / El ejemplo de arriba nos devuelve El número de filas que han sido borradas después de la sentencia DELETE Cursores explícitos Las sentencias SELECT que ocurren dentro de bloques de PL/SQL se denominan sentencias embebidas, estas sentencias deben de retornar solo un valor y de hecho, muchas solo devuelven uno, pero hay otras muchas que devuelven grandes numeros de filas. Para abordar ester problema, se puede definir una sentencia SELECT como un cursor (Es decir, reservar un area de memoria para alamcenar todos los registros que obtengo de mi sentencia SELECT). Para manipular El contenido de los cursores, dispones de cuatro comandos que se muestran en la siguiente tabla:

DECLARE Define el nombre y la estructura del cursor junto con la sentencia SELECT que rellenará dicho cursor con datos. La query se valida pero no se ejecuta..

OPEN Ejecuta la query que rellena el cursor con filas.

FETCH Carga la fila que esta direccionada por el puntero del cursor dentro de alguna variable y mueve dicho puntero a la siguiente fila.

CLOSE Vacía los datos contenidos en el cursor y lo cierra. El cursor puede ser reabierto y sus datos pueden ser actualizados.

Page 38: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

37

Los cursores se declaran dentro de una sección DECLARE, como se muestra en El ejemplo: DECLARE CURSOR MICURSOR IS SELECT ENAME, JOB FROM EMP; El cursor se define mediante la palabra reservada CURSOR seguida de El identificador Del cursor (MICURSOR en este caso) y la sentencia SELECT Es utilizada para rellenarlo. Para abrir un cursor utilizamos la cláusula OPEN, como se muestra en El ejemplo: DECLARE CURSOR MICURSOR IS SELECT ENAME, JOB FROM EMP; BEGIN OPEN MICURSOR; END; Para acceder a los datos contenidos en El cursor utilizamos la cláusula FETCH: DECLARE CURSOR MICURSOR IS SELECT ENAME, JOB FROM EMP; nombre VARCHAR2(20); trabajo VARCHAR(20); BEGIN OPEN MICURSOR; FETCH MICURSOR INTO nombre, trabajo; dbms_output.put_line(nombre); dbms_output.put_line(trabajo); END; La sentencia FETCH, lee los valores de la columna del cursor actual y los pone dentro de las variables que hemos especificado para ello. El puntero del cursor es actualizado para que apunte a la siguiente fila. Si el cursor no tiene más filas, el valor del puntero se establecerá a null. Las siguientes llamadas a FETCH me devolverán una excepción. La sentencia CLOSE vacía el cursor. DECLARE CURSOR MICURSOR IS SELECT ENAME, JOB FROM EMP; nombre VARCHAR2(20); trabajo VARCHAR(20); BEGIN OPEN MICURSOR; FETCH MICURSOR INTO nombre, trabajo; dbms_output.put_line(nombre); dbms_output.put_line(trabajo); CLOSE MICURSOR; END; Para procesar El contenido completo de un cursor, únicamente necesitamos situar la sentencia FETCH en un bulce y chequear El atributo NOTFOUND para ver si la fila se ha recuperado correctamente o no. He aquí un ejemplo que ilustra ester apartado: DECLARE CURSOR MICURSOR IS SELECT ENAME, JOB FROM EMP; nombre VARCHAR2(20); trabajo VARCHAR(20); BEGIN OPEN MICURSOR;

Page 39: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

38

LOOP FETCH MICURSOR INTO nombre, trabajo; dbms_output.put_line(nombre); dbms_output.put_line(trabajo); EXIT WHEN MICURSOR%NOTFOUND; END LOOP; CLOSE MICURSOR; END; Es posible variar el ResultSet devuelto mediante el uso de parámetros. Lo parámetros nos permiten especificar la selección de la query cuando se abre el cursor : DECLARE CURSOR MICURSOR (PARAM1 NUMBER) IS SELECT ENAME, JOB FROM EMP WHERE EMPNO = PARAM1; mi_fila MICURSOR%ROWTYPE; BEGIN OPEN MICURSOR(7369); FETCH MICURSOR INTO mi_fila; dbms_output.put_line(mi_fila.ename); CLOSE MICURSOR; OPEN MICURSOR(7934); FETCH MICURSOR INTO mi_fila; dbms_output.put_line(mi_fila.ename); CLOSE MICURSOR; END; El siguiente ejemplo muestra el uso de cursores mediante inserción de filas en una tabla: TRUNCATE TABLE tabla; DECLARE CURSOR mi_cur IS SELECT SUBSTR(object_name,1,5) COLUM FROM all_objects WHERE SUBSTR(object_name,1,5) LIKE 'A%'; mi_reg mi_cur%ROWTYPE; BEGIN OPEN mi_cur; LOOP FETCH mi_cur INTO mi_reg; EXIT WHEN mi_cur%NOTFOUND; INSERT INTO tabla VALUES (mi_reg.colum); END LOOP; COMMIT; CLOSE mi_cur; END;

Procedimientos y funciones Un procedimiento o una función Es un bloque PL/SQL con nombre que normalmente se almacena en la base de datos dentro de un package o individualmente. La ventaja de que resida en la base de datos es que cuando Es almacenado, este se parsea a la vez que se almacena. Cuando es ejecutado, Oracle ya lo tiene compilado y esto disminuye el tiempo de ejecución. Almacenar los bloques de código en procedimientos almacenados o funciones es una buena manera de agrupar la funcionalidad de la aplicación. Es posible invocar un procedimiento almacenado de Oracle desde la mayoría de herramientas de Oracle. Las principales ventajas de utilizar procedimientos y funciones son: - Mejora de la seguridad e integridad de los datos - Control del acceso indirecto a objetos desde usuarios sin privilegios mediante privilegios de

seguridad

Page 40: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

39

- Mejora del rendimiento, pues evitamos el parseo de PL/SQL durante el tiempo de ejecución para ser realizado en tiempo de compilación.

- Modificación de las rutinas online, sin necesidad de interferir con otros usuarios - Modificación de una rutina que afecta a múltiples aplicaciones Los procedimientos son simplemente bloques de PL/SQL con nombre, son creados por un esquema particular y como en cualquier otra base de datos, dicho esquema es propietario de los procedimientos que se crean sobre él. Existen dos tipos de bloques de código PL/SQL que pueden ser almacenados dentro de la base de datos, los procedimientos y las funciones. Procedimientos almacenados La sintaxis básica de un procedimiento almacenado es la siguiente: CREATE OR REPLACE PROCEDURE <nombre_proc> IS BEGIN <código> END <nombre_proc>; Procedimientos que no reciben parámetros CREATE OR REPLACE PROCEDURE sin_parametros IS BEGIN dbms_output.put_line('Hola mundo'); END sin_parametros; / set serveroutput on exec sin_parametros; Procedmientos que reciben parámetros por valor La sintaxis básica de un procedimiento que recibe un parámetro Es la siguiente CREATE OR REPLACE PROCEDURE <nombre_proc> ( <nombre_parametro> IN <tipo_dato>) IS BEGIN <código> END <nombre_proc>; Ejemplo: CREATE OR REPLACE PROCEDURE parametro_IN (msg IN VARCHAR2) IS BEGIN dbms_output.put_line(msg); END parametro_IN; / set serveroutput on exec parametro_IN('escribeme esto...'); Procedimientos que reciben parametros por referencia

Page 41: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

40

Su sintaxis básica Es: CREATE OR REPLACE PROCEDURE <nombre_proc> ( <nombre_parametro> OUT <tipo_dato>) IS BEGIN <código> END <nombre_proc>; Ejemplo: CREATE OR REPLACE PROCEDURE parametro_OUT (msg OUT VARCHAR2) IS BEGIN msg := 'Esta es la cadena con la que se ha inicializado la variabel MSG'; END parametro_OUT; / set serveroutput on DECLARE s VARCHAR2(100); BEGIN parametro_OUT(s); dbms_output.put_line(s); END; / Procedimientos con parámetros por valor y por referencia al mismo tiempo En PL/SQL podemos hacer que un parámetro de un procedimiento, sea pasado por valor y por referencia al mismo tiempo, su sintaxis básica Es la siguiente: CREATE OR REPLACE PROCEDURE <nombre_proc> ( <nombre_parametro> IN OUT <tipo_dato>) IS BEGIN <código> END <nombre_proc>;

Ejemplo: CREATE OR REPLACE PROCEDURE parametro_INOUT ( msg IN OUT VARCHAR2) IS BEGIN msg := msg || ' un parámetro IN OUT'; END parametro_INOUT; / set serveroutput on DECLARE s VARCHAR2(50) := 'Este procedimiento usa'; BEGIN parametro_INOUT(s); dbms_output.put_line(s); END; Procedimientos que reciben distintos tipos de parámetros Con PL/SQl podemos crear procedimientos que reciban múltiples parámetros, sin preocuparnos de si estos deben de ser parámetros por valor o por referencia. PL/SQL permite la combinación de múltiples tipos de parámetros en un solo procedimiento La sintaxis básica para este tipo de procedimientos es la siguiente: CREATE OR REPLACE PROCEDURE <nombre_proc> ( <nombre_parametro> IN <tipo_dato>,

Page 42: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

41

<nombre_parametro> OUT <tipo_dato>, <nombre_parametro> IN OUT <tipo_dato>) IS BEGIN <código> END <nombre_proc>; Ejemplo CREATE OR REPLACE PROCEDURE muchos_parametros ( msg1 IN VARCHAR2, msg2 OUT VARCHAR2, msg3 IN OUT VARCHAR2) IS BEGIN msg2 := msg1 || 'Parametro como OUT'; msg3 := msg3 || 'devuelto'; END muchos_parametros; / set serveroutput on DECLARE iparam VARCHAR2(50) := 'este es el IN '; oparam VARCHAR2(50); ioparam VARCHAR2(50) := 'Y este es el IN OUT '; BEGIN muchos_parametros(iparam, oparam, ioparam); dbms_output.put_line(oparam || ' ' || ioparam); END;

Funciones almacenadas Recordemos que un procedimiento puede leer de una lista de valores pero no devolverá ningún valor directamente (si lo hará indirectamente). Una función también lee de una lista de valores pero además esta retornará explícitamente un resultado simple que normalmente ha sido asignado a una variable o utilizado dentro de una estructura de control (como por ejemplo una sentencia IF). Al contrario que un procedimiento la claúsula REPLACE puede ser omitida si no se deseea que un función existente sea reemplazada con el mismo nombre. Las definiciones de las funciones varían de las definiciones de los procedimientos en que se debe explicitamente crear una variable que nos servirá para devolver un valor de retorno a través de la claúsula RETURN. Funciones que no reciben parámetros La sintaxis básica de una función que no recibe parámetros es la siguiente: CREATE OR REPLACE FUNCTION <nomnbre_func> RETURN <tipo_variable> IS <declaraciones de variable> BEGIN <codigo>; END <nombre_func>; Ejemplo: CREATE OR REPLACE FUNCTION funcion_simple RETURN VARCHAR2 IS BEGIN RETURN 'Función simple que \’devuelve\’ esta cadena';

Page 43: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

42

END funcion_simple; Para ejecutar una función en PL/SQL podemos hacerlo seleccionando la función de la tabla DUAL. La tabla DUAL es una tabla especial de Oracle que reside en el esquema SYS, esta tabla se utiliza para que los desarrolladores puedan acceder a cierta funcionalidad que no esta nativamente disponible en PL/SQL. Esta tabla siempre existe, y tiene exactamente una fila. Seleccionar una función de la tabla DUAL causará que el resultado de la función se muestre por pantalla. En una apartado posterior hablaremos de la función DUAL. Select funcion_simple from dual; Funciones que no reciben parámetros utilizando una select CREATE OR REPLACE FUNCTION datos_empleado RETURN varchar2 IS nombre emp.ename%TYPE; trabajo emp.job%TYPE; salario emp.sal%TYPE; BEGIN Select ename, job, sal into nombre, trabajo, salario from emp where ROWNUM < 2; return nombre || ' trabaja como ' || trabajo || ' y gana ' || salario ; END datos_empleado; \ Select datos_empleado from dual; Funciones que no reciben parámetros (uso en una sentencia INSERT) Para utilizar una función en una sentencia insert, lo único que hay que hacer es asegurarse de que el tamaño del valor de retorno de dicha función coincida o entre dentro del tipo de dato de la columna sobre la que insertaremos. En este ejemplo crearemos una tabla en donde insertaremos la cadena que nos devuelve una función, concretamente la que hemos realizado en el apartado anterior: Primero creamos la tabla en donde insertaremos: CREATE TABLE una_tabla( nombre varchar(40), comentario varchar(100)); Y realizamos las inserciones llamando a la función: INSERT INTO una_tabla (nombre,comentario) VALUES ('empleado1',datos_empleado); INSERT INTO una_tabla (nombre,comentario) VALUES ('empleado2',datos_empleado); Realizaremos una select para comprobar que mi función relleno las columnas: Select * from una_tabla; Funciones que no reciben parámetros (uso en una sentencia SELECT)

Page 44: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

43

Ahora realizaremos la acción inversa, utilizaremos una función para recuperar datos de una select, para ello modificaremos la función del apartado anterior , haciendo que devuelva el nombre de un empleado. La función es la siguiente: CREATE OR REPLACE FUNCTION nombre_empleado RETURN varchar2 IS nombre emp.ename%TYPE; BEGIN Select ename into nombre from emp where ROWNUM = 1; return nombre; END nombre_empleado; Y recuperamos los datos del empleado cuyo nombre es aquel que nos devuelve la función: Select * from emp where ename = nombre_empleado; Funciones que reciben parámetros: La sintaxis básica de una función que reibe parámetros es la siguiente CREATE OR REPLACE FUNCTION <nombre_funcion> (<parametros>) RETURN <tipo_variable> IS <declaración de variables> BEGIN <código>; END <nombre_funcion>; El siguiente ejemplo muestra una función que recibe un parámetro, el número de empleado y devuelve el salario de dicho empleado. CREATE OR REPLACE FUNCTION get_salario(p_Employee_ID IN emp.empno%TYPE) RETURN NUMBER IS CURSOR cur_Salario IS SELECT sal FROM emp WHERE empno = p_Employee_ID; salario emp.sal%TYPE; BEGIN OPEN cur_Salario; FETCH cur_Salario INTO salario; CLOSE cur_Salario; RETURN salario; END get_salario; / select get_salario(7900) from dual; Como en el apartado anterior, podemos utilizar nuestra función en una SELECT Select * from emp where sal = get_salario(7900); O en una sentencia INSERT

Page 45: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

44

insert into una_tabla values ('empleado_5', get_salario(7499)); O incluso en una sentencia de actualización de tabla Update una_tabla set nombre = 'nombre_cambiado' where comentario = TO_CHAR(get_salario(7499)); Podemos utilizar las funciones casi para cualquier cosa de la misma manera que las usaríamos en cualquier lenguaje procedural. A continuación mostramos una serie de ejemplos que ayudarán a ilustrar las utilidades de las funciones de PL/SQL Ejemplo 1: La siguiente función devuelve la diferencia en dias entre una fecha y otra CREATE OR REPLACE FUNCTION dias_entre ( fecha_inicio STRING, fecha_fin STRING) RETURN PLS_INTEGER IS BEGIN RETURN TO_DATE(fecha_inicio) - TO_DATE(fecha_fin); EXCEPTION WHEN OTHERS THEN RETURN -1; END dias_entre; / SELECT dias_entre('03-JUN-2004', '03-JUN-2003') FROM dual; Ejemplo 2: La siguiente función determina si la cadena que se le pasa como parámetro es numérica CREATE OR REPLACE FUNCTION es_numerico ( char_in VARCHAR2) RETURN BOOLEAN IS numero NUMBER; BEGIN numero := TO_NUMBER(char_in); RETURN TRUE; EXCEPTION WHEN OTHERS THEN RETURN FALSE; END es_numerico; / BEGIN IF es_numerico('325') THEN dbms_output.put_line('La cadena es un número'); ELSE dbms_output.put_line('La cadena no es un número'); END IF; END;

Page 46: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

45

Control de ERRORES en PL/SQL (Excepciones) PL/SQL permite que se puedan manejar los errores que se producen durante la ejecución de un procedimiento o de una función. Como se dijo al principio, cada bloque PL/SQL puede tener su propia sección de manejo de excepciones, en la cual los errores que ocurran pueden ser gestionados y manejados. Algunas excepciones vienen predefinidas por Oracle, estas se muestran en la siguiente tabla

Error Nombre de la Excepción Se alcanza cuando...

ORA-06530 ACCESS_INTO_NULL El programa intenta asignar valores a los atributos o a un objeto no inicializado (un objeto nulo).

ORA-06592 CASE_NOT_FOUND Ninguna de las elecciones en la claúsula WHEN de un CASE se selecciona y no hay claúsula ELSE.

ORA-06531 COLLECTION_IS_NULL El programa intenta aplicar métodos de colección en vez de EXISTS a una array o tabla no inicializado.

ORA-06511 CURSOR_ALREADY_OPEN El programa intenta abrir un cursor que ya ha sido abierto previamente.

ORA-00001 DUP_VAL_ON_INDEX El programa intenta alamcenar valores duplicados en una columna de una tabla que tiene una restricción UNIQUE.

ORA-00001 INVALID_CURSOR El programa intenta una operación ilegal tal como cerrar un cursor que no ha sido abierto.

ORA-01001 INVALID_NUMBER En una sentencia SQL, la conversion de una cadena en un número falla debido a que la cadena no representa un número válido.

ORA-01017 LOGIN_DENIED El programa intenta hacer logon en Oracle con un usuario o password inválidos.

ORA-01403 NO_DATA_FOUND Cuando una sentencia SELECT INTO no devuelve ninguna fila, o el programa referencia a un elemento borrado.

ORA-01012 NOT_LOGGED_ON El programa intenta una llamada a una base de datos sin estar conectado a Oracle.

ORA-06501 PROGRAM_ERROR El código PL/SQL contiene un problema interno.

ORA-06504 ROWTYPE_MISMATCH El tipo de fila y el tipo de fila de variable de un cursor tienen tipos distintos.

ORA-30625 SELF_IS_NULL

El programa intenta llamar a un método miembro o a una instania nula, es decir, al parámetro SELF (que es siempre el primer parámetro pasado a un método miembro) es nulo.

ORA-06500 STORAGE_ERROR No hay suficiente memoria para ejecutar el programa o la memoria está corrupta.

ORA-06533 SUBSCRIPT_BEYOND_COUNT El programa referencia a una tabla o array anidado utilizando un número de índice más grande que el número de elementos en la colección.

ORA-06532 SUBSCRIPT_OUTSIDE_LIMIT El programa intenta referenciar a un array o una tabla utilizando un índice numérico que no está en el rango de elementos de la tabla o array.

ORA-01410 SYS_INVALID_ROWID La conversión de una cadena de carácteres a un rowid universal falla porque la cadena no representa un rowid

Page 47: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

46

válido.

ORA-00051 TIMEOUT_ON_RESOURCE Un time-out ocurre mientras Oracle espera algún recurso.

ORA-01422 TOO_MANY_ROWS Una sentencia SELECT INTO devuelve más de una fila.

ORA-06502 VALUE_ERROR Un error aritmético, de conversión o incluso un error de restricción ocurre.

ORA-01476 ZERO_DIVIDE El programa intenta dividir un número entre cero.

Ejemplo de uso de excepciones prefedinidas por Oracle: set serveroutput on; DECLARE salario NUMBER(10,2); BEGIN -- SELECT sal into salario FROM emp WHERE job='CLERK'; Select sal into salario from emp where job='hairdresser'; IF salario > 0 THEN dbms_output.put_line('....'); ELSE dbms_output.put_line('....'); END IF; EXCEPTION WHEN NO_DATA_FOUND THEN dbms_output.put_line('No hay empleados que tengan ese trabajo'); WHEN TOO_MANY_ROWS THEN dbms_output.put_line('Se obtiene más de una fila para la consulta'); END;

Declaración de excepciones Como se ha visto en la tabla anterior, algunas excepciones se han definido previamente en el paquete STANDARD del esquema SYS, pero PL/SQL permite la declaración de excepciones propias, la sintaxis básica para declarar una excepción de usuario es la siguiente:

DECLARE Nombre_excepción EXCEPTION;

Ejemplo: DECLARE salario NUMBER; algo_va_mal EXCEPTION; BEGIN Select sal INTO salario from emp where empno='7900'; RAISE Algo_va_mal; EXCEPTION WHEN algo_va_mal THEN dbms_output.put_line('Se ha lanzado un error explícitamente'); WHEN OTHERS THEN dbms_output.put_line('Ha ocurrido algún error'); END; Una excepción solo puede ser declarada una vez en un bloque, pero los bloques anidados, pueden declarar una excepción con el mismo nombre que el que tendría otra en un bloque exterior. Cuando se crea una excepción propia, se debe hacer una llamada a la claúsula RAISE para lanzar explicitamente la excepción. Todas las excepciones declaradas tienen un código de error de 1 y un mensaje “User-defined-exception”, a no ser que utilicemos el pragma EXCEPTION_INIT, en donde nosotros definimos el número de error y

Page 48: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

47

el texto del mensaje, la sintaxis básica de la declaración de una excepción propia a través del PRAGMA es la siguiente:

DECLARE Nombre_excepcion EXCEPTION; PRAGMA EXCEPTION_INIT (nombre_excepción, número_error);

Donde número_error es un valor literal. Este número puede ser un número de error de Oracle tal como 1345, o cualquier otro que se encuentre en el rango –20000 a –20999. Ejemplo: CREATE OR REPLACE PROCEDURE borrar_dept(deptno_ IN NUMBER) BEGIN DECLARE mas_empleados EXCEPTION; PRAGMA EXCEPTION_INIT(mas_empleados,-2292); DELETE FROM dept WHERE deptno = deptno_; EXCEPTION WHEN mas_empleados THEN ...... END; END; Otra forma de ejecutar o lanzar nuestras propias excepciones es utilizando el procedimiento RAISE_APPLICATION_ERROR , el cual nos permite definir excepciones que ocurren en subprogramas almacenados. De esta manera se pueden notificar errores desde nuestros programas y evitar la devolución de excepciones no manejadas. La sintaxis básica de este método es la siguiente: raise_application_error(número_error, mensaje_error[, {TRUE | FALSE}]); Donde el número de error es negativo y está en el rango –20000...-20999 y el mensaje es una cadena de carácteres de hasta 2048 bytes de largo. El tercer parámetro es opcional , si es TRUE, el error es situado en la pila de errores previos, si es FALSE (por defecto), el error reemplaza todos los errores previos de la pila. Una aplicación puede llamar a este método (raise_application_error) solo desde un subprograma almacenado. Cuando es llamado, este finaliza el subprograma y devuelve un número de error de usuario y un mensaje a la aplicación. El número de error y el mensaje pueden ser capturados omo cualquier otro mensaje de error de Oracle. En el siguiente ejemplo, hacemos una llamada a raise_application_error si falta el salario del empleado cuyo número de empleado se le pasa como parámetro a la función. Para ello, lo primero que haremos será actualizar la tabla de empleados y eliminaremos el salario del empleado SCOTT, de esta forma, conseguiremos lanzar la excepción update emp set sal=null where emp.ename='SCOTT'; / CREATE Or REPLACE PROCEDURE actualizar_salario (emp_num NUMBER, cantidad NUMBER) AS salario NUMBER; BEGIN

Page 49: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

48

SELECT sal INTO salario FROM emp WHERE empno = emp_num; IF salario IS NULL THEN raise_application_error(-20101, 'Falta el salario del empleado'); ELSE UPDATE emp SET sal = salario + cantidad WHERE empno = emp_num; END IF; END actualizar_salario; / Select empno from emp where ename='SCOTT'; / exec actualizar_salario(7788,30000);

Las expcepciones capturadas en PL/SQL se propagan a un bloque más exterior si no han sido gestionadas o re-lanzadas en la sección EXCEPTION. Cuando ocurre una excepción, PL/SQL busca un manejador de excepciones que chequee dicha excepción en el bloque actual. Si no se encuentra ninguno, entonces PL/SQL propaga la excepción al bloque contenedor (el bloque que contiene el subbloque en donde se intenta manejar dicha excepción) o a la aplicación que llamó a dicho subbloque. La propagación continua siempre, de más adentro a más afuera, hasta que la excepción es manejada en el bloque más exterior o programa. Si en el bloque o aplicación más exterior, la excepción no es manejada, el programa se para y se realiza un ROLLBACK de las transacciones pendientes.

Una vez que la excepción ha sido manejada, no se seguirá propagando hacia afuera, sencillamente, esta espera al manejador, para continuar con la ejecución del bloque EXCEPTION.

La claúsula WHEN OTHERS Su sintaxis básica es la siguiente,

EXCEPTION WHEN OTHERS THEN ...

Utilizaremos esta claúsula dentro de la sección EXCEPTION para capturar cualquier tipo de error que pueda ocurrir en el bloque en ejecución. Si utilizamos esta claúsula en la sección EXCEPTION, deberemos de colocarla en último lugar, pues este tipo de excepción captura cualquier error y omite el resto de errores que presumiblemente quisieramos capturar. Las funciones SQLCODE y SQLERRM SQLCODE y SQLERRM son funciones pre-definidas de PL/SQL que nos permiten recuperar el código de error y el mensaje de una excepción. Para utilizar estas funciones, lo haremos dentro de la sección EXCEPTION, concretamente cuando utilicemos WHEN OTHERS de manera que podamos manejar errores mediante su número de error. Nota: Recordemos que el pragma EXCEPTION_INIT permite gestionar errores por nombre Ejemplo: CREATE TABLE test( nombre VARCHAR2(100), numero NUMBER, CONSTRAINT no_numeros_peques CHECK (numero > 1000));

Page 50: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

49

BEGIN INSERT INTO test (nombre, numero) VALUES ('Pedro',2); EXCEPTION WHEN OTHERS THEN IF SQLCODE = -2290 AND SQLERRM LIKE '%NO_NUMEROS_PEQUES%' -- AND SQLERRM LIKE 'no_numeros_peques'; THEN DBMS_OUTPUT.PUT_LINE('El núnero es demasiado pequeño'); ELSE DBMS_OUTPUT.PUT_LINE('Excepcion no capturada, '||' código ='||SQLCODE); DBMS_OUTPUT.PUT_LINE(SQLERRM); END IF; END; Tipos de Datos Estructurados PL/SQL nos proporciona tres tipos de datos estructurados. Los registros (RECORD) y los vectores (TABLE y VARRAY). Todos estos tipos se declaran en la sección DECLARE como si fueran variables. Los registros de PL/SQL Un registro de PL/SQL es una colección de variables que se agrupan dentro de una estructura de manera que todas ellas formen un único tipo de elemento o variable, las varibales que contienen los registros se denominan campos. El acceso a los campos dentro del registro es inmediato, como lo es el acceso a cualquier varibale que hayamos declarado, la única cosa a tener en cuenta, es que para acceder a los campos que contiene el registro es necesario anteponer al nombre del campo el nombre del registro que la contiene. Para declarar un tipo registro se emplea la siguiente sintaxis: TYPE nombre_tipo IS RECORD (campo [, campo] ...); TYPE tipo_empleado_reg IS RECORD( nombre VARCHAR2(10), salario VARCHAR2(8), trabajo NUMBER(6)); mi_empleado tipo_empleado_reg; Para referenciar los campos de un registro es necesario utilizar el separador ‘.’, anteponiendo el nombre del registro al nombre del campo como se muestra en el ejemplo: set serveroutput on; DECLARE TYPE tipo_empleado_reg IS RECORD( nombre VARCHAR2(20), salario NUMBER(8), trabajo VARCHAR2(20), fecha_alta emp.hiredate%TYPE); mi_empleado tipo_empleado_reg; BEGIN mi_empleado.nombre := 'empleado_1';

Page 51: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

50

mi_empleado.salario := 3000; mi_empleado.trabajo := 'secretario'; dbms_output.put_line(mi_empleado.nombre); IF mi_empleado.salario > 1000 THEN dbms_output.put_line(mi_empleado.salario); END IF; END; Los valores pueden ser asignados a los registros de cuatro maneras distintas: - El operador de asignación puede ser utilizado para asignar valor a un campo:

mi_empleado.fecha_alta := sysdate;

...... dbms_output.put_line(mi_empleado.fecha_alta); - Se puede realizar una SELECT INTO para un registro entero o para campos del registro

Individuales:

set serveroutput on; DECLARE TYPE tipo_empleado_reg IS RECORD( num_emp emp.empno%TYPE, nombre emp.ename%TYPE, trabajo emp.job%TYPE, num_mig emp.mgr%TYPE, fecha_alta emp.hiredate%TYPE, salario emp.sal%TYPE, comm emp.comm%TYPE, num_dept emp.deptno%TYPE); mi_empleado tipo_empleado_reg; BEGIN Select * into mi_empleado from emp where empno=7900; dbms_output.put_line(mi_empleado.nombre); dbms_output.put_line(mi_empleado.salario); dbms_output.put_line(mi_empleado.fecha_alta); END;

- También podemos utilizar la claúsula FETCH para cargar un registro completo o campos individuales

set serveroutput on; DECLARE CURSOR mi_cursor IS Select * from emp where empno=7900; TYPE tipo_empleado_reg IS RECORD( num_emp emp.empno%TYPE, nombre emp.ename%TYPE, trabajo emp.job%TYPE, num_mig emp.mgr%TYPE, fecha_alta emp.hiredate%TYPE, salario emp.sal%TYPE, comm emp.comm%TYPE, num_dept emp.deptno%TYPE);

Page 52: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

51

mi_empleado tipo_empleado_reg; BEGIN OPEN mi_cursor; FETCH mi_cursor into mi_empleado; dbms_output.put_line(mi_empleado.nombre); dbms_output.put_line(mi_empleado.salario); dbms_output.put_line(mi_empleado.fecha_alta); CLOSE mi_cursor; END;

- O asignar el contenido de un registro a otro utilizando el operador de asignación set serveroutput on; DECLARE CURSOR mi_cursor IS Select * from emp where empno=7900; TYPE tipo_empleado_reg IS RECORD( num_emp emp.empno%TYPE, nombre emp.ename%TYPE, trabajo emp.job%TYPE,

num_mig emp.mgr%TYPE, fecha_alta emp.hiredate%TYPE, salario emp.sal%TYPE, comm emp.comm%TYPE, num_dept emp.deptno%TYPE); mi_empleado tipo_empleado_reg; mi_empleado2 tipo_empleado_reg; BEGIN OPEN mi_cursor; FETCH mi_cursor into mi_empleado; mi_empleado2 := mi_empleado; dbms_output.put_line(mi_empleado2.nombre); dbms_output.put_line(mi_empleado2.salario); dbms_output.put_line(mi_empleado2.fecha_alta); close mi_cursor; END;

Los Arrays y las Tablas de PL/SQL El uso de los arrays (vectores) o tablas en PL/SQL se hace prácticamente de la misma manera que en cualquier otro lenguaje procedural, excepto en su inicialización y asignación que difiere un poco del resto de lenguajes. Para declarar vectores o tablas en PL/SQL se emplea la siguiente sintaxis: TYPE nombre_array IS VARRAY (tamaño_maximo) OF tipo_datos [NOT NULL]; Tanto en las tablas como en los vectores, el índice empieza a contar a partir de 1. Los vectores pueden ser creados vacíos o rellenos. A partir de entonces, cada vez que deseemos insertar nuevos elementos en el vector será necesario utilizar la función EXTEND. Veamos el ejemplo:

DECLARE TYPE tipo_empleado_reg IS RECORD( num_emp emp.empno%TYPE, nombre emp.ename%TYPE, trabajo emp.job%TYPE,

Page 53: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

52

num_mig emp.mgr%TYPE, fecha_alta emp.hiredate%TYPE, salario emp.sal%TYPE, comm emp.comm%TYPE, num_dept emp.deptno%TYPE); -- declaración de tipos TYPE tipo_varray IS VARRAY(3) OF emp.ename%TYPE; TYPE tipo_varray2 IS VARRAY(3) OF emp.ename%TYPE; TYPE tipo_varray3 IS VARRAY(2) OF NUMBER; TYPE tipo_varray_record IS VARRAY(2) OF tipo_empleado_reg; -- declaración de variables v_varray1 tipo_varray; v_varray2 tipo_varray; --Realizamos la inicialización en la misma declaración v_varray3 tipo_varray3:=tipo_varray3(1,2); --ERROR índice fuera de rango --v_varray3 tipo_varray3:=tipo_varray3(1,2); --Declaracion de una varibale de tipo tipo_varray_record array_empleados tipo_varray_record; --Delcaración de dos variables de tipo registro empleado1 tipo_empleado_reg; empleado2 tipo_empleado_reg; --Índice para recorrer un array i INTEGER := 0; BEGIN v_varray1 := tipo_varray('PEDRO', 'MARIA'); -- se crea con dos elementos v_varray1.EXTEND; v_varray1(3) := 'LUIS'; dbms_output.put_line(v_varray1(1)); dbms_output.put_line(v_varray1(2)); /* v_varray1(4) := 'Juan'; ERROR !!!no se ha extendido el array para ubicar nuevos valores */ --Podemos crear el array vacío v_varray2 := tipo_varray(); -- O preguntar si está vacío IF v_varray2 IS NULL THEN -- Y asignarle el contenido completo de otro array -- que este relleno si son del mismo tipo v_varray2 := v_varray1; --asignación de vectores END IF; --Inicialización del array de registros Select * into empleado1 from emp where empno=7900; Select * into empleado2 from emp where ename='SCOTT'; array_empleados := tipo_varray_record(empleado1,empleado2); FOR i IN 1 .. 2 LOOP dbms_output.put_line(array_empleados(i).num_emp); END LOOP; END;

Page 54: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

53

La declaración de tablas es prácticamente similar a la declaración de arrays(vectores). Posiblemente la principal diferencia que existe entre un vector y una tabla es que en los tipos VARRAY no se pueden borrar elementos, por lo que sus posiciones se deben ocupar consecutivamente. Sin embargo, en los tipos TABLE se pueden borrar elementos con la instrucción DELETE , pudiendo quedar huecos intermedios vacíos y sin poderse referenciar, aunque se pueden volver a llenar con una asignación. La función EXISTS nos permite saber si un elemento se puede referenciar o si ha sido borrado. Para declarar una tabla utilizamos la siguiente sintaxis: TYPE nombre_tabla IS TABLE OF tipo_datos [NOT NULL]; Observemos el siguiente ejemplo: DECLARE TYPE t_table IS TABLE OF VARCHAR2(20); v_table1 t_table; v_table2 t_table; BEGIN v_table1 := t_table('Pedro', 'Luis','Federico'); v_table1(2) := NULL; -- Dejar una posición vacía no es igual que borrarla v_table1.DELETE(3); -- Así es como se borra una posición v_table1.EXTEND; v_table1(4) := 'María'; -- v_table1(5) := 'Juan'; --ERROR v_table2 := t_table();--O podemos crearla vacía --Podemos preguntar si existe el primer elemento de la tabla IF v_table1.EXISTS(1) THEN dbms_output.put_line(v_table1(1)); ELSE v_table1(1) := 'Jose'; --Volvemos a crear el elemento 1 END IF; END; Arrays asociativos (arrays por índice): Un array asociativo es un conjunto de parejas clave-valor, en donde cada clave es única y se utiliza para obtener su correspondiente valor en el array. La clave puede ser un entero o una cadena Si asignamos valores utilizando la misma clave, conseguiremos que se actualice el contenido correspondiente a dicha clave. Es importante elegir una clave que sea única. En el ejemplo siguiente declaramos un tipo de array asociativo, y dos variables cuyo tipo es el del array que acabamos de declarar, para acceder a los valores utilizamos claves que son cadenas:

DECLARE TYPE tipo_colores IS TABLE OF VARCHAR2(10) INDEX BY VARCHAR2(64); colores_claros tipo_colores; colores_oscuros tipo_colores; que_color VARCHAR2(64); total PLS_INTEGER; BEGIN colores_claros('cyan') := '#234325'; colores_claros('yellow') := '#AA76EF'; que_color := colores_claros('cyan'); dbms_output.put_line(que_color); -- #234325 colores_oscuros('black') := '#000000'; colores_oscuros('red') := '#00ff44'; colores_oscuros('purple') := '#ee0054'; que_color := colores_oscuros.FIRST; dbms_output.put_line(que_color); -- black que_color := colores_oscuros.LAST;

Page 55: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

54

dbms_output.put_line(que_color); -- red total := colores_oscuros.COUNT; dbms_output.put_line(total); -- 3 END;

Posiblemente pueda parecer raro que la linea de código dbms_output.put_line(que_color); devuelva ‘red’ en vez de ‘purple’. En los arrays asociativos, el valor de retorno, al hacer la llamada al metodo LAST, se hace por orden alfabético, de esta forma, PL/SQL ordena todas las claves y devuelve la última de la lista ordenada alfabéticamente. Nota: Debido a que los arrays asociativos pretenden ser utilizados para almacenar datos temporales en vez de datos persistentes, no podemos utilizarlos en sentencias SQL, tales como INSERT o SELECT INTO. Cuando se procesa una tabla por índice en PL/SQL, hay veces que no podemos estar seguros de que el índice existe en la tabla, pues, como se dijo anteriormente, las tablas pueden tener huecos o índices sin rellenar. Podríamos utilizar un bucle FOR, para recorrer todos los valores, como en el ejemplo:

Declare type tipo_lista is table of number index by pls_integer; lista tipo_lista; begin -- Comenzamos en el índice 2 en vez de en el índice 1 lista(2) := 2; lista(3) := 3; -- nos saltamos el 4 y el 5 lista(6) := 6; for i in 1 .. lista.COUNT loop dbms_output.put_line(lista(i)); end loop; end;

El código de arriba arrojará un error de tipo ORA-01403: no se han encontrado datos, porque el índice 1 falta. Podemos intentar arreglar el problema utilizando los metodos, FIRST y LAST, de manera que empecemos en el primer índice y terminemos en el último, como se muestra en el ejemplo:

Declare type tipo_lista is table of number index by pls_integer; lista tipo_lista; begin lista(2) := 2; lista(3) := 3; lista(5) := 6; for i in lista.FIRST .. lista.LAST loop dbms_output.put_line(lista(i)); end loop; end;

Este código también nos dará un error del tipo ORA-01403: no se han encontrado datos, debido a los valores que nos hemos saltado. Para arreglar este error, se puede utilizar el metodo EXISTS, para verificar la existencia de un índice antes de acceder a él:

set serveroutput on; Declare

Page 56: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

55

type tipo_lista is table of number index by pls_integer; lista tipo_lista; begin lista(2) := 2; lista(3) := 3; lista(6) := 6; for i in lista.FIRST..lista.LAST loop if (lista.EXISTS(i)) then dbms_output.put_line(lista(i)); end if; end loop; end;

O incluso podríamos haber utilizado un LOOP normal y usar el método NEXT, para iterar a través de los índices existentes de la tabla. Para hacer esto, es necesario declarar nuestro propio contador

Declare type tipo_lista is table of number index by pls_integer; lista tipo_lista; contador pls_integer; begin lista(2) := 2; lista(3) := 3; lista(6) := 6; contador := lista.FIRST; loop dbms_output.put_line(lista(contador)); exit when contador = lista.LAST; contador := lista.NEXT(contador); end loop; end;

Paquetes de PL/SQL Un paquete es un objeto de base de datos que agrupa de manera lógica, tipos de datos PL/SQL, objetos y subprogramas. Normalmente un paquete se compone de dos partes, una especificación y cuerpo. La especificación es una especie de interfaz para aplicaciones, declara tipos, variables, constantes, excepciones, cursores y subprogramas de manera que estos esten disponibles en las aplicaciones. De esta forma el cuerpo define dichos cursores, subprogramas, etc.. y los implementa. Al contrario que los subprogramas, los paquetes no pueden ser lamados, parametrizados, o anidados. Su sintaxis es la siguiente. CREATE PACKAGE nombre_paquete AS -- especificación (parte visible) -- tipos públicos y declaración de objetos -- especificación de subprogramas END [nombre_paquete]; CREATE PACKAGE BODY nombre AS -- cuerpo (parte no visible) -- tipos privados y declaración de objetos -- cuerpos de subprogramas [BEGIN -- sentencias] END [nombre];

Page 57: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

56

La especificación mantiene declaraciones públicas, que son visibles para las aplicaciones. El cuerpo del paquete mantiene la implementación de las declaraciones públicas (declaraciones privadas), estas no son visibles por las aplicaciones. Paquetes que defininen constantes CREATE OR REPLACE PACKAGE <nombre_paquete> IS <nombre_variable> CONSTANT <tipo_dato> := <valor>; END <nombre_paquete>; Ejemplo: CREATE OR REPLACE PACKAGE paq_constantes IS nombre_libro CONSTANT DATE := TO_DATE('20-JUN-1998'); autor CONSTANT VARCHAR2(30) := 'Perez Ruarte'; isbn CONSTANT NUMBER(8) := 24354; END paq_constantes; / set serveroutput on DECLARE valor VARCHAR2(100); BEGIN valor := 'Daniel ' || paq_constantes.autor; dbms_output.put_line(valor); END; Paquetes que definen procedimientos Cuando se definen paquetes que contienen procedimientos o funciones, no basta con declarar la parte pública del mismo, es necesario declarar también la parte privada o cuerpo, que es en donde realizamos la implementación de los métodos o funciones declarados previamente. Su sintaxis es la siguiente: CREATE OR REPLACE PACKAGE <nombre_paquete> AS PROCEDURE <nombre_procedimiento> (<parametros>); END <nombre_paquete>; / CREATE OR REPLACE PACKAGE BODY <nombre_paquete> AS PROCEDURE <nombre_procedimiento> (<parametros>) IS <definición de varibales locales, constantes y excepciones> BEGIN <código>; END <nombre_procedimiento>; END <nombre_paquete>; Ejemplo:

CREATE OR REPLACE PACKAGE paquete_proc AS PROCEDURE get_nombreTabla(numero IN PLS_INTEGER);

Page 58: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

57

END paquete_proc; / CREATE OR REPLACE PACKAGE BODY paquete_proc AS PROCEDURE get_nombreTabla(numero IN PLS_INTEGER) IS nombre_tabla user_tables.table_name%TYPE; BEGIN SELECT table_name INTO nombre_tabla FROM user_tables WHERE rownum < numero; dbms_output.put_line(nombre_tabla); EXCEPTION WHEN OTHERS THEN dbms_output.put_line('Demasiadas filas'); END get_nombreTabla; END paquete_proc; / set serveroutput on; exec paquete_proc.get_nombreTabla(2);

Ejemplo: Definición y cuerpo de un paquete que contiene varios procedimientos. El siguiente ejemplo muestra un paquete y un cuerpo que defininen tres procedmientos: El primero da de alta un empleado, recibe el número de empleado y su nombre. El segundo procedimiento da de baja un empleado, recibe el número de empleado que se eliminará de la tabla, el tercer procedimiento modifica el salario, recibe un entero que correponde con el número de empleado al cual se le modificará el sueldo y la nueva cantidad que percibirá.

CREATE OR REPLACE PACKAGE paquete_procs AS --TYPE lista IS VARRAY(25) of NUMBER(3); PROCEDURE alta_empleado (emp_no INTEGER, nombre VARCHAR2); PROCEDURE despedir_empleado (emp_no INTEGER); PROCEDURE modificar_salario (emp_no INTEGER, cantidad REAL); END paquete_procs; / CREATE OR REPLACE PACKAGE BODY paquete_procs AS PROCEDURE alta_empleado (emp_no INTEGER, nombre VARCHAR2) IS BEGIN INSERT INTO emp (empno,ename) VALUES (emp_no, nombre); END alta_empleado; PROCEDURE despedir_empleado (emp_no INTEGER) IS BEGIN DELETE FROM emp WHERE empno = emp_no; END despedir_empleado; PROCEDURE modificar_salario (emp_no INTEGER, cantidad REAL) IS

Page 59: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

58

BEGIN DBMS_OUTPUT.PUT_LINE('Modificación de salario :' || to_char(cantidad)); UPDATE emp SET sal = sal + cantidad WHERE empno = emp_no; END modificar_salario; END paquete_procs;

Paquetes que definen funciones (Miembros Ocultos) La sintaxis básica para un paquete que define funciones es la siguiente: CREATE OR REPLACE PACKAGE <nombre_paquete> AS PROCEDURE <nombre_proc> (<parametros>); -- el miembro oculto no se define aqui -- solo de define en el cuerpo del paquete -- FUNCTION <nombre_func> (<parametros>); END <nombre_paquete>; Nota : El miembro que estará oculto no se define en la cabecera del paquete, solo en el cuerpo. Debe de existir al menos otro miembro que relacione al miembro oculto con el miembro público, como se muestra en el ejemplo.

CREATE OR REPLACE PACKAGE miembro_oculto AS PROCEDURE buscar_emp(emp_no NUMBER); END miembro_oculto; / CREATE OR REPLACE PACKAGE BODY miembro_oculto AS FUNCTION buscar_emp(emp_no NUMBER) RETURN VARCHAR2 IS nombre VARCHAR2(40); BEGIN Select ename into nombre from emp where empno = emp_no; RETURN nombre; END buscar_emp; PROCEDURE buscar_emp(emp_no NUMBER) IS valor VARCHAR2(50); BEGIN valor := buscar_emp(emp_no); dbms_output.put_line(valor); END buscar_emp; END miembro_oculto; / set serveroutput on exec miembro_oculto.buscar_emp(7499);

Paquetes con miembros sobrecargados En un paquete, una función o procedimiento está sobrecargado si existe otra función o método que tiene el mismo nombre pero posee distintos tipos de parámetros. En el ejemplo de abajo, tenemos un paquete que declara una función que recibe una cadena de texto que corresponde con el nombre de un empleado y encuentra su salario. Para mostrar la sobrecarga, hemos escrito otra función que se llama de la misma manera, pero recibe, en vez de una cadena, un número. La funcionalidad es la misma, pero le doy la posibilidad al usuario de hacer la búsqueda del salario de un empleado, por nombre o por número. Veamos el ejemplo:

Page 60: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

59

CREATE OR REPLACE PACKAGE sobrecargado IS FUNCTION get_salario(nombre VARCHAR2) return PLS_INTEGER; FUNCTION get_salario(num_emp PLS_INTEGER) return PLS_INTEGER; END sobrecargado; / CREATE OR REPLACE PACKAGE BODY sobrecargado IS FUNCTION get_salario(nombre VARCHAR2) RETURN PLS_INTEGER IS salario PLS_INTEGER; BEGIN SELECT sal into salario from emp where ename = nombre; RETURN salario; END get_salario; FUNCTION get_salario(num_emp PLS_INTEGER) RETURN PLS_INTEGER IS salario PLS_INTEGER; BEGIN Select sal into salario from emp where empno = num_emp; RETURN salario; END get_salario; END sobrecargado; / set serveroutput on; Select sobrecargado.get_salario('TURNER') from dual; Select sobrecargado.get_salario(7844) from dual;

Triggers de Oracle Oracle permite definir procedimientos llamados TRIGGERS que se ejecutan de manera automatica cuando una estructura INSERT, UPDATE o DELETE es empleada sobre una tabla e incluso en algunas ocasiones sobre una vista; asi mismo pueden estar asociados a eventos que ocurran sobre la Base de datos. Estos procedimientos pueden ser escritos en PL/SQL o Java y almacenados en la Base de Datos o implementados en C como llamadas externas. Un TRIGGER almacenado en la BD puede incluir estructuras de SQL, PL/SQL o Java e invocar a procedimientos almacenados. Un TRIGGER difiere en gran medida de un procedimiento almacenado en la BD ya que unos son ejecutados de manera explicita por el usuario, los TRIGGER son ejecutados de manera implicita por Oracle ante la ocurrencia de algun evento. La sintaxis para crear un trigger es la siguiente:

Page 61: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

60

CREATE OR REPLACE TRIGGER <nombre_trigger> <BEFORE | AFTER> <ACTION> ON <nombre_tabla> DECLARE <definicionde variables> BEGIN <codigo_trigger> EXCEPTION <codigo_exception> END <nombre_trigger>; Cuando se crea un trigger de base de datos, se comprueba su sintaxis, se compila y alamcena en al base de datos. Los triggers son similares a los paquetes o procedimientos. La principal diferencia es que el código de los triggers es almacenado en una tabla del diccionario de datos diferente de la que almacena los procedimientos. Si existen errores durante la creación o compilación de un trigger, este sigue siendo almacenado. Aunque el trigger sea almacenado, este necesitará ser borrado, arreglado o creado de nuevo. A la hora de crear triggers es recomendable que este mantenga el máximo nivel de simplicidad como sea posible y que sea creado únicamente si verdaderamente es necesario. Si un trigger necesita de muchas lineas de código, es posible almacenar parte de este código en un procedimiento y hacer la llamada a este procedimiento desde el mismo trigger. El máximo tamaño que puede ocupar un trigger es 32K. Seguridad de los triggers de la base de datos Para crear un trigger de base de datos, el esquema debe de tener tres privilegios de sistema CREATE TRIGGER : Este privilegio permite que un esquema pueda crear triggers sobre tablas que le pertenezcan. CREATE ANY TRIGGER: Este privilegio premite que un esquema pueda crear triggers de base de datos sobre tablas que pertenecen a otro esquema. ADMINISTER DATABASE TRIGGER : Este privilegio permite a un esquema crear triggers sobre todas las tablas de base de datos. Habilitar y deshabilitar triggers Habilitar o deshabilitar triggers de la base de datos, solo nos sirve para poner a un estado valido o inválido oibjetos de la base de datos. En algunas ocasiones, un trigger deshabilitado es más peligroso que un objeto de base de datos invalido, pues este no falla, sencillamente, NO SE EJECUTA!!!. Esto puede tener consecuencias en aplicaciones que dependen de una lógica almacenada basada en código alamacenado que se ejecuta de manera transparente a los usuarios o incluso al DBA (ejemplo: un trigger que almacena valores en una tabla log, si el trigger está deshabilitado, este no funciona y tampoco falla, con lo que no recibimos ningún mensaje de error). Para asegurarnos que un código contenido en un trigger es ejecutado cuando corresponde, podemos ejecutar el siguiente script: SELECT trigger_name, trigger_type, base_object_type, triggering_event FROM user_triggers WHERE status <> 'ENABLED' AND base_object_type IN ('DATABASE ', 'SCHEMA') ORDER BY trigger_name; Una vez que el trigger ha sido identificado, podemos habilitarlo manualmente o mediante la creación de un script que habilite los triggers que quedan deshabilitados. Para habilitar un triggger podemos ejecutar las siguientes sentencias: ALTER TRIGGER db_startup_trigg ENABLE; -- habilita un trigger de la base de datos

Page 62: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

61

ALTER TRIGGER before_insert_empleados ENABLE; -- habilita un trigger de tabla ALTER TABLE s_emp ENABLE ALL TRIGGERS; -- habilita todos los triggers sobre una tabla Hay otras veces que es necesario deshabilitar triggers, para una carga de datos, por ejemplo. La sintaxis para deshailitar un trigger es la siguiente ALTER TRIGGER DB_STARTUP_TRIGG DISABLE; ALTER TRIGGER before_insert_emp DISABLE; ALTER TABLE s_emp DISABLE ALL TRIGGERS; Los triggers de base de datos de la versión 9i, se han separado en dos categorías. Los triggers que se ejecutan por eventos del sistema de base de datos y los que se ejecutan por eventos cliente o DDL (Data Definition Language). Para cada evento, existen lo que se denominan atributos de evento que son establecidos por Oracle de manera interna cuando ocurre un evento. Estos atributos pueden ser referenciados en la lógica del trigger. Por ejemplo, la sentencia CREATE, puede referenciar al nombre del esquema, al tipo de objeto creado, el nombre del objeto, etc. Eventos de base de datos Hay seis eventos de base de datos para triggers. La siguiente tabla muestra estos eventos.

Database Trigger BEFORE/AFTER Description Attribute Event LOGOFF BEFORE Se ejecuta cuando un

usuario hace logoff en la base de datos.

ora_sysevent ora_login_user ora_instance_num ora_database_name

LOGON AFTER Ejecutado cuando un usuario entra en la base de datos, exactamente cuando hay un login cuyo usuario y pasword son correctos.

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_client_ip_address

STARTUP AFTER Se ejecuta cuando una base de datos es abierta;

ora_sysevent ora_login_user ora_instance_num ora_database_name

SHUTDOWN BEFORE Se ejecuta cuando se hace shutdown sobre una instancia.

ora_sysevent ora_login_user ora_instance_num ora_database_name

SERVERERROR AFTER Se ejecuta cuando ocurre un error de Oracle (se puede chequear el número de un error para que solo se ejecute cuando ocurre uno en concreto)

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_server_error ora_is_servererror space_error_info

SUSPEND AFTER Se ejecuta cuando un error del servidor hace que una transacción en ejecución se suspenda (ej//out-of-space)

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_server_error ora_is_servererror space_error_info

Los triggers para shutdown y startup solo pueden ser creados a nivel de base de datos. Los otros cuatro eventos pueden ser creados a nivel de base de datos o de esquema. Eventos cliente o DDL:

Page 63: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

62

Hay 14 eventos cliente o DDL que pueden ser creados a nivel de base de datos y que se ejecutarán para todos los esquemas. Estos eventos pueden también ser creados a nivel de esquema y hacer que solo se ejecuten para el esquema para el que fueron creados. Cuando un trigger se crea a nivel de esquema, el trigger se crea para un esquema espeífico y se ejecuta solo para ese esquema. La siguiente tabla muestra los eventos DDL. Database Trigger BEFORE/AFTER Description Attribute Events

ALTER

BEFORE/AFTER

Es ejecutado cuando se modifica un objeto de la base de datos

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_type ora_dict_obj_name ora_dict_obj_owner ora_des_encrypted_password (para eventos de modificación de usuario) ora_is_alter_column, ora_is_drop_column (para eventos de modificación de tablas)

DROP

BEFORE/AFTER

Se ejecuta cuando se elimina un objeto de la base de datos

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_type ora_dict_obj_name ora_dict_obj_owner

ANALYZE

BEFORE/AFTER

Se lanza cuando el comando ANALYZE es ejecutado

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner

ASSOCIATE STATISTICS

BEFORE/AFTER

Se lanza cuando el comando associate statistics se ejecuta.

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_dict_obj_name_list ora_dict_obj_owner_list

AUDIT/NOAUDIT

BEFORE/AFTER

Se lanza cuando el comando audit o noaudit es ejecutado

ora_sysevent ora_login_user ora_instance_num ora_database_name

COMMENT

BEFORE/AFTER

Se lanza cuando el comando comment es ejecutado

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner

CREATE

BEFORE/AFTER

Se ejecuta cuando un objeto es creado

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_type ora_dict_obj_name ora_dict_obj_owner ora_is_creating_nested_table (for CREATE TABLE events)

DDL BEFORE/AFTER Se lanza cuando algún comando DDL es ejecutado (no se ejecuta con un ALTER/CREATE DATABASE o CREATE CONTROLFILE)

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner

Page 64: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

63

DISASSOCIATE STATISTICS

BEFORE/AFTER

Se lanza cuando el comando disassociate statistics se ejecuta

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_dict_obj_name_list ora_dict_obj_owner_list

GRANT

BEFORE/AFTER

Se lanza cuando el comando grant se ejecuta

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_grantee ora_with_grant_option ora_privileges

RENAME

BEFORE/AFTER

Se lanza cuando el comando rename es ejecutado.

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_owner ora_dict_obj_type

REVOKE

BEFORE/AFTER

Se lanza cuando el comando revoke se ejecuta.

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner ora_revokee ora_privileges

TRUNCATE

BEFORE/AFTER

Se ejecuta cuando se trunca una tabla.

ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner

Los triggers son una buena herramienta para los administradores de bases de datos, pues permiten construir mecanismos basados en eventos (por ejemplo, se podría construir un sistema de logs que controlara el workflow de la base de datos). La siguiente tabla muestra los atributos de los eventos con una pequeña descripción y el tipo de dato que tiene cada uno:

Attribute Event Data Type Description

ora_client_ip_address VARCHAR2 Devuleve la dirección IP de la máquina cliente cuando se utiliza un protocolo TCP/IP

ora_database_name VARCHAR2(50) Devuelve el nombre de la base de datos

ora_des_encrypted_password VARCHAR2 Devuelve la password encriptada del usuario que

esta siendo creado o modificado

ora_dict_obj_name VARCHAR(30) Devuelve el nombre del objeto que esta siendo manipulado

ora_dict_obj_name_list BINARY_INTEGER Devuelve una lista de objetos que están siendo

Page 65: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

64

(name_list OUT ora_name_list_t)

manipulados

ora_dict_obj_owner VARCHAR(30) Devuelve el nombre del propietario del objeto que está siendo manipulado

ora_dict_obj_owner_list(owner_list OUT ora_name_list_t)

BINARY_INTEGER Devuelve los nombres de los propietarios de los objetos que están siendo manipulados

ora_dict_obj_type VARCHAR(20) Devuelve el tipo de objeto que está siendo manipulado

ora_grantee( user_list OUT ora_name_list_t)

BINARY_INTEGER Devuelve la lista de usuarios que tienen permisos

ora_instance_num NUMBER Devuelve el número de instancia ora_is_alter_column( column_name IN VARCHAR2) BOOLEAN

Devuelve un valor de TRUE si la columna específica ha sido modificada

ora_is_creating_nested_table BOOLEAN Devuelve un valor de TRUE si el evento actual crea

una tabla anidada ora_is_drop_column( column_name IN VARCHAR2) BOOLEAN

Devuelve un valor de TRUE si la columna específica es eliminada

ora_is_servererror BOOLEAN Devuelve un valor de TRUE si el error especificado está en la pila de errores

ora_login_user VARCHAR2(30) Devuelve el usuario que hizo login en la sesión actual

ora_partition_pos BINARY_INTEGER

Devuelve la posición en un comando CREATE TABLE donde la claúsula de partición puede ser utilizada cuando se utiliza la sentencia INSTEAD OF en triggers

ora_privilege_list( privilege_list OUT ora_name_list_t)

BINARY_INTEGER Devuelve la lista de privilegios que han sido concedidos o revocados

ora_revokee (user_list OUT ora_name_list_t)

BINARY_INTEGERDevuelve la lista de los usuarios que han perdido algun/os privilegios cuando se ejecuta el comando REVOKE

ora_server_error NUMBER Devuelve el error, de la pila de errores, para una posición específica en la pila (donde uno es la parte más superior de la pila)

ora_server_error_depth BINARY_INTEGER Devuelve el número total de errores en la pila de errores

ora_server_error_msg (position in binary_integer)

VARCHAR2 Devuelve el error de la pila para la posición especificada en la pila (1 es la posición más alta de la pila)

ora_server_error_num_params (position in binary_integer)

BINARY_INTEGER Devuelve el número de cadenas que han sido sustituidas dentro del mensaje de error

ora_server_error_param (position in binary_integer, param in binary_integer)

VARCHAR2

Devuelve el valor de la sustitución que encaja en el mensaje de error con el número de parámetro especificado en conjunción con el valor especificado de la posición en la pila

ora_sql_txt (sql_text out ora_name_list_t)

BINARY_INTEGER

Devuelve la sentencia SQL de la sentencia que provocó que se ejecutara el trigger (Si la sentencia es demasiado larga, esta será separada en multiples elementos de tabla de PL/SQL) el valor devuelto

Page 66: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

65

especifica el número de elementos

ora_sysevent VARCHAR2(20) Devuelve el evento de sistema o de cliente que provocó que el trigger se ejecutara

ora_with_grant_option BOOLEAN Devuelve un valor de TRUE si los privilegios concedidos han sido otorgados con la instrución GRANT

space_error_info( error_number OUT NUMBER, error_type OUT VARCHAR2, object_owner OUT VARCHAR2, table_space_name OUT VARCHAR2, object_name OUT VARCHAR2, sub_object_name OUT VARCHAR2)

BOOLEAN

Devuelve un valor de true si el error esta relacionado con un error del tipo out-of-space y devuelve un objeto de información con el error

A continuación se listan algunos ejemplos de triggers que demuestran la flexibilidad del uso de código almacenado ejecutado sobre eventos. Para utilizar estos ejemplos crearemos una tabla llamada logon_session en donde guardaremos un registro de las entradas y salidas de usuarios de la base de datos. Hay que tener en cuenta que la tabla se rellenará cuando se haga logon o logoff sobre la base de datos con lo que entonces, esta puede ser creada en el esquema SYS pues este trigger se ejecuta a nivel de base de datos no a nivel usuario. CREATE TABLE logon_session ( sid NUMBER, usuario VARCHAR2(30), hora_comienzo DATE, hora_fin DATE); Este es el trigger que se ejecutará cuando se haga logon sobre la base de datos CREATE OR REPLACE TRIGGER logon_trigger AFTER LOGON ON DATABASE BEGIN INSERT INTO logon_session (sid, usuario, hora_comienzo) SELECT DISTINCT sid, ora_login_user, SYSDATE FROM v$mystat; END; Y este el trigger que se ejecutará cuando se haga logoff sobre la base de datos CREATE OR REPLACE TRIGGER logoff_trigger BEFORE LOGOFF ON DATABASE BEGIN UPDATE logon_session SET hora_fin = SYSDATE WHERE sid = (select distinct sid from v$mystat) AND hora_fin IS NULL; END; El SID es seleccionado de la vista V$MYSTAT. El siguiente script devuelve la información sobre la tabla logon_session:

Page 67: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

66

COLUMN usuario FORMAT a15 COLUMN hora_comienzo FORMAT a20 COLUMN hora_fin FORMAT a20 SELECT sid, usuario, TO_CHAR(hora_comienzo, 'MM/DD/YYYY HH24:MI:SS') hora_emtrada, TO_CHAR(hora_fin, 'MM/DD/YYYY HH24:MI:SS') hora_salida FROM logon_session; Triggers que capturan eventos CREATE, ALTER o DROP El siguiente ejemplo muestra la habilidad de los triggers para contruir una traza sobre los objetos de la base de datos que son eliminados, modificados o creados. Primero crearemos la tabla que guardará la información de auditoría de los objetos de la base de datos sobre los que ocurre alguno de estos eventos -- tabla que guarda las modificaciones sobre -- objetos de la base de datos CREATE TABLE mod_objetos (fecha_mod DATE, tipo_de_mod VARCHAR2(20), usuario VARCHAR2(30), num_instancia NUMBER, nombre_basedatos VARCHAR2(50), propietario_obj VARCHAR2(30), tipo_objeto VARCHAR2(20), nombre_objeto VARCHAR2(30)); Ahora creamos los triggers para los eventos CREATE, DROP o ALTER. Estos se crean para todos los esquemas pero si la auditoría la quisieramos hacer sobre un esquema en particular, entonces la claúsula “ON DATABASE” se cambiaría por “NOMBRE_ESQUEMA.SCHEMA”, donde NOMBRE_ESQUEMA es el nombre del esquema del que se desea hacer la auditoría. CREATE OR REPLACE TRIGGER create_objeto AFTER CREATE ON DATABASE BEGIN INSERT INTO mod_objetos (fecha_mod, tipo_de_mod, usuario, num_instancia,nombre_basedatos, propietario_obj, tipo_objeto, nombre_objeto) VALUES (sysdate, ora_sysevent, ora_login_user, ora_instance_num, ora_database_name, ora_dict_obj_owner, ora_dict_obj_type, ora_dict_obj_name); END; CREATE OR REPLACE TRIGGER alter_objeto AFTER ALTER ON DATABASE BEGIN INSERT INTO mod_objetos (fecha_mod, tipo_de_mod, usuario, num_instancia, nombre_basedatos, propietario_obj, tipo_objeto, nombre_objeto) VALUES

Page 68: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

67

(sysdate, ora_sysevent, ora_login_user, ora_instance_num, ora_database_name, ora_dict_obj_owner, ora_dict_obj_type, ora_dict_obj_name); END; CREATE OR REPLACE TRIGGER drop_objeto AFTER DROP ON DATABASE BEGIN INSERT INTO mod_objetos (fecha_mod, tipo_de_mod, usuario, num_instancia, nombre_basedatos, propietario_obj, tipo_objeto, nombre_objeto) VALUES (sysdate, ora_sysevent, ora_login_user, ora_instance_num, ora_database_name, ora_dict_obj_owner, ora_dict_obj_type, ora_dict_obj_name); END; Para probar estos triggers podemos hacer logoff en la base de datos y crear una tabla cuyo propietario sea SCOTT, despues hacemos nos conectamos de nuevo con SYS y realizamos la siguiente SELECT: COLUMN tipo_de_mod FORMAT a10 COLUMN usuario FORMAT a12 COLUMN nombre_basedatos FORMAT a10 COLUMN propietario_obj FORMAT a12 COLUMN tipo_objeto FORMAT a10 COLUMN nombre_objeto FORMAT a25 SET LINESIZE 130 SELECT fecha_mod, tipo_de_mod, usuario, num_instancia, nombre_basedatos, propietario_obj, tipo_objeto, nombre_objeto FROM mod_objetos ORDER BY fecha_mod, tipo_de_mod, propietario_obj, tipo_objeto, nombre_objeto; Los triggers individuales CREATE, ALTER y DROP que se crearon antes pueden ser reemplazados por un solo trigger utilizando la claúsula OR como se muestra a continuación: CREATE OR REPLACE TRIGGER todos_eventos_trigger AFTER CREATE OR ALTER OR DROP ON DATABASE BEGIN INSERT INTO mod_objetos (fecha_mod, tipo_de_mod, usuario, num_instancia, nombre_basedatos, propietario_obj, tipo_objeto, nombre_objeto) VALUES (sysdate, ora_sysevent, ora_login_user, ora_instance_num, ora_database_name, ora_dict_obj_owner, ora_dict_obj_type, ora_dict_obj_name); END; Prohibiendo que un usuario borre objetos: El siguiente ejemplo ilustra como un trigger puede ser creado para un esquema específico para deshabilitar la opción de borrar objetos de la base de datos. CREATE OR REPLACE TRIGGER disable_dropping BEFORE DROP ON SCOTT.SCHEMA

Page 69: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

68

BEGIN RAISE_APPLICATION_ERROR ( num => -20000, msg => 'No se le permite borrar objetos'); END; Cuando el usuario SCOTT intente borrar cualquier objeto de la base de datos se lanza un error y no se le permite la operación: SQL> drop table bonus; drop table bonus * ERROR en línea 1: ORA-00604: error producido a nivel 1 de SQL recursivo ORA-20000: No se le permite borrar objetos ORA-06512: en línea 2 Inhabilitando loggins dinámicamente El siguiente trigger impide que se puedan hacer más logins en la base de datos mediante el uso de una bandera en una tabla. Se inserta un registro de SI. Este trigger referencia a dicha tabla y comprueba que el valor sea SI, si es así, se permite hacer loggin en la base de datos, si no es así, nadie excepto el esquema SYS, SYSTEM o el usuario que creó el trigger, prodrán logarse en la base de datos. La tabla es la siguiente: CREATE TABLE mas_loggins (SINO VARCHAR2(2) NOT NULL); Insertremos un valor de NO para comprobar la eficacia del trigger: INSERT INTO mas_loggins (SINO) VALUES ('NO'); Y creamos el trigger: CREATE OR REPLACE TRIGGER permitir_mas_loggins AFTER LOGON ON DATABASE DECLARE CURSOR cursor_1 IS SELECT SINO FROM sys.mas_loggins; VALOR sys.mas_loggins.SINO%TYPE; BEGIN OPEN cursor_1; FETCH cursor_1 INTO VALOR; IF VALOR = 'NO' THEN BEGIN RAISE_APPLICATION_ERROR ( num => -20000, msg => 'No se permiten más loggins'); END; ELSE CLOSE cursor_1; END IF; END; Si el trigger fue creado con un error y esta en un estado INVALIDO, los tres usuarios (SYS, SYSTEM y el que creó el trigger) y cualquier esquema con el privilegio ADMINISTER DATABASE TRIGGER podrán hacer login en la base de datos. Sin embargo, si el trigger fue creado con un error y es INVALIDO y cualquiera intenta logarse, recibirán el siguiente error:

Page 70: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

69

ERROR: ORA-04098: el disparador 'SYS.PERMITIR_MAS_LOGGINS' no es válido y ha fallado al revalidar Triggers que actuan sobre una tabla determinada El siguiente ejemplo muestra como invocar a un trigger cuando se hace un borrado sobre una tabla. Para este ejmplo utilizaremos la tabla EMP. Si la fila se borra satisfactoriamente, el usuario recibe un mensaje con el número de filas que quedan en la tabla despues de borrar: CREATE OR REPLACE TRIGGER contador_emp AFTER DELETE ON SCOTT.EMP FOR EACH ROW DECLARE n INTEGER; BEGIN SELECT COUNT(*) INTO n FROM SCOTT.EMP; DBMS_OUTPUT.PUT_LINE(' Ahora hay '|| n ||' empleados.'); END; Este trigger se creó desde el esquema SYS, pero puede actuar sobre una tabla de otro esquema anteponiendo el nombre del propietario del objeto al nombre de la tabla. Triggers que actuan sobre cada fila de una tabla Como hemos visto, un trigger puede actuar sobre una única tabla del esquema actual o sobre una tabla de cualquier otro esquema siempre y cuando el usuario que lo crea tenga privilegios suficientes para hacerlo. Pero podríamos querer implementar un trigger que actuase para cada una de las filas de una tabla determinada. Supongamos que deseamos llevar un control de reservas que los usuarios hacen de cara a las vacaciones. Disponemos de una tabla que guarda las reservas que se realizan a ciertos destinos y otra tabla (una tabla catálogo) que guarda todos los destinos a donde un usuario puede ir. Es posible que el usuario desee ir a un destino que no este almacenado en la tabla Destinos y que el agente de viajes haga la reserva para dicho destino. Deseamos hacer un trigger que compruebe, cada vez que se hace una reserva, si el destino existe en la tabla, de manera que si no existe, este se inserte en la tabla Destinos y quede almacenado como un posible lugar más a donde poder ir de vacaciones. Lo primero que haremos será crear la tabla destinos y la tabla reserva. CREATE SEQUENCE codigo_dest_SEQ INCREMENT BY 2 START WITH 1000 MINVALUE 1000 MAXVALUE 1998 NOCYCLE NOORDER CACHE 20; -- Tabla destinos DROP TABLE Destinos; CREATE TABLE Destinos ( codigo_dest NUMBER(5) CONSTRAINT codigodest_pk PRIMARY KEY, nombre_dest VARCHAR2(25) NOT NULL ); CREATE SEQUENCE codigo_res_SEQ INCREMENT BY 2 START WITH 100000 MINVALUE 100000 MAXVALUE 10000000

Page 71: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

70

NOCYCLE NOORDER CACHE 20; CREATE TABLE Reservas ( codigo_res NUMBER(10) CONSTRAINT pk_codigo_res PRIMARY KEY NOT NULL, nombre_usr VARCHAR2(25) NOT NULL, nombre_dest VARCHAR2(25) NOT NULL, fecha_ida DATE NOT NULL, fecha_vuelta DATE NOT NULL ); Ahora rellenamos la tabla Destinos: truncate table Destinos; begin insert into Destinos (codigo_dest, nombre_dest) values (codigo_dest_SEQ.NEXTVAL, 'Paris'); insert into Destinos (codigo_dest, nombre_dest) values (codigo_dest_SEQ.NEXTVAL, 'Roma'); insert into Destinos (codigo_dest, nombre_dest) values (codigo_dest_SEQ.NEXTVAL, 'Niza'); insert into Destinos (codigo_dest, nombre_dest) values (codigo_dest_SEQ.NEXTVAL, 'Barcelona'); insert into Destinos (codigo_dest, nombre_dest) values (codigo_dest_SEQ.NEXTVAL, 'Madrid'); insert into Destinos (codigo_dest, nombre_dest) values (codigo_dest_SEQ.NEXTVAL, 'Dublin'); commit; end; Una vez que tenemos ambas tablas, solo nos queda construir el trigger que realizará dicha comprobación: CREATE OR REPLACE TRIGGER "SCOTT"."CHECKDESTINO" AFTER INSERT ON "SCOTT"."RESERVAS" REFERENCING OLD AS VIEJO NEW AS NUEVO FOR EACH ROW DECLARE nombre varchar(20):=''; BEGIN Select nombre_dest into nombre from Destinos where nombre_dest = :nuevo.nombre_dest; EXCEPTION WHEN NO_DATA_FOUND THEN INSERT INTO Destinos (codigo_dest, nombre_dest) VALUES (codigo_dest_SEQ.NEXTVAL,:nuevo.nombre_dest); END; Lo primero que observamos en este trigger es la palabra reservada REFERENCING, que se utiliza para indicarle a Oracle que ‘NEW’ y ‘OLD’ serán referenciados mediante un alias (en el ejemplo, en vez de utilizar NEW utilizamos NUEVO y en vez de utilizar OLD, utilizaremos VIEJO). Pero la pregunta es, ¿qué son NEW y OLD?. Pues bien, si tengo un trigger que se dispara cada vez que se hace un insert sobre una tabla y necesito utilizar, dentro del trigger, los valores que se insertaron al lanzar la sentencia INSERT, puedo hacerlo utilizando las palabras reservadas NEW y OLD. En donde NEW siempre hace referencia a los valores nuevos que se insertan en una sentencia INSERT, mientras que OLD hace referencia a los que había ‘antes’ de lanzar una sentencia UPDATE, es decir, los viejos valores que todavía no han sido actualizados. En el ejemplo, se realiza una select para comprobar si existe el destino para el cual se hizo la reserva, si la select no arroja ningún dato (el destino no existe), el trigger realiza una inserción del nuevo destino en la tabla Destinos, para poder obtener el valor del nombre del destino que se insertó al hacer la reserva, utilizo la palabra reservada NEW que he referenciado como NUEVO y la antepongo al nombre de la columna de la cual quiero recuperar el valor, en este caso, nombre_dest, que fue el nombre del destino que se insertó en la tabla Reservas: :nuevo.nombre_dest

Page 72: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

71

Otra cosa que cabe destacar es el uso de las palabaras reservadas FOR EACH ROW. Si incluimos esta sentencia en nuestro trigger, le estaremos diciendo a Oracle que ejecute el trigger una vez por cada fila afectada por la orden que provocó el disparo. También podríamos haber incluido una condición de ejecución del trigger, por ejemplo, ‘PARA CADA FILA CUANDO NOMBRE_COLUMNA!=’’’, como se muestra en las siguiente lineas de código: ........ FOR EACH ROW WHEN (NUEVO.nombre_dest!='') DECLARE ......... Ahora solo nos falta comprobar si nuestro trigger funciona correctamente. Haremos una inserción en la tabla reservas, de manera que reservemos para un destino que no se encuentre en la tabla, luego haremos la select correspondiente para comprobar que nuestro destino ha sido insertado en la tabla Destinos. INSERT INTO Reservas (codigo_res, nombre_usr, nombre_dest, fecha_ida, fecha_vuelta) VALUES (codigo_res_SEQ.NEXTVAL, 'Ferrnando Gómez', 'Limerick', sysdate, '03-SEP-2006'); Select * from Destinos Select * from Reservas Modificación de Vistas complejas mediante triggers (triggers ‘INSTEAD OF’) Los triggers denominados INSTEAD OF, nos permiten, de una manera transparente, modificar vistas que no pueden ser modificadas directamente mediante sentencias UPDATE, INSERT o DELETE. Estos triggers se denominan INSTEAD OF (en vez de), porque Oracle lanza el trigger en vez de ejecutar la propia sentencia INSERT o UPDATE o DELETE. El trigger debe de determinar que operación se pretende realizar y ejecutar las operaciones de UPDATE, INSERT o DELETE directamente sobre las tablas señaladas. Con un trigger INSTEAD OF, se pueden escribir sentencias UPDATE, INSERT y DELETE contra la vista, y el trigger trabaja de manera invisible en background para llevar a cabo acciones correctas sobre esta vista. Este tipo de trigger solo puede ser activado para cada fila de la tabla y se han de valorar los siguientes puntos de cara a su creación. - La opción INSTEAD OF solo puede ser usada para triggers que se crean sobre vistas - Las opciones BEFORE/AFTER no pueden ser utilizadas en triggers creados sobre vistas. - La opción CHECK para las vistas no se utiliza cuando se hacen inserciones o actualizaciones sobre la

vista utilizando un trigger INSTEAD OF. El cuerpo del trigger será quien se encargue de realizar el CHECK.

Tipos de vistas que requieren triggers INSTEAD OF Una vista no puede ser modificada mediante el uso de UPDATE, INSERT o DELETE si la query de la vista contiene alguna de las siguientes claúsulas: - OPERADORES SET - FUNCIONES DE GRUPO - GROUP BY, CONNECT BY o START WITH - El operador DISTINCT Si una vista contiene pseudo-columnas o expresiones, entonces solo se puede actualizar la vista con una sentencia UPDATE que no haga referencia a dichas pseudo-columnas o expresiones.

Page 73: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

72

Veamos un ejemplo de un trigger INSTEAD OF para insertar filas en una vista. Para ellos crearemos primero la vista sobre la tabla SCOTT.EMP que sencillamente nos muestre el nombre y número de empleado de todas las filas de la tabla:

CREATE OR REPLACE VIEW VISTA_NOMBRES_EMPNO AS SELECT EMPNO,ENAME FROM SCOTT.EMP;

Ahora realizamos la select sobre la vista para comprobar su funcionamiento:

Select * from VISTA_NOMBRES_EMPNO;

Y realizamos una inserción sobre nuestra vista INSERT INTO VISTA_NOMBRES_EMPNO (empno, ename) VALUES (7999,'Esuebio');

Y comprobamos que la inserción se ha realizado correctamente

Select * from scott.emp; En Oracle, actualizar o insertar sobre una vista no es una tarea muy adecuada, pero podemos utilizar un trigger que implemente esta funcionalidad :

CREATE OR REPLACE TRIGGER EMPLEADOS_VISTA_INSERT INSTEAD OF INSERT ON VISTA_NOMBRES_EMPNO FOR EACH ROW BEGIN DBMS_OUTPUT.PUT_LINE('Insertando: ' || :NEW.ENAME); -- O implementar el insert a mano nosotros mismos -- INSERT INTO SCOTT.EMP(EMPNO,ENAME) VALUES (7125,:NEW.ENAME); END;

Ahora hacemos un insert sobre nuestra vista: INSERT INTO VISTA_NOMBRES_EMPNO (empno, ename) VALUES (8000,'Esuebio'); ¿Qué ocurre?, ¿se inserta realmente el valor?. No, aunque Oracle nos dice que sí, pero lo que realmente hacemos es lanzar el trigger cuando alguien intenta insertar un valor dentro de una vista. Si nos fijamos en la linea de código comentada dentro del cuerpo del trigger podríamos haber simulado nosotros mismos la inserción haciendo la insert correspondiente a mano dentro del trigger. Veamos un ejemplo más complejo de un trigger INSTEAD OF para insertar filas en una vista. Para ellos crearemos primero la estructura de tablas que necesitaremos para este ejemplo y luego crearemos la vista sobre la que haremos las modificaciones: CREATE TABLE tabla_proyecto ( nivel_proyeto NUMBER, Proyectono NUMBER, dept_resp NUMBER); CREATE TABLE Empleados ( Empno NUMBER NOT NULL,

Page 74: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

73

Ename VARCHAR2(10), Job VARCHAR2(9), Mgr NUMBER(4), Hiredate DATE, Sal NUMBER(7,2), Comm NUMBER(7,2), Deptno NUMBER(2) NOT NULL); CREATE TABLE Departamentos ( Deptno NUMBER(2) NOT NULL, Dname VARCHAR2(14), Loc VARCHAR2(13), Mgr_no NUMBER, Tipo_dept NUMBER); Ahora construiremos la vista sobre la que haremos las modificaciones. CREATE OR REPLACE VIEW informacion_proy_empleados AS SELECT e.ename, e.empno, d.Tipo_dept, d.deptno, p.Nivel_proyeto, p.proyectono FROM Empleados e, Departamentos d, Tabla_Proyecto p WHERE e.empno = d.mgr_no AND d.deptno = p.dept_resp; Y por último creamos el trigger que realizará la gestión de información sobre la vista anterior: CREATE OR REPLACE TRIGGER insert_info INSTEAD OF INSERT ON informacion_proy_empleados REFERENCING NEW AS n FOR EACH ROW DECLARE filas number; BEGIN SELECT COUNT(*) INTO filas FROM Empleados WHERE empno = :n.empno; IF filas = 0 THEN INSERT INTO Empleados (empno,ename) VALUES (:n.empno, :n.ename); ELSE UPDATE Empleados SET Empleados.ename = :n.ename WHERE Empleados.empno = :n.empno; END IF; SELECT COUNT(*) INTO filas FROM Departamentos WHERE deptno = :n.deptno; IF filas = 0 THEN INSERT INTO Departamentos (deptno, Tipo_dept) VALUES(:n.deptno, :n.Tipo_dept); ELSE UPDATE Departamentos SET Departamentos.Tipo_dept = :n.Tipo_dept WHERE Departamentos.deptno = :n.deptno; END IF; SELECT COUNT(*) INTO filas FROM Tabla_proyecto WHERE Tabla_proyecto.projno = :n.projno; IF filas = 0 THEN INSERT INTO Tabla_proyecto (proyectono, nivel_proyeto) VALUES(:n.proyectono, :n.nivel_proyeto); ELSE UPDATE Tabla_proyecto SET Tabla_proyecto.nivel_proyeto = :n.nivel_proyeto WHERE Tabla_poyecto.proyectono = :n.proyectono;

Page 75: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

74

END IF; END; Llamando a procedimientos desde un trigger Como se dijo anteriormente, la capacidad máxima que puede ocupar el cuerpo de un trigger es de 32K. Este límite debe de ser suficiente para implementar el código de cualquier trigger, pero es posible que econtremos alguna situación en la que este espacio no sea suficiente, por ello, en Oracle es posible realizar llamadas a procedimientos o funciones almacenadas desde el cuerpo de un trigger. En el ejemplo siguiente crearemos una tabla que con tres columnas numéricas, nuestro procedimiento calcula la suma de las cantidades de las dos primeras columnas y almacena el resultado en un parametro de salida. Nuestro trigger llama al proceimiento, recupera el valor de la suma e inserta dicho valor en la tercera columna. Creamos la tabla:

Create table mi_tabla( columna1 number(3), columna2 number(3), columna3 number(5));

Y ahora creamos el procedimiento que realizará la suma:

CREATE OR REPLACE PROCEDURE mi_procedure (variable1 IN mi_tabla.columna1%type, variable2 IN mi_tabla.columna2%type, v_out OUT number) AS BEGIN v_out := variable1 + variable2 ; END ;

Por último, implementamos el trigger que llamará al procedimiento

CREATE OR REPLACE TRIGGER mi_trigger_proc BEFORE INSERT ON mi_tabla FOR EACH ROW DECLARE var_out number ; BEGIN mi_procedure(:new.columna1, :new.columna2, var_out) ; :new.columna3 := var_out ; END;

Para probarlo realizaremos la siguiente inserción:

INSERT INTO mi_tabla (columna1, columna2) VALUES (10, 20);

Esta INSERT provocará que el trigger se dispare y almacene la suma de ambas cantidades dentro de la tercera columna, si realizamos una SELECT podremos comprobar que dicha cantidad queda almacenada en la tercera columna de la tabla. Select * from mi_tabla; COLUMNA1 COLUMNA2 COLUMNA3

Page 76: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

75

---------- ---------- ---------- 10 20 30 1 filas seleccionadas. Capturando INSERTING, UPDATING y DELETING dentro de un trigger Dentro del bloque de PL/SQL de un trigger, podemos utilizar sentencias IF para determinar que tipo de sentencia SQL (INSERT, UPDATE o DELETE) causó que el trigger se disparase. Para determinar que sentencia disparó el trigger podemos capturar los tres eventos siguientes que se muestran en la tabla. Predicado TRUE/FALSE INSERTING TRUE si la orden de disparo es INSERT; FALSE en otro caso. UPDATING TRUE si la orden de disparo es UPDATE; FALSE en otro caso. DELETING TRUE si la orden de disparo es DELETE; FALSE en otro caso. Este bloque de código tiene una estructura general que se ajusta al siguiente patrón

IF INSERTING THEN ................ IF UPDATING THEN ................ IF DELETING THEN ................ END IF;

En el ejemplo siguiente implementamos un trigger que se limita a capturar el tipo de acción que realizamos sobre la tabla EMP y devuelve un mensaje al usuario dándole confirmación de dicha operación.

CREATE OR REPLACE TRIGGER ABM_EMPLEADOS BEFORE INSERT OR UPDATE OR DELETE ON SCOTT.EMP FOR EACH ROW BEGIN IF INSERTING THEN DBMS_OUTPUT.PUT_LINE('Insertado empleado: ' || :NEW.ENAME); END IF; IF UPDATING THEN DBMS_OUTPUT.PUT_LINE('Actualizando empleado: ' || :OLD.ENAME || ' por ' || :NEW.ENAME); END IF; IF DELETING THEN DBMS_OUTPUT.PUT_LINE('Borrando empleado: ' || :OLD.ENAME); END IF; END;

- Cuando introducimos una fila en la tabla empleados INSERT into SCOTT.EMP (empno, ename) VALUES (1234, 'María'); Insertado empleado: María 1 fila creada. - Cuando actualizamos una fila en la tabla empleados UPDATE SCOTT.EMP SET ename = 'María Jose' where empno = 1234;

Page 77: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

76

Actualizando empleado: María por María Jose 1 fila actualizada. - Cuando borramos una fila de la tabla empleados DELETE FROM SCOTT.EMP where empno=1234; Borrando empleado: María Jose 1 fila suprimida. Trigger que se ejecutan al realizar alguna acción sobre las columnas de una tabla Hemos vistro como realizar un trigger cuando se hacen modificaciones sobre una tabla, pero es posible que deseemos realizar un trigger que además actue sobre un campo concreto de esa tabla, es decir, ‘deseo hacer un trigger sobre la tabla A que se dispare al hacer un UPDATE sobre uno de sus campos’. En PL/SQL podemos hacer que nuestro trigger solo se dispare si se actualiza o borra o inserta un campo concreto de un tabla. La sintaxis para este tipo de triggers es la siguiente:

CREATE [OR REPLACE] TRIGGER trigger_name BEFORE (or AFTER) INSERT OR UPDATE [OF COLUMNS] OR DELETE ON tablename [FOR EACH ROW [WHEN (condition)]] BEGIN ... END;

El siguiente trigger se define sobre la tabla DEPT para reforzar la actualización o borrado de las filas de dicha tabla de manera que la clave ajena que hace referencia a la tabla EMP quede actualizada o borrada en los registros correspondientes a la tabla DEPT estableciendo su valor a NULL:

CREATE OR REPLACE TRIGGER Dept_set_null AFTER DELETE OR UPDATE OF Deptno ON Dept FOR EACH ROW -- Antes de que una fila sea borrada de la tabla DEPT o la clave -- primaria (DEPTNO) de la tabla DEPT sea actualizada, -- estableceremos todas la claves ajenas dependientes de esta -- última a NULL en la tabla EMP BEGIN IF UPDATING AND :OLD.Deptno != :NEW.Deptno OR DELETING THEN UPDATE Emp SET Emp.Deptno = NULL WHERE Emp.Deptno = :old.Deptno; END IF; END;

Para probar el ejemplo, bastaría con eliminar de la tabla DEPT, todos los empleados que trabajan, por ejemplo, en el departamento 20:

DELETE FROM DEPT where deptno=20;

Vistas del diccionario de datos para triggers de la base de datos

Page 78: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

77

Hay varias vistas del diccionario de datos que arrojan información sobre los triggers almacenados. Las dos vistas principales se centran en la vista del código fuente para paquetes, procedimientos y funciones, pero también almacenan el estado de compilación de los triggers. Esto puede ser importante para determinar si un trigger se compiló con exito o no. El código fuente para el trigger y la información acerca del estado ENABLED o DISBALED se almacena también en las vistas de los triggers. Hay tres vistas para cada area de información dentro del ámbito de los triggers: user_triggers user_source all_triggers all_source dba_triggers dba_source Realizaremos algunos ejemplos que nos mostrarán el uso de estas vistas. El primero de ellos nos devolverá información a cerca del estado de compilación del trigger. Un estado de VALIDO nos indica que el trigger se ha compilado correctamente y que está lista para su ejecución. Un estado de INVALIDO significa que el trigger necesita ser compilado antes de que pueda ser ejecutado: COLUMN object_name FORMAT a24 SELECT object_name, object_type, status FROM user_objects WHERE object_type = 'TRIGGER'; Como es evidente por la salida obtenida, todos los triggers pueden tener un estado VALIDO. Si un objeto que es referenciado por uno de estos triggers es eliminado, esto causará que el trigger adopte un estado de INVALIDO. El segundo ejemplo devuelve información a cerca de todos los triggers existentes en el sistema. Esto es útil para entender la base de datos y los triggers definidos en ella. COLUMN trigger_name FORMAT a24 COLUMN triggering_event FORMAT a15 SELECT trigger_name, trigger_type, base_object_type, triggering_event, status FROM user_triggers WHERE db_object_type IN ('DATABASE ', 'SCHEMA'); La salida identifica a los triggers por su tipo, por los eventos que causan su ejecución, por la hora de ejecución y el estado. Puede ser interesante solicitar a la base de datos el contenido (o código fuente de alguno de los triggers contenidos en ella). La siguiente consulta, muestra los atributos del trigger así como el contenido completo del trigger cuando fue creado: COLUMN trigger_name FORMAT a24 COLUMN triggering_event FORMAT a15 COLUMN trigger_body FORMAT a100 SELECT trigger_name, trigger_type, base_object_type, triggering_event, trigger_body FROM user_triggers WHERE trigger_name = 'PERMITIR_MAS_LOGGINS'; Objetos de PL/SQL En esta introducción, no hablaré de la programación orientada a objetos, ni tampoco de las bases de datos orientadas objetos, sencillamente, hablaremos de que son los objetos de la base de datos, de su funcionalidad y utilidad en un entorno Oracle.

Page 79: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

78

Diremos que un tipo de objeto es algo similar a un registro de PL/SQL, pero difiere con este en que un objeto se almacena en la base de datos y puede ser utilizado tanto desde SQL como desde PL/SQL sin necesidad de ser redefinido en PL/SQL. Asi como un registro define una tupla de variables con sus tipos, un objeto, además, define dentro de si, las funciones y procedimientos que determinarán su funcionalidad. Un obeto se compone los siguientes elementos: Tipo Objeto: Es un tipo de Oracle no primitivo, que se maneja desde SQL y PL/SQL y define una estructura de datos (atributos) y las operaciones (metodos) que se pueden realizar sobre esos atributos. Objecto: Es una instancia de un tipo de objeto. El objeto es el lugar físico en memoria donde se almacenan los datos. Los objetos pueden ser almacenados en tablas (denominados objetos persistentes), o pueden existir de forma temporal en varibales de PL/SQL (denaominados transitorios). Atributo: Es una parte estructural de un objeto (una variable) que define su tipo de dato. Los atributos deben de declararse mediante el uso de tipos de datos simples como por ejemplo VARCHAR o INTEGER Method Se denomina método, a los procedimientos o funciónes que opera sobre los atributos del objeto. Los métodos de un objeto, solo pueden ser invocados sobre el propio contexto del objeto. Hay un método especial que se crea por defecto cuando implementamos un objeto y se denomina constructor que se utiliza para inicializar objetos. Consideremos el siguiente ejemplo:

CREATE TYPE empleado AS OBJECT ( dni NUMBER(4), nombre VARCHAR2(10), f_nac DATE, );

Este tipo de objeto que acabamos de crear puede ser utilizado para crear una tabla basandose en su estructura de la siguiente manera.

CREATE TABLE Empleados OF empleado; La sentencia de arriba crea una tabla llamada empleados que tiene exactamente la misma estructura que el objeto empleado. Podríamos hacer una insert en la tabla a través de nuestro objeto:

insert into empleados values(empleado('4443','Pedro','03-JUN- 1974'));

o como siempre:

insert into empleados values('222','Maria','03-JUN-1980'); Una vez que un objeto ha sido creado, solo falta acceder a la información que contiene, veamos el siguiente código de PL/SQL:

SET SERVEROUTPUT ON; declare

Page 80: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

79

un_empleado empleado; emp_nombre varchar2(20) := '&nombre'; begin select value(e) into un_empleado from empleados e where e.nombre = emp_nombre; dbms_output.put_line('DNI: ' || un_empleado.dni); dbms_output.put_line('Fecha de nacimiento: ' || un_empleado.f_nac); end;

En el ejemplo de arriba, se declara una variable ‘un_empleado’ basándose en el tipo de objeto empleado (la instancia del objeto). La parte más importante a destacar es la carga del objeto que realiza la select. La tabla empleados tiene un alias ‘e’ y estamos utilizando la función value (en combinación con el alias) para devolver valores al objeto, el objeto devuelto por la función value se coloca, de forma ordenada, en el objeto ‘un_empleado’. De esta forma, ahora nuestro objeto contiene los datos resultantes de la select. Otra cosa que cabe destacar, es el uso del punto, recordemos que un usuario de base de datos es propietario de sus objetos y que para llamarlos desde otro usuario con suficientes privilegios, siempre anteponemos el nombre del propietario al nombre del objeto mediante un punto. Pues bien, un objeto es “propietario” de sus atributos y funciones, con lo que para acceder a ellos, siempre hemos de anteponer el nombre del objeto al nombre del método o atributo mediante un punto. En el ejemplo ‘un_empleado’ accede a sus miembros dni y f_nac anteponiendo su nombre, es decir, un_empleado.dni o un_empleado.f_nac. Hasta ahora, en los ejemplos anteriores, solo hemos utilizado los objetos para recoger una única fila de una select, pero, ¿y si deseasemos utilizarlos para recuperar más filas?, vamos a modificar un poco el código anterior : declare type tabla_empleados is table of empleado; mi_tabla tabla_empleados; i integer; begin select value(e) bulk collect into mi_tabla from empleados e; i := mi_tabla.first; while i is not null loop dbms_output.put_line(mi_tabla(i).nombre); i := mi_tabla.next(i); end loop; end; Hay que entender que existen varias maneras de recuperar la información de una tabla basada en objeto. Como hemos visto en el ejemplo anterior, creamos una tabla de objetos y realizamos la select correspondiente que recuperará la información de la tabla. Mediante la cláusula ‘bulk collect’ le decimos a Oracle que, de forma ordenada, vaya introduciendo los registros de la select dentro de cada uno de los objetos que tengo dentro de mi tabla. Esto significa que cada fila de la tabla es considerada un objeto (no valores individuales dentro de columnas) aunque pueda parecer que los valores se almacenen directamente como columnas. Pero, ¿y por qué no almacenar objetos dentro de columnas de una tabla de la base de datos?. Fijémonos en el siguiente código: CREATE or REPLACE TYPE direccion AS OBJECT ( calle varchar2(20),

Page 81: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

80

ciudad varchar2(20), cp varchar2(20) ); El script de arriba, crea un objeto llamado direccion cuyos campos son, calle, ciudad y código postal. Ahora modificaremos nuestra tabla empleados (antes borraremos la antigua) : Drop table empleados; create table empleados ( nombre varchar2(20), direccion_ofi direccion, direccion_casa direccion ); Lo que hemos hecho en este script ha sido generar una tabla con tres campos, uno es el nombre, de tipo VARCHAR y los otros dos son direccion_ofi y direccion_casa del mismo tipo de dato que el objeto que hemos creado. De esta manera cada una de las dos columnas puede tener su propia información agrupada sin que tengan ninguna relación entre ellas. Si deseamos insertar filas en esta tabla podemos hacerlo de la siguiente manera: insert into empleados values ( 'Jorge', direccion('una calle','Madrid','28000'), direccion('otra calle','Barcelona','97200') ); Para ver los valores que acabamos de insertar, podríamos hacer una simple select como ‘select * from empleados;’, pero lo vamos a hacer llamando a los campos de la tabla como en el ejemplo: Select e.nombre, e.direccion_ofi.calle, e.direccion_casa.ciudad from empleados e; Pero también podemos cargar un objeto del tipo direccion, de la misma manera que cargamos en cualquier otra variable un valor de una columna de una tabla utilizando la claúsula INTO: declare una_direccion direccion; un_nombre varchar2(20) := 'Jorge'; begin select direccion_ofi into una_direccion from empleados where nombre = un_nombre; dbms_output.put_line(''||una_direccion.calle); dbms_output.put_line(''||una_direccion.ciudad); dbms_output.put_line(''||una_direccion.cp); end; Objetos que declaran métodos y atributos En una sección anterior hemos nombrado los elementos de los que se compone un objeto, y hemos citado como miembros a los atributos y a los métodos, que pueden ser procedimientos y funciones. Por ejemplo, si queremos que el propio objeto nos facilite su información (los datos que contiene almacenados en los atributos) podríamos implementar una función o un procedimiento que nos facilitase dicha información y llamarlo. Cuando creamos un paquete es necesario implementar la parte de la

Page 82: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

81

definición (parte pública) y la parte del cuerpo del paquete (parte privada), con los objetos nos ocurre lo mismo, es necesario hacer la declaración del objeto y luego la parte del cuerpo, para poder hacer llamadas a los procedimientos y funciones que contiene. Consideremos el siguiente ejemplo en el que creamos un objeto persona que implementa dos funciones y un procedimiento: CREATE OR REPLACE TYPE empleado AS OBJECT ( num_emp INTEGER, -- numero de empleado nombre VARCHAR2(60), -- nombre del empleado tipo_empleado VARCHAR2(1), -- tipo_empleado : A, B o C sexo VARCHAR2(1), -- H/M MEMBER FUNCTION set_num_emp (num_empleado IN INTEGER) RETURN empleado, MEMBER PROCEDURE mostrar_todo); Ahora crearemos el cuerpo del objeto: CREATE OR REPLACE TYPE BODY empleado AS MEMBER FUNCTION set_num_emp (num_empleado IN INTEGER) RETURN empleado IS mi_empleado empleado := SELF; -- inicializa el empleado actual BEGIN mi_empleado.num_emp := num_empleado; RETURN mi_empleado; END; MEMBER PROCEDURE mostrar_todo IS alias_directorio VARCHAR2(60); nombre_fichero VARCHAR2(60); BEGIN DBMS_OUTPUT.PUT_LINE('Numero empleado : ' || num_emp); DBMS_OUTPUT.PUT_LINE('Nombre : ' || nombre); DBMS_OUTPUT.PUT_LINE('Tipo empleado : ' || tipo_empleado); DBMS_OUTPUT.PUT_LINE('Sexo : ' || sexo); END; END; Para crear un objeto basado en un tipo, es decir, instanciarlo, se puede utilizar el constructor por defecto que tiene todo objeto. Este constructor no es más que una función que implementa Oracle automáticamente cuando este es creado. El constructor tiene el mismo nombre que el tipo de objeto y acepta cada valor de los atributos como argumentos o parámetros en el mismo orden en el que los atributos fueron declarados en la definición del tipo de objeto. De esta forma ‘empleado’ es un tipo de objeto y el nombre de su correspondiente constructor.

SET SERVEROUTPUT ON; DECLARE un_empleado empleado; BEGIN -- Instanciamos un empleado utilizando el constructor por defecto un_empleado := empleado(104, 'Pedro', 'A', 'H'); -- Invocamos a un método para cambiar el numero de empleado un_empleado := un_empleado.set_num_emp(105); -- Imprimimos el contenido del empleado un_empleado.mostrar_todo();

Page 83: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

82

END;

Es importante destacar la sintaxis utilizada para realizar llamadas a los métodos del objeto. El uso de ‘objeto.metodo’ es la estandar de llamar a miembros de un objeto en lenguajes OO. Fijémonos en las siguientes llamadas:

Un_empleado.set_num_emp(112);

En este caso la llamada es correcta ya que un_empleado es una variable del tipo empleado, (es una instancia). Empleado.set_num_emp(104) ; En este caso, la llamada es inválida, pues ¿a que empleado le estamos cambiando el número? Para crear una estructura que mantenga objetos persistentes de un tipo dado, se puede crear una tabla en la base de datos que tenga la misma estructura que dicho tipo de objetos:

CREATE TABLE empleados OF empleado (PRIMARY KEY (num_emp));

Esta sentencia creará una tabla con las columnas necesarias para albergar los valores de los atributos del objeto. Cuando creamos una tabla de esta manera, Oracle, además de los campos de atributo, crea una columna más para guardar el identificador de cada objeto insertado. Como viene implícito en la propia claúsula ‘PRIMARY KEY’, Oracle crea también una clave primary sobre el campo num_emp y por consiguiente un índice único adicional. Como se vió anteriormente, para insertar un objeto en esta tabla podemos utilizar la siguiente sintaxis: INSERT INTO empleados VALUES(empleado(104,'Blas','A','M')); podríamos haber creado el empleado y despues haberlo insertado, por ejemplo, si deseamos que los valores almacenados sean introducidos por teclado, primero tendremos que crear el objeto empleado, despues almacenar en sus atributos los valores recogidos desde teclado y despues lanzar la correspondiente insert pasándole el objeto creado. SET SERVEROUTPUT ON; DECLARE un_empleado empleado; BEGIN -- Instanciamos un empleado utilizando el constructor por defecto un_empleado := empleado(105, 'Pedro', 'A', 'H'); -- Lo insertamos INSERT INTO empleados VALUES(un_empleado); -- Lo actualizamos un_empleado.nombre := 'PEPE'; UPDATE empleados SET nombre=un_empleado.nombre WHERE num_emp = un_empleado.num_emp; -- Comprobamos su contenido un_empleado.mostrar_todo(); -- Por ultimo lo eliminamos DELETE FROM empleados WHERE num_emp = un_empleado.num_emp; COMMIT; END;

Page 84: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

83

Práctica: UN HOTEL. Objetos: Persona : Atributos : nombre, dni, num_habitacion. Funciones : get_nombre, get_dni, get_num_habitacion Procedmientos : set_nombre, set_dni, set_num_habitacion (actualizar_persona, borrar_persona, alta_persona) Habitacion: Atributos : num_habitacion, libre(s/n), doble_simple(d/s). Funciones : get_num_habitacion, is_libre, get_tipo_habitacion. Procedimientos : set_num_habitacion, set_libre, set_tipo_habitacion (actualizar_hab, borrar_hab, alta_hab). Hotel: Atributos : --- Funciones : --- Procedmientos : alta_cliente, baja_cliente, actualizacion_cliente(solo cambio de habitación), listado_clientes, listado_cliente, mostrar_menu, salir, mostrar_menu. Tablas: Para hacer la correspondencia con los objetos creados, fabricaremos las tablas en base a estos objetos. No se establecerán referencias, pero hay que recordar que cada cliente debe de tener una clave única como identificador y que cada habitación tendrá su propia clave (puede ser el propio número de habitación). Desde un bloque de código de PL/SQL instanciaremos un objeto del tipo HOTEL, que mostrará un menú con las siguientes opciones (haremos la llamada al procedimiento menu):

- Nuevo cliente - Baja cliente - Alta cliente - Listar cliente - Listar todos - SALIR Selección : _

Cada método del objeto Hotel debe de tener la siguiente funcionalidad: - alta_cliente : almacena un nuevo cliente en la base de datos - baja_cliente : da de baja un cliente (la tabla habitaciones debe de ser actualizada también). - actualizacion_cliente : Cambia de habitación a un cliente (solo cambio de habitación). - listado_clientes, listado_cliente, mostrar_menu, salir, mostrar_menu. Primero crearemos los objetos de los cuales obtendré las tablas CREATE OR REPLACE TYPE persona AS OBJECT ( nombre VARCHAR2(60), -- nombre dni VARCHAR2(8), -- dni num_habitacion NUMBER(2), -- num_habitacion MEMBER FUNCTION get_nombre RETURN VARCHAR2, MEMBER FUNCTION get_dni RETURN VARCHAR2, MEMBER FUNCTION get_num_habitacion RETURN NUMBER,

Page 85: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

84

MEMBER PROCEDURE set_nombre(nom VARCHAR2), MEMBER PROCEDURE set_dni(dn VARCHAR2), MEMBER PROCEDURE set_num_habitacion(num_hab NUMBER) ); CREATE OR REPLACE TYPE habitacion AS OBJECT ( num_habitacion NUMBER(2), -- num_habitacion libre CHAR(1), -- libre doble_simple CHAR(1), -- doble_simple MEMBER FUNCTION get_num_habitacion RETURN NUMBER, MEMBER FUNCTION is_libre RETURN CHAR, MEMBER FUNCTION get_tipo_habitacion RETURN CHAR, MEMBER PROCEDURE set_num_habitacion(num_hab NUMBER), MEMBER PROCEDURE set_libre(lib CHAR), MEMBER PROCEDURE set_tipo_habitacion(ds CHAR), MEMBER PROCEDURE actualizar_hab(num_hab NUMBER, libre CHAR, ds CHAR) ); CREATE OR REPLACE TYPE HOTEL AS OBJECT ( num_habitaciones NUMBER(2), -- num_habitaciones MEMBER FUNCTION get_num_habitaciones RETURN NUMBER, MEMBER PROCEDURE mostrar_menu, MEMBER PROCEDURE set_num_habitaciones(num_hab NUMBER), MEMBER PROCEDURE alta_cliente(cliente persona), MEMBER PROCEDURE baja_cliente(cliente persona), MEMBER PROCEDURE actualizacion_cliente(cliente persona, num_hab NUMBER), MEMBER PROCEDURE listado_clientes, MEMBER PROCEDURE listado_cliente(cliente persona), MEMBER PROCEDURE salir ); Despues creamos las tablas CREATE TABLE CLIENTES OF PERSONA (PRIMARY KEY (dni)); CREATE TABLE HABITACIONES OF habitacion (PRIMARY KEY (num_habitacion)); Ahora creo los cuerpos de los objetos CREATE OR REPLACE TYPE BODY persona AS MEMBER FUNCTION get_nombre RETURN VARCHAR2 IS -- empleado := SELF; -- inicializa el empleado actual BEGIN

Page 86: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

85

RETURN SELF.nombre; END; MEMBER FUNCTION get_dni RETURN VARCHAR2 IS BEGIN RETURN SELF.dni; END; MEMBER FUNCTION get_num_habitacion RETURN NUMBER IS BEGIN RETURN SELF.num_habitacion; END; MEMBER PROCEDURE set_nombre(nom VARCHAR2) IS BEGIN SELF.nombre := nom; END; MEMBER PROCEDURE set_dni(dn VARCHAR2) IS BEGIN SELF.dni := dn; END; MEMBER PROCEDURE set_num_habitacion(num_hab NUMBER) IS BEGIN SELF.num_habitacion := num_hab; END; END; CREATE OR REPLACE TYPE BODY habitacion AS MEMBER FUNCTION get_num_habitacion RETURN NUMBER IS -- empleado := SELF; -- inicializa el empleado actual BEGIN RETURN SELF.num_habitacion; END; MEMBER FUNCTION is_libre RETURN CHAR IS BEGIN RETURN SELF.libre; END; MEMBER FUNCTION get_tipo_habitacion

Page 87: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

86

RETURN CHAR IS BEGIN RETURN SELF.doble_simple; END; MEMBER PROCEDURE set_num_habitacion(num_hab NUMBER) IS BEGIN SELF.num_habitacion := num_hab; END; MEMBER PROCEDURE set_libre(lib CHAR) IS BEGIN SELF.libre := lib; END; MEMBER PROCEDURE set_tipo_habitacion(ds CHAR) IS BEGIN SELF.doble_simple:= ds; END; MEMBER PROCEDURE actualizar_hab(num_hab NUMBER, libre CHAR, ds CHAR) IS BEGIN SELF.num_habitacion := num_hab; SELF.libre := libre; SELF.doble_simple:= ds; UPDATE habitaciones SET num_habitacion = SELF.num_habitacion, libre = SELF.libre WHERE num_habitacion = SELF.num_habitacion; END; END; CREATE OR REPLACE TYPE BODY HOTEL AS MEMBER FUNCTION get_num_habitaciones RETURN NUMBER IS BEGIN RETURN SELF.num_habitaciones; END; MEMBER PROCEDURE mostrar_menu IS BEGIN dbms_output.put_line('1. NUEVO CLIENTE'); dbms_output.put_line('2. BAJA CLIENTE'); dbms_output.put_line('3. ALTA CLIENTE'); dbms_output.put_line('4. LISTAR CLIENTE'); dbms_output.put_line('5. LISTAR TODOS'); dbms_output.put_line('6. SALIR'); dbms_output.put_line(''); dbms_output.put_line('OPCIÓN :');

Page 88: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

87

END; MEMBER PROCEDURE set_num_habitaciones(num_hab NUMBER) IS BEGIN SELF.num_habitaciones := num_hab; END; MEMBER PROCEDURE alta_cliente(cliente persona) IS BEGIN INSERT INTO clientes (nombre,dni,num_habitacion) VALUES (persona.nombre, persona.dni, persona.num_habitacion); UPDATE habitaciones SET libre = 'N' WHERE num_habitacion = persona.num_habitacion; COMMIT; END; MEMBER PROCEDURE baja_cliente(cliente persona) IS BEGIN DELETE FROM clientes WHERE dni = persona.dni; UPDATE habitaciones SET libre = 'S' WHERE num_habitacion = persona.num_habitacion; COMMIT; END; MEMBER PROCEDURE actualizacion_cliente(cliente persona, num_hab NUMBER) IS BEGIN UPDATE clientes SET num_habitacion = num_hab WHERE dni = persona.dni; UPDATE habitaciones SET libre = 'S' WHERE num_habitacion = cliente.num_habitacion; UPDATE habitaciones SET libre = 'N' WHERE num_habitacion = num_hab; END; MEMBER PROCEDURE listado_clientes IS BEGIN EXECUTE IMMEDIATE 'Select * from clientes'; END; MEMBER PROCEDURE listado_cliente(cliente persona) IS BEGIN EXECUTE IMMEDIATE 'select * from clientes where dni =:1'USING persona.dni; END; MEMBER PROCEDURE salir IS BEGIN dbms_output.put_line('SALIENDO...'); END; END; / SHOW ERRORS;

Page 89: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

88

--Rellenamos la tabla de habitaciones declare i number:=1; begin FOR i IN 1..30 LOOP INSERT INTO habitaciones (num_habitacion,libre,doble_simple) VALUES (i,'S','D'); END LOOP; FOR i IN 31..61 LOOP INSERT INTO habitaciones (num_habitacion,libre,doble_simple) VALUES (i,'S','S'); END LOOP; commit; end; --Comprobamos si se rellenó la tabla Select * from habitaciones order by num_habitacion; SET SERVEROUTPUT ON; DECLARE num_habitaciones NUMBER(2):=0; opcion NUMBER:=0; mi_hotel hotel; cliente persona; hab habitacion; salir BOOLEAN:=FALSE; BEGIN select count(*) into num_habitaciones from habitaciones; mi_hotel := hotel(num_habitaciones); dbms_output.put_line(num_habitaciones); WHILE salir = FALSE LOOP mi_hotel.mostrar_menu(); opcion := &opcion; CASE WHEN opcion = 1 THEN BEGIN dar_alta_cliente(&nombre,&dni,&ds,mi_hotel); END; WHEN opcion = 2 THEN BEGIN dbms_output.put_line(''); dbms_output.put_line(''); END; WHEN opcion = 3 THEN BEGIN dbms_output.put_line(''); dbms_output.put_line(''); END; WHEN opcion = 4 THEN BEGIN dbms_output.put_line(''); dbms_output.put_line(''); END; WHEN opcion = 5 THEN

Page 90: Tutorial PL SQL - Dic 2012

Introducción a PL/SQL Héctor Tévar

89

BEGIN dbms_output.put_line(''); dbms_output.put_line(''); END; WHEN opcion = 6 THEN BEGIN dbms_output.put_line(''); dbms_output.put_line(''); END; ELSE DBMS_OUTPUT.PUT_LINE('Opción no válida'); END CASE; END LOOP; END; CREATE OR REPLACE PROCEDURE dar_alta_cliente (un_nombre VARCHAR2,un_dni VARCHAR,ds VARCHAR, mi_hotel OUT hotel) IS num_hab NUMBER(2):=0; cliente persona; BEGIN Select num_habitacion into num_hab from habitaciones where doble_simple = ds and libre='S' and ROWNUM < 2; cliente := persona(un_nombre,un_dni,num_hab); mi_hotel.alta_cliente(cliente); EXCEPTION WHEN NO_DATA_FOUND THEN BEGIN dbms_output.put_line('No hay habitaciones disponibles'); cliente:=NULL; END; END;