Tutorial PL SQL - Dic 2012
Tutorial PL SQL - Dic 2012
PL/SQL
Columna1
Columna2
Columna N
...........................
.........................
.........................
..............................
Una tabla es nicamente identificada por su nombre y consiste en filas que contienen la informacin 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, tambin llamada esquema de relacin, es definida por sus atributos. El tipo de informacin que se almacena en una tabla es definido por los tipos de datos de los atributos durante la creacin de la misma. SQL usa los trminos de tabla, respectivamente). fila, y columna para establecer relaciones (tupla, y atributo,
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 alfanumricos (cadenas), nmeros y formatos de fecha. Oracle ofrece los siguientes tipos de datos bsicos: 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 mxima 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 ningn otro carcter 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 (precisin, escala) Ejemplo: X=number (7,2) X=155'862 Error ya que solo puede tomar 2 decimales
X= 155'86 Bien Nota: El rango mximo va de 1 a 38. LONG: DATE: Almacena la fecha. Se almacena de la siguiente forma: No almacena nmeros de gran tamao, sino cadenas de caracteres de hasta 2 GB
Sentencias de consultas de datos Select: Select [ALL | Distinct] [expresin_columna1, expresin_columna2, ., | *] From [nombre1, nombre_tabla1, , nombre_tablan] {[Where condicin] [Order By expresin_columna [Desc | Asc]]};
Vamos a explicar como leer la consulta anterior y as seguir la pauta para todas las dems. Cuando ponemos ignifica que debemos la que va dentro debe existir, y si adems 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 segn 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 estn 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: Ntese que cada consulta de SQL que hagamos hemos de terminarla con un punto y coma ";".
SELECT EMPNO, ENAME, JOB, DEPTNO FROM EMP WHERE DEPTNO = 10 ORDER BY EMPNO;
Este ejemplo mostrar el nmero 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 nmero de empleado. Notemos tambin 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 nmero 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 aritmticos: + * / Suma Resta Multiplicacin Divisin
Operadores de comparacin y lgicos: != >= <= = Like % _ Not And Or Distinto Mayor o igual que Menor o igual que Igual que Se utiliza para unir cadenas de caracteres Representa cualquier cadena de caracteres de 0 o ms caracteres Representa un nico carcter cualquiera Negacin Y, a and b, Cierto si son ciertas a y b O, a or b, Cierto si a o b o ambas son ciertas
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 posicin: 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%'; Comprobacin con conjuntos de valores: In= permite saber si una expresin pertenece o no a un conjunto de valores. Between= permite saber si una expresin 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 dlares 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');
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, expresin)= Sustituye un valor nulo por otro valor. POWER (m, exponente)= Calcula la potencia de un numero. ROUND (numero [, m])= Redondea nmeros con el numero de dgitos de precisin indicados. SIGN (valor)= Indica el signo del "valor". SQRT(n)= Devuelve la raz cuadrada de "n". TRUNC (numero, [m])= Trunca nmeros para que tengan una cierta cantidad de dgitos de precisin. 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 (* | Expresin)= Cuenta el numero de veces que la expresin evala algn dato con valor no nulo. La
SUM (expresin)= Obtiene la suma de los valores de la expresin. 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 carcter cuyo valor en binario es equivalente a "n". CONCAT (cad1, cad2)= Devuelve "cad1" concatenada con "cad2". LOWER (cad)= Devuelve la cadena "cad" en minsculas. UPPER (cad)= Devuelve la cadena "cad" en maysculas. INITCAP (cad)= Convierte la cadena "cad" a tipo titulo. LPAD (cad1, n[,cad2])= Aade caracteres a la izquierda de la cadena hasta que tiene una cierta longitud. RPAD (cad1, n[,cad2])= Aade 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 carcter o caracteres de una cadena con 0 o mas caracteres.
SUBSTR (cad, m [,n])= Obtiene parte de una cadena. TRANSLATE (cad1, cad2, cad3)= Convierte caracteres de una cadena en caracteres diferentes, segn un plan de sustitucin marcado por el usuario. Funciones que devuelven valores numricos: ASCII(cad)= Devuelve el valor ASCII de la primera letra de la cadena "cad". INSTR (cad1, cad2 [, comienzo [,m]])= Permite una bsqueda de un conjunto de caracteres en una cadena pero no suprime ningn carcter despus. 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 da 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 da de la semana indicado por "cad" despus de la fecha indicada por "fecha". Funciones de conversin: 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.
Agrupacin de elementos. Group by y Having: La sentencia "Select" posibilita agrupar uno o ms conjuntos de filas. El agrupamiento se lleva a cabo mediante la clusula "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 funcin de grupo (SUM, COUNT, AVG) Una columna expresada en el Group By.
La clusula Group By sirve para calcular propiedades de uno o ms conjuntos de filas. Si se selecciona ms de un conjunto de filas, Group By controla que las filas de la tabla original sean agrupadas en un temporal. La clusula Having se emplea para controlar cual de los conjuntos de filas se visualiza. Se evala 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 caracterstica 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 mnimo salario y El mximo de la tabla empleados teniendo en cuenta que solo buscar aquellos trabajos que sean de tipo CLERK y en donde haya ms de uno.
Combinacin 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 smbolo +. 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 SELECTFROMWHERE 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 aparecern 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 sern eliminadas.
MINUS= Devuelve aquellas filas que estn en la primera "Select" y no estn en la segunda "Select". Las filas duplicadas del primer conjunto se reducirn a una fila nica antes de que empiece la comparacin con el otro conjunto. select _ from EMP minus select _ from EMP2;
Reglas para la utilizacin 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 aaden 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 clusula Insert se consideran, por defecto, todas las columnas de la tabla. Las columnas a las que damos valores se identifican por su nombre. La asociacin 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 carcter han de ir encerrados entre comillas simples (' ') (los de tipo fecha tambin).
Con Select: Se aaden 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:
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 parntesis 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'; Definicin 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 perdern si al salir del programa no realizamos esta accin. Puede programarse para que lo haga automticamente. Algunas ordenes que lleven COMMIT implcito: QUIT EXIT CONNECT DISCONNECT CREATE TABLE
CREATE VIEW GRANT REVOQUE DROP TABLE DROP VIEW ALTER AUDIT NO AUDIT
Creacin de una tabla: Su primer carcter debe ser alfabtico y el resto pueden ser letras, nmeros y el carcter 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; Caractersticas: Las definiciones individuales de columnas se separan mediante comas. No se pone coma despus de la ltima definicin de columna. Las maysculas y minsculas 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 restriccin de integridad ser: Una regla que restringe el rango de valores para una o ms columnas en la tabla. Restricciones en create table: Usamos la clusula 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 definicin de columnas. Al final, una vez especificados todas las columnas. Formato: CREATE TABLE NOMBRE_TABLA (COLUMNA1 TIPO_DE_DATO {CONSTRAINT NOMBRE_RESTRICCION} {NOT NULL}
{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 unvocamente a cada fila. Debe ser nica, no nula y obligatoria. Como mximo, podemos definir una clave primaria por tabla. Esta clave se puede referenciar por una columna o columnas. Cuando se crea una clave primaria, automticamente se crea un ndice que facilita el acceso a la tabla. Formato de restriccin 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 restriccin de tabla: CREATE TABLE NOMBER_TABLA (COL1 TIPO_DATO, COL2 TIPO_DATO,
Claves ajenas: Foreign Key: Esta formada por una o varias columnas que estn 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 restriccin de columna: CREATE TABLE NOMBRE_TABLA (COLUMNA1 TIPO_DATO [CONSTRAINT NOMBRERESTRICCION] REFERENCES NOMBRETABLA [(COLUMNA)] [ON DELETE CASCADE] )[TABLESPACE ESPACIO_DE_TABLA];
Formato de restriccin 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) );
Notas: En la clusula REFERENCES indicamos la tabla a la cual remite la clave ajena. Hay que crear primero una tabla y despus aquella que le hace referencia. Hay que borrar primero la tabla que hace referencia a otra tabla y despus 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 automticamente. La restriccin 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 clusula INSERT. En la especificacin DEFAULT es posible incluir varias expresiones: constantes, funciones SQL y variables UID y SYSDATE. Verificacin de restricciones: CHECK: Acta como una clusula where. Puede hacer referencia a una o ms columnas, pero no a valores de otras filas. En una clusula CHECK no se pueden incluir subconsultas ni las pseudoconsultas SYSDATE, UID y USER. Nota: La restriccin 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 restriccin 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 restriccin UNIQUE se crea un ndice automticamente. Vistas del diccionario de datos para las restricciones: Contienen informacin 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. Creacin 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 accin con la clusula AS colocada al final de la orden CREATE TABLE. CREATE TABLE NOMBRETABLA (COLUMNA [,COLUMNA] )[TABLESPACE ESPACIO_DE_TABLA] AS CONSULTA;
CREATE TABLE COPIA_EMP AS SELECT * FROM EMP WHERE ENAME LIKE 'SCOTT'; No es necesario especificar tipos ni tamao de las consultas, ya que vienen determinadas por los tipos y los tamaos de las recuperadas en la consulta. La consulta puede tener una subconsulta, una combinacin 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. Creacin 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 prximas veces que se rebase ester espacio, se incremente en un 50% sobre El tamao que tena. Este segmento es una parte del tablespace SYSTEM
Supresin de tablas: DROP TABLE: suprime una tabla de la base de datos. Cada usuario puede borrar sus propias tablas, pero solo el administrador o algn usuario con el privilegio "DROP ANY TABLE" puede borrar las tablas de otro usuario. Al suprimir una tabla tambin se suprimen los ndices y los privilegios asociados a ella. Las vistas y los sinnimos creados a partir de esta tabla dejan de funcionar pero siguen existiendo en la base de datos por tanto deberamos 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 definicin 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. Modificacin de tablas: Se modifican las tablas de dos formas: Cambiando la definicin de una columna (MODIFY) aadiendo
una columna a una tabla existente (ADD): Formato: ALTER TABLE NOMBRETABLA {[ADD (COLUMNA [,COLUMNA])] [MODIFY (COLUMNA [,COLUMNA])] [ADD CONSTRAINT RESTRICCION] [DROP CONSTRAINT RESTRICCION]}; ADD= Aade 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= Aade una restriccin a la definicin de la tabla. ALTER TABLE EMP5 ADD CONSTRAINT ck_comm2 CHECK (SAL<=1000); DROP CONSTRAINT= Elimina una restriccin de la tabla. ALTER TABLE EMP5 DROP CONSTRAINT ck_comm2; A la hora de aadir una columna a una tabla hay que tener en cuenta: Si la columna no esta definida como NOT NULL se le puede aadir en cualquier momento. Si la columna esta definida como NOT NULL se pueden seguir estos pasos: 1. Se aade 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 opcin MODIFY NOT NULL solo ser posible cuando la tabla no contenga ninguna fila con valor nulo en la columna que se modifica.
Creacin y uso de vistas No contienen informacin por si mismas, sino que estn basadas en las que contienen otras tablas y refleja los datos de estas. Si se suprime una tabla la vista asociada se invalida. Formato:
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 aparecern en la vista. [OR REPLACE]= Crea de nuevo la vista si ya exista. 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: Actualizacin si una vista esta basada en una sola tabla, se pueden modificar las filas de la vista. La modificacin de la vista cambia la tabla sobre la que esta definida. Borrado de filas a travs de una vista= Para borrar filas de una tabla a travs de una vista, esta se debe crear: Con filas de una sola tabla. Sin utilizar la clusula GROUP BY ni DISTINCT. Sin usar funciones de grupo o referencias a pseudocolumnas. Actualizacin de filas a travs de una vista: Para actualizar filas en una tabla a travs de una vista, esta ha de estar definida segn las restricciones anteriores y , adems, ninguna de las columnas que se va a actualizar se habr definido como una expresin. Insercin de filas a travs de una vista: Para insertar filas en una tabla a travs de una vista se han de tener en cuenta todas las restricciones anteriores y, adems, 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. Tambin podemos modificar filas siempre y cuando la columna que se va a modificar no sea la columna expresad en forma de clculo o con funciones. Nota: No es posible insertar filas si las columnas de la vista contiene clculos o funciones. Cambios de nombre
RENAME cambia el nombre de una tabla, vista o sinnimo. 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 automticamente al nuevo objeto. RENAME NOMBRE_ANTERIOR TO NOMBRE_NUEVO; RENAME EMP5 TO MIS_EMPLEADOS;
Gestin de tablespaces Un tablespace es una unidad lgica de almacenamiento d datos representada fsicamente por uno o ms 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 TAMAO MINEXTENTS TAMAO MAXEXTENTS TAMAO PCTINCREASE VALOR )] [ONLINE | OFFLINE]; REUSE= Reutiliza el archivo si ya existe o lo crea si no existe. DEFAULT STORAGE= Define el almacenamiento por omisin 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
Modificacin 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= Aade al tablespace uno o varios archivos. AUTOEXTEND= Activa o desactiva el crecimiento automtico de los archivos de datos del tablespace. Cuando un tablespace se llena podemos usar esta opcin para que el tamao del archivo o archivos de datos asociados crezca automticamente. Autoextend off: desactiva el crecimiento automtico. RENAME_DATAFILE= Cambia el nombre de un archivo existente del tablespace. Este cambio se tiene que hacer desde el sistema operativo y, despus, 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 instruccin alter tablespace TSprueba1 coalesce;
Borrado de tablespaces DROP TABLESPACE NOMBRETABLESPACE [INCLUDING CONTENTS]; INCLUDING CONTENTS= Permite borrar un tablespace que tenga datos. Sin esta opcin solo se puede suprimir un tablespace vaco. Se recomienda poner el talespace offline entes de borrarlo para asegurarse de que no haya sentencias SQL que estn accediendo a datos del tablesapace, en cuyo caso no seria posible borrarlo.
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;
Gestin y eleccin de los ndices Los ndices son campos elegidos arbitrariamente por el constructor de la base de datos que permiten la bsqueda a partir de dicho campo a una velocidad notablemente superior. Sin embargo, esta ventaja se ve contrarrestada por el hecho de ocupar mucha ms memoria (el doble ms o menos) y de requerir para su insercin y actualizacin un tiempo de proceso superior. Evidentemente, no podemos indexar todos los campos de una tabla extensa ya que doblamos el tamao de la base de datos. Igualmente, tampoco sirve de mucho el indexar todos los campos en una tabla pequea ya que las selecciones pueden efectuarse rpidamente de todos modos.
Un caso en el que los ndices pueden resultar muy tiles es cuando realizamos peticiones simultneas sobre varias tablas. En este caso, el proceso de seleccin 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 ningn tipo de peticin ya que, adems del problema de memoria ya mencionado, estamos ralentizando otras tareas de la base de datos como son la edicin, insercin y borrado. Es por ello que vale la pena pensrselo dos veces antes de indexar un campo que no sirve de criterio para bsquedas 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 estn embebidas dentro del cdigo de la aplicacin. Es mucho ms 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 clusula WHERE aquellos campos que formen parte de la clave del fichero por el cual interrogamos. Adems se especificarn en el mismo orden en el que estn 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 sera ms 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 condicin APELLIDO Like '%' devolvera 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 clusula FROM. Si deseamos saber cuantos alumnos se matricularon en el ao 1996 y escribimos: FROM Alumnos, Matriculas WHERE Alumno.IdAlumno = Matriculas.IdAlumno AND Matriculas.Ao = 1996 el gestor recorrer todos los alumnos para buscar sus matriculas y devolver las correspondientes. Si escribimos FROM Matriculas, Alumnos WHERE Matriculas.Ao = 1996 AND Matriculas.IdAlumno = Alumnos.IdAlumnos, el gestor filtra las matrculas y despus selecciona los alumnos, de esta forma tiene que recorrer menos registros.
Consultas bsicas La sintaxis bsica de una consulta de seleccin es la siguiente: SELECT Campos FROM Tabla
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 telfono de la tabla clientes. Devolver Literales En determinadas ocasiones nos puede interesar incluir una columna con un texto fijo en una consulta de seleccin, 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 clusula 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 clusula (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 clusula y el primer nombre del campo a recuperar, los posibles predicados son: Predicado * ROWNUM DISTINCT * 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 instruccin SQL y devuelve todos y cada uno de sus campos. Descripcin Devuelve todos los campos de la tabla Devuelve un determinado nmero de registros de la tabla Omite los registros cuyos campos seleccionados coincidan totalmente
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 ms rpido indicar el listado de campos deseados. SELECT * FROM EMP
ROWNUM Devuelve un cierto nmero de registros que entran entre al principio o al final de un rango especificado por una clusula 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 instruccin 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 Lpez en el campo ENAME, la siguiente instruccin SQL devuelve un nico registro: Select DISTINCT ENAME FROM EMP; Con otras palabras el predicado DISTINCT devuelve aquellos registros cuyos campos indicados en la clusula 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 procederamos de la siguiente forma: AS no es una palabra reservada de ANSI, existen diferentes sistemas de asignar los alias en funcin del motor de bases de datos. En ORACLE para asignar un alias a un campo hay que hacerlo de la siguiente forma:
Tambin podemos asignar alias a las tablas dentro de la consulta de seleccin, 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 tcnica ser de gran utilidad ms 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 clusula FROM).
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 condicin referida a un campo de texto la condicin de bsqueda 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 segn el banco de datos: Banco de Datos SQL-SERVER ORACLE ACCESS Sintaxis Fecha = #mm-dd-aaaa# Fecha = to_date('YYYYDDMM','aaaammdd',) Fecha = #mm-dd-aaaa#
Referente a los valores lgicos 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 Lgicos Los operadores lgicos soportados por SQL son: AND, OR, XOR, Eqv, Imp, Is y Not. A excepcin de los dos ltimos todos poseen la siguiente sintaxis: <expresin1> operador <expresin2> En donde expresin1 y expresin2 son las condiciones a evaluar, el resultado de la operacin vara en funcin del operador lgico. La tabla adjunta muestra los diferentes posibles resultados: <expresin1> Verdad Verdad Falso Falso Verdad Verdad Falso Falso Verdad Verdad Falso Falso Verdad Verdad Falso Falso Verdad Verdad Operador AND AND AND AND OR OR OR OR XOR XOR XOR XOR Eqv Eqv Eqv Eqv Imp Imp <expresin2> Falso Verdad Verdad Falso Falso Verdad Verdad Falso Verdad Falso Verdad Falso Verdad Falso Verdad Falso Verdad Falso Resultado Falso Verdad Falso Falso Verdad Verdad Verdad Falso Falso Verdad Verdad Falso Verdad Falso Falso Verdad Verdad Falso
Si a cualquiera de las anteriores condiciones le anteponemos el operador NOT el resultado de la operacin 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 segn el intervalo de valores de un campo emplearemos el operador Between cuya sintaxis es: campo [Not] Between valor1 And valor2 (la condicin Not es opcional) En este caso la consulta devolvera los registros que contengan en "campo" un valor incluido en el intervalo valor1, valor2 (ambos inclusive). Si anteponemos la condicin 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 expresin de cadena con un modelo en una expresin SQL. Su sintaxis es: expresin Like modelo En donde expresin es una cadena modelo o campo contra el que se compara expresin. 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 Mara), o se puede utilizar una cadena de caracteres comodn como los reconocidos por el sistema operativo para encontrar un rango de valores (Like An%). El operador Like se puede utilizar en una expresin para comparar un valor de un campo con una expresin 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 parmetros, 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 dgitos:
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 cmo utilizar el operador Like para comprobar expresiones con diferentes modelos. ORACLE Ejemplo LIKE 'A%' LIKE '_NG' LIKE '[AF]%' LIKE '[A-F]%' LIKE '[A^B]%' Descripcin Todo lo que comience por A Todo lo que comience por cualquier carcter y luego siga NG Todo lo que comience por A F Todo lo que comience por cualquier letra comprendida entre la A y la F Todo lo que comience por A y la segunda letra no sea una B
En determinado motores de bases de datos, esta clusula, no reconoce el asterisco como carcter comodn y hay que sustituirlo por el carcter 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: expresin [Not] In(valor1, valor2, . . .) SELECT * FROM EMP2 WHERE JOB In ('CLERK', 'PRESIDENT') ;
La clusula WHERE La clusula WHERE puede usarse para determinar qu registros de las tablas enumeradas en la clusula FROM aparecern en los resultados de la instruccin SELECT. Despus de escribir esta clusula se deben especificar las condiciones expuestas en los apartados anteriores. Si no se emplea esta clusula, la consulta devolver todas las filas de la tabla. WHERE es opcional, pero cuando aparece debe ir a continuacin de FROM.
select ENAME, DNAME from EMP E, DEPT D where E.DEPTNO = D.DEPTNO; select E1.ENAME, E2.ENAME from EMP E1, EMP E2 where E1.MGR = E2.EMPNO;
select ENAME, E.DEPTNO, DNAME from EMP E, DEPT D where E.DEPTNO = D.DEPTNO and JOB = 'SALESMAN'; select ENAME, SAL from EMP where EMPNO in (select PMGR from PROJECT where PSTART < 31-DEC-90) and DEPTNO =20; select * from EMP E1 where DEPTNO in (select DEPTNO from EMP E where E.EMPNO = E1.MGR);
select * from EMP where DEPTNO in (select DEPTNO from DEPT where LOC = 'BOSTON');
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)
and DEPTNO = 10; select * from DEPT where not exists (select * from EMP where DEPTNO = DEPT.DEPTNO);
and DEPTNO <> 30; 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 algn registro. Supongamos que deseamos recuperar todos aquellos empleados que esten asociados a algn 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 seleccionarn los campos 1,2,..., n de la tabla origen y se grabarn en los campos 1,2,.., n de la Tabla. La condicin SELECT puede incluir la clusula 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 grabarn en Tabla, para realizar esta operacin es necesario que todos los campos de Tabla Origen estn 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 atencin con los campos contadores o autonumricos puesto que al insertar un valor en un campo de este tipo se escribe el valor que contenga su campo homlogo en la tabla origen, no incrementndose como le corresponde. Se puede utilizar la instruccin INSERT INTO para agregar un registro nico a una tabla, utilizando la sintaxis de la consulta de adicin de registro nico tal y como se mostr anteriormente. En este caso, su cdigo 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.
Tambin se puede utilizar INSERT INTO para agregar un conjunto de registros pertenecientes a otra tabla o consulta utilizando la clusula SELECT... FROM como se mostr anteriormente en la sintaxis de la consulta de adicin de mltiples registros. En este caso la clusula 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 agregarn 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 clusula IN para agregar registros a una tabla en otra base de datos. Se pueden averiguar los registros que se agregarn en la consulta ejecutando primero una consulta de seleccin que utilice el mismo criterio de seleccin y ver el resultado. Una consulta de adicin copia los registros de una o ms tablas en otra. Las tablas que contienen los registros que se van a agregar no se vern afectadas por la consulta de adicin. En lugar de agregar registros existentes en otra tabla, se puede especificar los valores de cada campo en un nuevo registro utilizando la clusula VALUES. Si se omite la lista de campos, la clusula 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 actualizacin que cambia los valores de los campos de una tabla especificada basndose en un criterio especfico. Su sintaxis es: UPDATE Tabla SET Campo1=Valor1, Campo2=Valor2, CampoN=ValorN WHERE Criterio UPDATE es especialmente til cuando se desea cambiar un gran nmero de registros o cuando stos se encuentran en mltiples tablas UPDATE EMP set JOB = 'MANAGER', DEPTNO = 20, SAL = SAL +1000 where ENAME = 'JONES'; Si en una consulta de actualizacin suprimimos la clusula WHERE todos los registros de la tabla sealada sern actualizados.
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:
Introduccin a PL/SQL
PL/SQL Es un lenguaje estructurado formado por bloques. Es decir, sus unidades bsicas (procedimientos, funciones y bloques annimos), hacen que PL/SQL se estructure en bloques lgicos, 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 cdigo 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 nmeros de cuatro dgitos y una variable llamada almacn para guardar valores TRUE o FALSE. La declaracin Es la siguiente: Mi_numero NUMBER(4); Almacen BOOLEAN;
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 nmero de la variable emp_id)
La tercera manera de asignar valores a las variables Es mediante El paso de parmetros OUT o IN OUT a un subprograma. En El ejemplo siguiente se muestra un parmetro IN OUT que permite pasar valores a un subprograma que devuelve otros valores actualizados en funcin 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 declaracin 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 informacin almacenada. Por ejemplo, para consultas que devuelven ms de una fila, se puede declarar un cursor para procesar las filas de manera individual. Un ejemplo de la declaracin de un cursor se muestra ms 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 implcitamente 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 ms que propiedades que permiten referenciar el tipo de dato y estructura de un item si necesidad de tener que repetir su definicin. Las tablas y columnas de una base de datos, tienen atributos similares, que se pueden utilizar para hacer un fcil mantenimiento de la base de datos.
Para utilizar los atributos, utilizamos el smbolo %. 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 ejecucin).
Mi_titulo libros.title%TYPE;
En PL/SQL los registros se utilizan para agrupar datos. Un registro esta compuesto por un nmero 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 extrada 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 podra utilizar el atributo %ROWTYPE, de manera que almacene la misma informacin 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 estn almacenados en cada registro del cursor
Estructuras de control Posiblemente sea una de las partes ms importantes de PL/SQL, no solo porque nos permiten manipular datos de Oracle sino tambin 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 funcin de ciertas circunstancias. La clusula IFTHEN-ELSE nos permite ejecutar una secuencia de manera condicional. Si la clusula IF define una condicin, la clusula THEN le dice al programa que hacer si se cumple dicha condicin o bien la clusula ELSE si no se cumple. En el siguiente ejemplo realizamos una transaccin 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
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 expresin CASE evala una condicin y realiza una accin (que puede ser un bloque entero de PL/SQL). Fijmonos 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 cdigo ir comparando ese valor con una lista de valores definidos en el propio cdigo, si alguno de ellos es igual, mandar un mensaje dicindonos en que nmero coincide. Otro ejemplo ms, crearemos un procedimiento que calcular el valor del rea de un circulo, de una rectngulo o de un cuadrado dependiendo de la opcin 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;
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 * WHEN forma = 'circulo' THEN BEGIN area := 3.14 * (radio * radio); END; WHEN forma = 'rectangulo' THEN area := lado ELSE DBMS_OUTPUT.PUT_LINE('No hay area para END CASE; DBMS_OUTPUT.PUT_LINE('El area del ' || forma END;
lado;
Registros de PL/SQL
Un registro de PL/SQL Es una varibale que contiene una coleccin 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 tamaos diferentes. Para declarar un registro basado en una coleccin 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:
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 ms de 4000'); ELSE dbms_output.put_line('El empleado '|| REC_EMP2.ENAME ||' No gana ms 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 declaracin 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 ms importantes de PL/SQL son las expresiones condicionales mediante las cuales se puede hacer que nuestro programa ejecute un bloque de cdigo u otro en funcin a una o unas condiciones que ocurran durante la ejecucin Del mismo. En El siguiente ejemplo se pregunta si El valor de una variable Es menor que 10, si Es as, envamos 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;
Podramos querer bifurcar la condicin y aadir un nuevo bloque de cdigo si la X fuera mayor, tengamos en cuenta que en nuestro ejemplo, solamente se ejecuta un bloque de cdigo 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
dbms_output.put_line('X es mayor que 10'); END IF; END; Tambin podramos preguntar por ciertos valores de la X, supongamos que queremos interponer una condicin si X Es menor que 10 o mayor o incluso igual a 10, o igual a cualquier otro nmero. Para ello utilizamos la clusula ELSIF, que Es equivalente a un ELSE IF. El siguiente cdigo 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 ondicin tal como haber alcanzado El nmero de vueltas impuesto o que se cumpla la condicin de terminacin Del bucle. Los bucles de PL/SQL no difieren mucho de aquellos que se podran encontrar en cualquier otro lenguaje procedural. A continuacin mostraremos los tipos de bucles que podemos utilizar en PL/SQL y sus diferencias. El bucle LOOP END LOOP El bucle ms bsico de los que podemos encontrar en PL/SQl Es aquel que consiste de una serie de sentencias contenidas entre las clusulas LOOP y END LOOP. Todo bucle necesita de una condicin para su finalizacin. La manera en la que podemos terminar las iteraciones de un bucle como este es a travs de la clasula 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 nos permite iterar sobre un conjunto de sentencias, basandose en una condiin que se evalua en cada iteracin. La condicin Es evaluada al principio Del bucle, si Es falsa, entonces no se realizar ninguna iteracin. Is la condicin 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 condicin definalizacin 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
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 implcito 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 continuacin.
El nmero de filas procesadas por la sentencia SQL.. TRUE si como mnimo una fila fue procesada. TRUE si ninguna fila fue procesada 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 explcitos.
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 nmero de filas que han sido borradas despus de la sentencia DELETE Cursores explcitos 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 OPEN FETCH CLOSE 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.. Ejecuta la query que rellena el cursor con filas. Carga la fila que esta direccionada por el puntero del cursor dentro de alguna variable y mueve dicho puntero a la siguiente fila. Vaca los datos contenidos en el cursor y lo cierra. El cursor puede ser reabierto y sus datos pueden ser actualizados.
Los cursores se declaran dentro de una seccin 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 clusula 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 clusula 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 ms filas, el valor del puntero se establecer a null. Las siguientes llamadas a FETCH me devolvern una excepcin. La sentencia CLOSE vaca 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;
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 parmetros. Lo parmetros nos permiten especificar la seleccin 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 insercin 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 funcin 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 ejecucin. Almacenar los bloques de cdigo en procedimientos almacenados o funciones es una buena manera de agrupar la funcionalidad de la aplicacin. Es posible invocar un procedimiento almacenado de Oracle desde la mayora 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
Mejora del rendimiento, pues evitamos el parseo de PL/SQL durante el tiempo de ejecucin para ser realizado en tiempo de compilacin. Modificacin de las rutinas online, sin necesidad de interferir con otros usuarios Modificacin de una rutina que afecta a mltiples 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 cdigo PL/SQL que pueden ser almacenados dentro de la base de datos, los procedimientos y las funciones.
Procedimientos almacenados
La sintaxis bsica de un procedimiento almacenado es la siguiente:
CREATE OR REPLACE PROCEDURE <nombre_proc> IS BEGIN <cdigo> END <nombre_proc>;
Procedmientos que reciben parmetros por valor La sintaxis bsica de un procedimiento que recibe un parmetro Es la siguiente
CREATE OR REPLACE PROCEDURE <nombre_proc> ( <nombre_parametro> IN <tipo_dato>) IS BEGIN <cdigo> 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...');
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 parmetros por valor y por referencia al mismo tiempo En PL/SQL podemos hacer que un parmetro de un procedimiento, sea pasado por valor y por referencia al mismo tiempo, su sintaxis bsica Es la siguiente:
CREATE OR REPLACE PROCEDURE <nombre_proc> ( <nombre_parametro> IN OUT <tipo_dato>) IS BEGIN <cdigo> END <nombre_proc>;
Ejemplo:
CREATE OR REPLACE PROCEDURE parametro_INOUT ( msg IN OUT VARCHAR2) IS BEGIN msg := msg || ' un parmetro 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 parmetros Con PL/SQl podemos crear procedimientos que reciban mltiples parmetros, sin preocuparnos de si estos deben de ser parmetros por valor o por referencia. PL/SQL permite la combinacin de mltiples tipos de parmetros en un solo procedimiento La sintaxis bsica para este tipo de procedimientos es la siguiente:
CREATE OR REPLACE PROCEDURE <nombre_proc> ( <nombre_parametro> IN <tipo_dato>,
<nombre_parametro> OUT <tipo_dato>, <nombre_parametro> IN OUT <tipo_dato>) IS BEGIN <cdigo> 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 ningn valor directamente (si lo har indirectamente). Una funcin tambin lee de una lista de valores pero adems esta retornar explcitamente 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 clasula REPLACE puede ser omitida si no se deseea que un funcin existente sea reemplazada con el mismo nombre. Las definiciones de las funciones varan de las definiciones de los procedimientos en que se debe explicitamente crear una variable que nos servir para devolver un valor de retorno a travs de la clasula RETURN. Funciones que no reciben parmetros La sintaxis bsica de una funcin que no recibe parmetros 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 'Funcin simple que \devuelve\ esta cadena';
END funcion_simple; Para ejecutar una funcin en PL/SQL podemos hacerlo seleccionando la funcin 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 funcin de la tabla DUAL causar que el resultado de la funcin se muestre por pantalla. En una apartado posterior hablaremos de la funcin DUAL. Select funcion_simple from dual; Funciones que no reciben parmetros 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 parmetros (uso en una sentencia INSERT) Para utilizar una funcin en una sentencia insert, lo nico que hay que hacer es asegurarse de que el tamao del valor de retorno de dicha funcin 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 funcin, 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 funcin: 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 funcin relleno las columnas: Select * from una_tabla;
Ahora realizaremos la accin inversa, utilizaremos una funcin para recuperar datos de una select, para ello modificaremos la funcin del apartado anterior , haciendo que devuelva el nombre de un empleado. La funcin 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 funcin: Select * from emp where ename = nombre_empleado;
Funciones que reciben parmetros: La sintaxis bsica de una funcin que reibe parmetros es la siguiente CREATE OR REPLACE FUNCTION <nombre_funcion> (<parametros>) RETURN <tipo_variable> IS <declaracin de variables> BEGIN <cdigo>; END <nombre_funcion>; El siguiente ejemplo muestra una funcin que recibe un parmetro, el nmero 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 funcin en una SELECT Select * from emp where sal = get_salario(7900); O en una sentencia INSERT
insert into una_tabla values ('empleado_5', get_salario(7499)); O incluso en una sentencia de actualizacin 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 usaramos en cualquier lenguaje procedural. A continuacin mostramos una serie de ejemplos que ayudarn a ilustrar las utilidades de las funciones de PL/SQL Ejemplo 1: La siguiente funcin 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 funcin determina si la cadena que se le pasa como parmetro es numrica 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 nmero'); ELSE dbms_output.put_line('La cadena no es un nmero'); END IF; END;
Error
Nombre de la Excepcin
ORA-06531 COLLECTION_IS_NULL ORA-06511 CURSOR_ALREADY_OPEN ORA-00001 DUP_VAL_ON_INDEX ORA-00001 INVALID_CURSOR ORA-01001 INVALID_NUMBER ORA-01017 LOGIN_DENIED ORA-01403 NO_DATA_FOUND ORA-01012 NOT_LOGGED_ON ORA-06501 PROGRAM_ERROR ORA-06504 ROWTYPE_MISMATCH
ORA-30625 SELF_IS_NULL
Se alcanza cuando... El programa intenta asignar valores a los atributos o a un objeto no inicializado (un objeto nulo). Ninguna de las elecciones en la clasula WHEN de un CASE se selecciona y no hay clasula ELSE. El programa intenta aplicar mtodos de coleccin en vez de EXISTS a una array o tabla no inicializado. El programa intenta abrir un cursor que ya ha sido abierto previamente. El programa intenta alamcenar valores duplicados en una columna de una tabla que tiene una restriccin UNIQUE. El programa intenta una operacin ilegal tal como cerrar un cursor que no ha sido abierto. En una sentencia SQL, la conversion de una cadena en un nmero falla debido a que la cadena no representa un nmero vlido. El programa intenta hacer logon en Oracle con un usuario o password invlidos. Cuando una sentencia SELECT INTO no devuelve ninguna fila, o el programa referencia a un elemento borrado. El programa intenta una llamada a una base de datos sin estar conectado a Oracle. El cdigo PL/SQL contiene un problema interno. El tipo de fila y el tipo de fila de variable de un cursor tienen tipos distintos. El programa intenta llamar a un mtodo miembro o a una instania nula, es decir, al parmetro SELF (que es siempre el primer parmetro pasado a un mtodo miembro) es nulo. No hay suficiente memoria para ejecutar el programa o la memoria est corrupta. El programa referencia a una tabla o array anidado utilizando un nmero de ndice ms grande que el nmero de elementos en la coleccin. El programa intenta referenciar a un array o una tabla utilizando un ndice numrico que no est en el rango de elementos de la tabla o array. La conversin de una cadena de carcteres a un rowid universal falla porque la cadena no representa un rowid
vlido. Un time-out ocurre mientras Oracle espera algn recurso. Una sentencia SELECT INTO devuelve ms de una fila. Un error aritmtico, de conversin o incluso un error de restriccin ocurre. El programa intenta dividir un nmero 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 ms de una fila para la consulta'); END; Declaracin 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 declaracin de excepciones propias, la sintaxis bsica para declarar una excepcin de usuario es la siguiente: DECLARE Nombre_excepcin 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 explcitamente'); WHEN OTHERS THEN dbms_output.put_line('Ha ocurrido algn error'); END; Una excepcin solo puede ser declarada una vez en un bloque, pero los bloques anidados, pueden declarar una excepcin con el mismo nombre que el que tendra otra en un bloque exterior. Cuando se crea una excepcin propia, se debe hacer una llamada a la clasula RAISE para lanzar explicitamente la excepcin. Todas las excepciones declaradas tienen un cdigo de error de 1 y un mensaje User-defined-exception, a no ser que utilicemos el pragma EXCEPTION_INIT, en donde nosotros definimos el nmero de error y
el texto del mensaje, la sintaxis bsica de la declaracin de una excepcin propia a travs del PRAGMA es la siguiente: DECLARE Nombre_excepcion EXCEPTION; PRAGMA EXCEPTION_INIT (nombre_excepcin, nmero_error); Donde nmero_error es un valor literal. Este nmero puede ser un nmero 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 devolucin de excepciones no manejadas. La sintaxis bsica de este mtodo es la siguiente: raise_application_error(nmero_error, mensaje_error[, {TRUE | FALSE}]); Donde el nmero de error es negativo y est en el rango 20000...-20999 y el mensaje es una cadena de carcteres de hasta 2048 bytes de largo. El tercer parmetro 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 aplicacin puede llamar a este mtodo (raise_application_error) solo desde un subprograma almacenado. Cuando es llamado, este finaliza el subprograma y devuelve un nmero de error de usuario y un mensaje a la aplicacin. El nmero 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 nmero de empleado se le pasa como parmetro a la funcin. 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 excepcin update emp set sal=null where emp.ename='SCOTT'; / CREATE Or REPLACE PROCEDURE actualizar_salario (emp_num NUMBER, cantidad NUMBER) AS salario NUMBER; BEGIN
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 ms exterior si no han sido gestionadas o re-lanzadas en la seccin EXCEPTION. Cuando ocurre una excepcin, PL/SQL busca un manejador de excepciones que chequee dicha excepcin en el bloque actual. Si no se encuentra ninguno, entonces PL/SQL propaga la excepcin al bloque contenedor (el bloque que contiene el subbloque en donde se intenta manejar dicha excepcin) o a la aplicacin que llam a dicho subbloque. La propagacin continua siempre, de ms adentro a ms afuera, hasta que la excepcin es manejada en el bloque ms exterior o programa. Si en el bloque o aplicacin ms exterior, la excepcin no es manejada, el programa se para y se realiza un ROLLBACK de las transacciones pendientes. Una vez que la excepcin ha sido manejada, no se seguir propagando hacia afuera, sencillamente, esta espera al manejador, para continuar con la ejecucin del bloque EXCEPTION. La clasula WHEN OTHERS Su sintaxis bsica es la siguiente, EXCEPTION WHEN OTHERS THEN ... Utilizaremos esta clasula dentro de la seccin EXCEPTION para capturar cualquier tipo de error que pueda ocurrir en el bloque en ejecucin. Si utilizamos esta clasula en la seccin EXCEPTION, deberemos de colocarla en ltimo lugar, pues este tipo de excepcin 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 cdigo de error y el mensaje de una excepcin. Para utilizar estas funciones, lo haremos dentro de la seccin EXCEPTION, concretamente cuando utilicemos WHEN OTHERS de manera que podamos manejar errores mediante su nmero 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));
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 nnero es demasiado pequeo'); ELSE DBMS_OUTPUT.PUT_LINE('Excepcion no capturada, '||' cdigo ='||SQLCODE); DBMS_OUTPUT.PUT_LINE(SQLERRM); END IF; END;
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 asignacin 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; Tambin podemos utilizar la clasula 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);
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 asignacin 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 prcticamente de la misma manera que en cualquier otro lenguaje procedural, excepto en su inicializacin y asignacin 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 (tamao_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 vacos o rellenos. A partir de entonces, cada vez que deseemos insertar nuevos elementos en el vector ser necesario utilizar la funcin EXTEND. Veamos el ejemplo: 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); -- declaracin 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; -- declaracin de variables v_varray1 tipo_varray; v_varray2 tipo_varray; --Realizamos la inicializacin en la misma declaracin 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; --Delcaracin 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 vaco v_varray2 := tipo_varray(); -- O preguntar si est vaco 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; --asignacin de vectores END IF; --Inicializacin 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;
La declaracin de tablas es prcticamente similar a la declaracin 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 instruccin DELETE , pudiendo quedar huecos intermedios vacos y sin poderse referenciar, aunque se pueden volver a llenar con una asignacin. La funcin 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 posicin vaca no es igual que borrarla v_table1.DELETE(3); -- As es como se borra una posicin v_table1.EXTEND; v_table1(4) := 'Mara'; -- v_table1(5) := 'Juan'; --ERROR v_table2 := t_table();--O podemos crearla vaca --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;
dbms_output.put_line(que_color); total := colores_oscuros.COUNT; dbms_output.put_line(total); END; Posiblemente pueda parecer raro que la linea de cdigo dbms_output.put_line(que_color);
-- red -- 3
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 alfabtico, de esta forma, PL/SQL ordena todas las claves y devuelve la ltima de la lista ordenada alfabticamente. 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. Podramos 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 cdigo 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 cdigo tambin 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:
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 podramos haber utilizado un LOOP normal y usar el mtodo NEXT, para iterar a travs 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 lgica, tipos de datos PL/SQL, objetos y subprogramas. Normalmente un paquete se compone de dos partes, una especificacin y cuerpo. La especificacin 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 -- especificacin (parte visible) -- tipos pblicos y declaracin de objetos -- especificacin de subprogramas END [nombre_paquete]; CREATE PACKAGE BODY nombre AS -- cuerpo (parte no visible) -- tipos privados y declaracin de objetos -- cuerpos de subprogramas [BEGIN -- sentencias] END [nombre];
La especificacin mantiene declaraciones pblicas, que son visibles para las aplicaciones. El cuerpo del paquete mantiene la implementacin de las declaraciones pblicas (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 pblica del mismo, es necesario declarar tambin la parte privada o cuerpo, que es en donde realizamos la implementacin de los mtodos 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 <definicin de varibales locales, constantes y excepciones> BEGIN <cdigo>; END <nombre_procedimiento>; END <nombre_paquete>; Ejemplo: CREATE OR REPLACE PACKAGE paquete_proc AS PROCEDURE get_nombreTabla(numero IN PLS_INTEGER);
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: Definicin 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 nmero de empleado y su nombre. El segundo procedimiento da de baja un empleado, recibe el nmero de empleado que se eliminar de la tabla, el tercer procedimiento modifica el salario, recibe un entero que correponde con el nmero 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
BEGIN DBMS_OUTPUT.PUT_LINE('Modificacin 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 bsica 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 pblico, 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 funcin o procedimiento est sobrecargado si existe otra funcin o mtodo que tiene el mismo nombre pero posee distintos tipos de parmetros. En el ejemplo de abajo, tenemos un paquete que declara una funcin 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 funcin que se llama de la misma manera, pero recibe, en vez de una cadena, un nmero. La funcionalidad es la misma, pero le doy la posibilidad al usuario de hacer la bsqueda del salario de un empleado, por nombre o por nmero. Veamos el ejemplo:
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:
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 cdigo de los triggers es almacenado en una tabla del diccionario de datos diferente de la que almacena los procedimientos. Si existen errores durante la creacin o compilacin 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 mximo nivel de simplicidad como sea posible y que sea creado nicamente si verdaderamente es necesario. Si un trigger necesita de muchas lineas de cdigo, es posible almacenar parte de este cdigo en un procedimiento y hacer la llamada a este procedimiento desde el mismo trigger. El mximo tamao 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 invlido oibjetos de la base de datos. En algunas ocasiones, un trigger deshabilitado es ms 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 lgica almacenada basada en cdigo 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 ningn mensaje de error). Para asegurarnos que un cdigo 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 creacin 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
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 versin 9i, se han separado en dos categoras. 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 lgica del trigger. Por ejemplo, la sentencia CREATE, puede referenciar al nombre del esquema, al tipo de objeto creado, el nombre del objeto, etc.
LOGON
AFTER
STARTUP
AFTER
ora_sysevent ora_login_user ora_instance_num ora_database_name Ejecutado cuando un usuario ora_sysevent entra en la base de datos, ora_login_user exactamente cuando hay un ora_instance_num login cuyo usuario y ora_database_name pasword son correctos. ora_client_ip_address Se ejecuta cuando una base ora_sysevent de datos es abierta; ora_login_user ora_instance_num ora_database_name Se ejecuta cuando se hace shutdown sobre una instancia. Se ejecuta cuando ocurre un error de Oracle (se puede chequear el nmero de un error para que solo se ejecute cuando ocurre uno en concreto) ora_sysevent ora_login_user ora_instance_num ora_database_name
SHUTDOWN
BEFORE
SERVERERROR
AFTER
SUSPEND
AFTER
ora_sysevent ora_login_user ora_instance_num ora_database_name ora_server_error ora_is_servererror space_error_info Se ejecuta cuando un error ora_sysevent del servidor hace que una ora_login_user transaccin en ejecucin se ora_instance_num suspenda (ej//out-of-space) 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.
Hay 14 eventos cliente o DDL que pueden ser creados a nivel de base de datos y que se ejecutarn para todos los esquemas. Estos eventos pueden tambin 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 espefico y se ejecuta solo para ese esquema. La siguiente tabla muestra los eventos DDL.
Database Trigger BEFORE/AFTER Description Attribute Events 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 modificacin de usuario) ora_is_alter_column, ora_is_drop_column (para eventos de modificacin de tablas) ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_type ora_dict_obj_name ora_dict_obj_owner ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner 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 ora_sysevent ora_login_user ora_instance_num ora_database_name ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner 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) ora_sysevent ora_login_user ora_instance_num ora_database_name ora_dict_obj_name ora_dict_obj_type ora_dict_obj_owner
ALTER
BEFORE/AFTER
DROP
BEFORE/AFTER
ANALYZE
BEFORE/AFTER
ASSOCIATE STATISTICS
BEFORE/AFTER
AUDIT/NOAUDIT
BEFORE/AFTER
Se lanza cuando el comando audit o noaudit es ejecutado Se lanza cuando el comando comment es ejecutado
COMMENT
BEFORE/AFTER
CREATE
BEFORE/AFTER
DDL
BEFORE/AFTER
Se lanza cuando algn comando DDL es ejecutado (no se ejecuta con un ALTER/CREATE DATABASE o CREATE CONTROLFILE)
DISASSOCIATE STATISTICS
BEFORE/AFTER
GRANT
BEFORE/AFTER
RENAME
BEFORE/AFTER
REVOKE
BEFORE/AFTER
TRUNCATE
BEFORE/AFTER
ora_sysevent ora_login_user ora_instance_num Se lanza cuando el ora_database_name comando disassociate ora_dict_obj_name statistics se ejecuta ora_dict_obj_type ora_dict_obj_owner ora_dict_obj_name_list ora_dict_obj_owner_list ora_sysevent ora_login_user ora_instance_num ora_database_name Se lanza cuando el ora_dict_obj_name comando grant se ora_dict_obj_type ejecuta ora_dict_obj_owner ora_grantee ora_with_grant_option ora_privileges ora_sysevent ora_login_user Se lanza cuando el ora_instance_num comando rename es ora_database_name ejecutado. ora_dict_obj_name ora_dict_obj_owner ora_dict_obj_type ora_sysevent ora_login_user ora_instance_num ora_database_name Se lanza cuando el ora_dict_obj_name comando revoke se ora_dict_obj_type ejecuta. ora_dict_obj_owner ora_revokee ora_privileges ora_sysevent ora_login_user ora_instance_num Se ejecuta cuando se ora_database_name trunca una tabla. 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 podra 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 pequea descripcin y el tipo de dato que tiene cada uno: Attribute Event ora_client_ip_address ora_database_name Data Type VARCHAR2 VARCHAR2(50) Description Devuleve la direccin IP de la mquina cliente cuando se utiliza un protocolo TCP/IP Devuelve el nombre de la base de datos
Devuelve la password encriptada del usuario que esta siendo creado o modificado Devuelve el nombre del objeto que esta siendo VARCHAR(30) manipulado BINARY_INTEGER Devuelve una lista de objetos que estn siendo
manipulados Devuelve el nombre del propietario del objeto que est siendo manipulado
ora_dict_obj_owner_list( Devuelve los nombres de los propietarios de los BINARY_INTEGER owner_list OUT objetos que estn siendo manipulados ora_name_list_t) Devuelve el tipo de objeto que est siendo ora_dict_obj_type VARCHAR(20) manipulado ora_grantee( BINARY_INTEGER Devuelve la lista de usuarios que tienen permisos user_list OUT ora_name_list_t) ora_instance_num NUMBER Devuelve el nmero de instancia ora_is_alter_column( Devuelve un valor de TRUE si la columna BOOLEAN column_name IN VARCHAR2) especfica ha sido modificada ora_is_creating_nested_t Devuelve un valor de TRUE si el evento actual crea BOOLEAN able una tabla anidada ora_is_drop_column( Devuelve un valor de TRUE si la columna BOOLEAN column_name IN VARCHAR2) especfica es eliminada Devuelve un valor de TRUE si el error especificado ora_is_servererror BOOLEAN est en la pila de errores Devuelve el usuario que hizo login en la sesin ora_login_user VARCHAR2(30) actual Devuelve la posicin en un comando CREATE TABLE donde la clasula de particin puede ser ora_partition_pos BINARY_INTEGER utilizada cuando se utiliza la sentencia INSTEAD OF en triggers ora_privilege_list( Devuelve la lista de privilegios que han sido privilege_list OUT BINARY_INTEGER concedidos o revocados ora_name_list_t) ora_revokee (user_list Devuelve la lista de los usuarios que han perdido BINARY_INTEGER algun/os privilegios cuando se ejecuta el comando OUT ora_name_list_t) REVOKE Devuelve el error, de la pila de errores, para una ora_server_error NUMBER posicin especfica en la pila (donde uno es la parte ms superior de la pila) Devuelve el nmero total de errores en la pila de ora_server_error_depth BINARY_INTEGER errores ora_server_error_msg Devuelve el error de la pila para la posicin VARCHAR2 (position in especificada en la pila (1 es la posicin ms alta de binary_integer) la pila) ora_server_error_num_par ams Devuelve el nmero de cadenas que han sido BINARY_INTEGER (position in sustituidas dentro del mensaje de error binary_integer) ora_server_error_param Devuelve el valor de la sustitucin que encaja en el (position in mensaje de error con el nmero de parmetro VARCHAR2 binary_integer, especificado en conjuncin con el valor param in binary_integer) especificado de la posicin en la pila Devuelve la sentencia SQL de la sentencia que ora_sql_txt (sql_text provoc que se ejecutara el trigger (Si la sentencia BINARY_INTEGER out es demasiado larga, esta ser separada en multiples ora_name_list_t) elementos de tabla de PL/SQL) el valor devuelto
ora_sysevent ora_with_grant_option
VARCHAR2(20) BOOLEAN
especifica el nmero de elementos Devuelve el evento de sistema o de cliente que provoc que el trigger se ejecutara Devuelve un valor de TRUE si los privilegios concedidos han sido otorgados con la instrucin GRANT
space_error_info( error_number OUT NUMBER, error_type OUT VARCHAR2, object_owner OUT VARCHAR2, BOOLEAN table_space_name OUT VARCHAR2, object_name OUT VARCHAR2, sub_object_name OUT VARCHAR2)
Devuelve un valor de true si el error esta relacionado con un error del tipo out-of-space y devuelve un objeto de informacin con el error
A continuacin se listan algunos ejemplos de triggers que demuestran la flexibilidad del uso de cdigo 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 informacin sobre la tabla logon_session:
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;
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
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 clasula OR como se muestra a continuacin: 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 especfico para deshabilitar la opcin de borrar objetos de la base de datos. CREATE OR REPLACE TRIGGER disable_dropping BEFORE DROP ON SCOTT.SCHEMA
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 operacin: SQL> drop table bonus; drop table bonus * ERROR en lnea 1: ORA-00604: error producido a nivel 1 de SQL recursivo ORA-20000: No se le permite borrar objetos ORA-06512: en lnea 2
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 comprobacin: 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 sern 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 haba antes de lanzar una sentencia UPDATE, es decir, los viejos valores que todava 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 ningn dato (el destino no existe), el trigger realiza una insercin 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
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. Tambin podramos haber incluido una condicin de ejecucin del trigger, por ejemplo, PARA CADA FILA CUANDO NOMBRE_COLUMNA!=, como se muestra en las siguiente lineas de cdigo: ........ FOR EACH ROW WHEN (NUEVO.nombre_dest!='') DECLARE ......... Ahora solo nos falta comprobar si nuestro trigger funciona correctamente. Haremos una insercin 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 Gmez', 'Limerick', sysdate, '03-SEP-2006'); Select * from Destinos Select * from Reservas
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.
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 nmero 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 insercin sobre nuestra vista INSERT INTO VISTA_NOMBRES_EMPNO (empno, ename) VALUES (7999,'Esuebio'); Y comprobamos que la insercin 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 cdigo comentada dentro del cuerpo del trigger podramos haber simulado nosotros mismos la insercin haciendo la insert correspondiente a mano dentro del trigger. Veamos un ejemplo ms 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,
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 gestin de informacin 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;
Para probarlo realizaremos la siguiente insercin: 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
---------10
---------20
---------30
1 filas seleccionadas.
Este bloque de cdigo tiene una estructura general que se ajusta al siguiente patrn 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 accin que realizamos sobre la tabla EMP y devuelve un mensaje al usuario dndole confirmacin de dicha operacin. 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, 'Mara'); Insertado empleado: Mara 1 fila creada. - Cuando actualizamos una fila en la tabla empleados UPDATE SCOTT.EMP SET ename = 'Mara Jose' where empno = 1234;
Actualizando empleado: Mara por Mara Jose 1 fila actualizada. - Cuando borramos una fila de la tabla empleados DELETE FROM SCOTT.EMP where empno=1234; Borrando empleado: Mara Jose 1 fila suprimida.
Trigger que se ejecutan al realizar alguna accin 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 adems 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 actualizacin 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, bastara con eliminar de la tabla DEPT, todos los empleados que trabajan, por ejemplo, en el departamento 20: DELETE FROM DEPT where deptno=20;
Hay varias vistas del diccionario de datos que arrojan informacin sobre los triggers almacenados. Las dos vistas principales se centran en la vista del cdigo fuente para paquetes, procedimientos y funciones, pero tambin almacenan el estado de compilacin de los triggers. Esto puede ser importante para determinar si un trigger se compil con exito o no. El cdigo fuente para el trigger y la informacin acerca del estado ENABLED o DISBALED se almacena tambin en las vistas de los triggers. Hay tres vistas para cada area de informacin dentro del mbito de los triggers: user_triggers all_triggers dba_triggers user_source all_source dba_source
Realizaremos algunos ejemplos que nos mostrarn el uso de estas vistas. El primero de ellos nos devolver informacin a cerca del estado de compilacin del trigger. Un estado de VALIDO nos indica que el trigger se ha compilado correctamente y que est lista para su ejecucin. Un estado de INVALIDO significa que el trigger necesita ser compilado antes de que pueda ser ejecutado: COLUMN SELECT FROM WHERE object_name FORMAT a24 object_name, object_type, status user_objects 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 informacin 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 COLUMN SELECT status FROM WHERE trigger_name FORMAT a24 triggering_event FORMAT a15 trigger_name, trigger_type, base_object_type, triggering_event, user_triggers db_object_type IN ('DATABASE ', 'SCHEMA');
La salida identifica a los triggers por su tipo, por los eventos que causan su ejecucin, por la hora de ejecucin y el estado. Puede ser interesante solicitar a la base de datos el contenido (o cdigo 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 introduccin, no hablar de la programacin 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.
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, adems, define dentro de si, las funciones y procedimientos que determinarn 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 fsico 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 mtodo, a los procedimientos o funcines que opera sobre los atributos del objeto. Los mtodos de un objeto, solo pueden ser invocados sobre el propio contexto del objeto. Hay un mtodo 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. Podramos hacer una insert en la tabla a travs de nuestro objeto: insert into empleados values(empleado('4443','Pedro','03-JUN1974')); 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 informacin que contiene, veamos el siguiente cdigo de PL/SQL: SET SERVEROUTPUT ON; declare
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 basndose en el tipo de objeto empleado (la instancia del objeto). La parte ms importante a destacar es la carga del objeto que realiza la select. La tabla empleados tiene un alias e y estamos utilizando la funcin value (en combinacin con el alias) para devolver valores al objeto, el objeto devuelto por la funcin 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 mtodo 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 ms filas?, vamos a modificar un poco el cdigo 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 informacin 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 informacin de la tabla. Mediante la clusula 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?. Fijmonos en el siguiente cdigo: CREATE or REPLACE TYPE direccion AS OBJECT ( calle varchar2(20),
ciudad varchar2(20), cp varchar2(20) ); El script de arriba, crea un objeto llamado direccion cuyos campos son, calle, ciudad y cdigo 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 informacin agrupada sin que tengan ninguna relacin 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, podramos 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 tambin 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 clasula 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;
definicin (parte pblica) y la parte del cuerpo del paquete (parte privada), con los objetos nos ocurre lo mismo, es necesario hacer la declaracin 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 ms que una funcin que implementa Oracle automticamente 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 parmetros en el mismo orden en el que los atributos fueron declarados en la definicin 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 mtodo para cambiar el numero de empleado un_empleado := un_empleado.set_num_emp(105); -- Imprimimos el contenido del empleado un_empleado.mostrar_todo();
END; Es importante destacar la sintaxis utilizada para realizar llamadas a los mtodos del objeto. El uso de objeto.metodo es la estandar de llamar a miembros de un objeto en lenguajes OO. Fijmonos 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 invlida, pues a que empleado le estamos cambiando el nmero? 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, adems de los campos de atributo, crea una columna ms para guardar el identificador de cada objeto insertado. Como viene implcito en la propia clasula PRIMARY KEY, Oracle crea tambin 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')); podramos 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 pasndole 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;
Prctica: 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 habitacin), 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 establecern referencias, pero hay que recordar que cada cliente debe de tener una clave nica como identificador y que cada habitacin tendr su propia clave (puede ser el propio nmero de habitacin). Desde un bloque de cdigo 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
Seleccin : _ Cada mtodo 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 tambin). actualizacion_cliente : Cambia de habitacin a un cliente (solo cambio de habitacin). 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,
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
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;
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('OPCIN :');
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;
--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
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('Opcin no vlida'); 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;