CASE DESARROLLO DE APLICACIONES INFORMÁTICAS EN LEGUAJES DE CUARTA GENERACIÓN CON HERRAMIENTAS CASE. DeMoN BLOQUE II T.7. LENGUAJE PL/SQL 1. INTRODUCCIÓN HISTÓRICA. 2. ESTRUCTURA DE UN PROGRAMA PL/SQL: BLOQUES 3. CARACTERÍSTICAS GENERALES DEL LENGUAJE PL/SQL 4. VARIABLES PL/SQL 5. ESTRUCTURAS DE CONTROL 6. TIPOS COMPUESTOS DE DATOS: REGISTROS 7. TABLAS O ARRAYS. 8. YTILIZACIÓN DE SENTENCIAS SQL EN PL/SQL 9. CURSORES 10. SUBPROGRAMAS: PROCEDIMIENTOS Y FUNCIONES. 11. PAQUETES 12. DISPARADORES O TRIGGERS DE LA BD 13. TRATAMIENTO DE ERRORES 1. INTRODUCCIÓN HISTÓRICA En PL/SQL se pueden escribir sentencias SQL, que se encargan de acceder a los datos y que están intercaladas con las sentencias de control propias de un lenguaje de 3ªG. En este caso se dice que SQL está embebido en un lenguaje de programación al que se le llama lenguaje anfitrión. El lenguaje anfitrión puede ser cualquiera que esté preparado por el fabricante para contener SQL como C, ADA, JAVA, PL/SQL... PL/SQL significa PROCEDURAL LANGUAJE/SQL. Es prodedimental, creado por ORACLE, que combina SQL con instrucciones y características procedimentales de un lenguaje de 3ªG, como son: - Variables y tipos de datos. Estructuras de control. Subprogramas: funciones y procedimientos. PL/SQL está basado en lenguaje ADA. 3 2. ESTRUCTURA DE UN PROGRAMA PL/SQL: BLOQUES. La unidad básica en PL/SQL es el bloque. Todo programa está compuesto por bloques, que pueden estar situados de manera secuencial o anidados. Un bloque tiene 3 secciones: [DECLARE /*sección declarativa*/ ] BEGIN /*sección de instrucciones*/ ] [EXCEPTION /*sección de excepciones*/ ] END; La sección DECLARE es donde se declaran las variables, tipos de datos definidos por el usuario y cursores usados por el bloque. En ella también se pueden declarar funciones y procedimientos locales. La sección BEGIN es donde aparecen las sentencias procedimentales de PL/SQL y las sentencias de SQL embebidas. La sección EXCEPTION se ejecuta si se produce una excepción o un error y se utiliza para detectar y gestionar los errores y excepciones. Hay varios tipos de bloques: - Bloques no almacenados en la BD: Son bloques que se escriben para ser ejecutados una sola vez y no se almacenan como objetos en la BD. Existen dos formas de escribirlos: - - Bloques anónimos: no se identifican por ningún nombre. Bloques nominados: bloques identificados mediante una etiqueta. Bloques almacenados en la BD: Son bloques de código que se almacenan en la BD como objetos de la BD, al igual que otros objetos como tablas, usuarios, roles... Se construyen para ser ejecutados múltiples veces. Existen dos tipos: 4 - Subprogramas: funciones, procedimientos o paquetes. Han de ser llamados de forma explícita para ser ejecutados, mediante una llamada. - Disparadores o Triggers: que se ejecutan de una manera implícita cada vez que acontece el suceso o disparo asociado al trigger. Un suceso de disparo es una sentencia DML de SQL que modifica la BD (insert, update...). 3. CARACTERÍSTICAS GENERALES DEL LENGUAJE PL/SQL - PLSQL no distingue entre mayúsculas y minúsculas. - Los identificadores que dan nombre a los objetos PL/SQL como variables, tipos de datos, cursores, subprogramas, subtipos, paquetes, etc. han de empezar con una letra seguido de letras, digitos y los caracteres “$”,”_”,”#” hasta 30 caracteres de longitud máxima (si se encierra entre comillas dobles el identificador se pueden utilizar palabras reservadas, caracteres no permitidos y diferenciar entre mayúsculas y minúsculas: “BEGIN” o “Una variable” o “x*y”. - Constantes de 3 tipos: . Cadena de Caracteres entre comillas simples: ‘ASI’ o ‘Administracion de Sistemas’ . Numéricos: 10 o 12.93 . Booleanas: TRUE, FALSE o NULL - Comentarios de 2 tipos: . Comentarios monolínea: comienzan x dos guiones y continúa hasta el final de la línea: BEGIN V_Codrama char(3):=’COI’: --esto es un comentario monolinea . Comentarios multilínea: están delimitados por /* y */: /* esto es un comentario multilínea */ - Todas las sentencias finalizan con “;” Operadores: Los operadores del lenguaje PLSQL son: - Operador de asignación: “:=” - Operador de concatenación: “||” - Operadores relacionales: “>”,”>=”,”<”,”<=”,”=” y “!=” - Operador IS NULL devuelve TRUE si el valor de la variable es nulo: IF v_salida IS NULL THEN DBMS_OUT....PUT_LINE (‘No hay nada para escribir’); - Operador LIKE compara cadenas de caracteres con patrones: IF v_salida LIKE ‘%INFORMATIC%’ THEN DBMS_OUT.... (‘La nueva rama es del ciclo de informatica’); - Operador BETWEEN permite combinar los operadores “<=” y “>=” en una sola expresión: IF v_salar BETWEEN 1000 AND 1500 THEN DBMS_OUT... (‘El salario no sta mal’); 5 - - Operador IN devuelve TRUE si el valor está contenido en la lista de constantes entre paréntesis: IF v_codrama IN (‘DAI’, ‘ASI’) THEN DBMS_OUTPUT_PUT_LINE (‘Es un ciclo de Informatica’); Operadores lógicos “AND”, “OR” y “NOT Operadores aritméticos “+”, “-“, “/” y “*” 4. VARIABLES PL/SQL 4.1 DECLARACIÓN DE VARIABLES Se han de declarar las variables en la sección declarativa de un bloque. La sintaxis para declara una variable es: nombre.variable [CONSTANT] tipo [NOT NULL] [{:=|DEFAULT}valor]; Donde con la clausula CONSTANT el valor inicial no podra ser modificado por lo que se convierte en un dato constante con nombre: NUNMAX CONSTANT NUMBER:=80; Con la clausula NOT NULL es obligatorio inicializar la variable y no se le podra asignar el valor NULL,para incializar una variable se utiliza “:=” o DEFAULT. Ambos tienen idéntico comportamiento de inicializar la variable,si al declarar una varibale no se inicializa,se le asigna el valor NULL. Solo se puede declarar una variable por linea,así,es erroneo poner: v_var1, v_var2 number;--esto es una declaración errónea ejem: DECLARE v_codigo number := 100; v_denominación char(50) DEFAULT ‘SEDE CENTRAL’; PI Constant number:=3.1416; v_respuesta char NOT NULL:=’S’; 4.2 TIPOS DE DATOS Los tipos de datos determinan los valores q pueden contener una variable. Hay tres categorías de tipos de datos: - Escalares: son datos simples, es decir, q no tienen componentes (CHAR, FLOAT, etc) - Compuestos: tipo RECORD y tipo TABLE. - Referencias como los punteros de C. Los tipos de datos disponibles en PL/SQL se definen en un paquete llamado STANDARD que se encuentrá en C: \ORACLE\ORA81\RDBMS\ADMIN\STANDARD.SQL Los tipos de datos disponibles en PL/SQL son los mismos que pueden utilizarse en una columna de la Base de Datos más algunos tipos adicionales - NUMBER (p,s) 6 - - - BINARY_INTEGER es un tipo para datos numéricos que se almacena en binario con complemento A2,no se utiliza para datos que vayan a ser almacenados en una columna de una tabla de la base de datos,se suele usar como índice de arrays,su rango es:+2.147.483.647 VARCHAR2(n):Para cadenas de longitud variable,su longitud máxima es:32.767 bytes CHAR(n):Para cadenas de lo0ngitud fija,su longtud máxima es de:32.767 B LONG:Es una cadena de longitud variable,su longitud máxima es de 32.760 B (El tipo de dato LONG de base de datos es de cuatro GB,asi que no se uede asignar una columna de la base de datos de tipo LONG a una varibale de tipo SQL de tipo LONG DATE:Contien fechas,incluyendo año,mes,dia,hora,minutos y segundo BOOLEAN:Puede tener los valores TRUE,FALSE,NULL ROWID:Almacena identificadores unívocos de filas de la base de datos,cada fila de una tabla tiene un identificador único en toda la base de datos,este es su ROWID,que puede ser consultado en una sentencia SELECT usando la pseudocolumna ROWID. 4.3. ATRIBUTO % TYPE Para declarar una variable de un tipo determinado se puede indicar que tome el mismo tipo que una columna de una tabla de la base de datos o de otra varibale del programa Mediante el atributo % TYPEl.El atributo %TYPE cuando se aplica sobre una columna o una variable devuelve su tipo de dato. 4.4 SUBTIPOS Es un nombre alternativo para un tipo de dato,y que ademas puede restringir el conjunto de valores del tipo original.PLSQL define varios subtipos en el paquete estandar.La sintaxis para definir los subtipos es:SUBTYPE tipo_contador is number; v_i1 tipo_contador; 4.5 ATRIBUTO Y VISIBILIDAD DE LAS VARIOABLES El ambito de una variable es la parte del programa en la que se puede acceder a una variable.El ambito de una variable PLSQL es desde que se declara hasta el final del bloque en que se declara.La visibilad de una variable es la parte del programa donde se puede acceder a la variable sin necesidad de calificar.Si una variable esta declarada en un bloque y otra variable con el mismo nombre se declara en un bloque anidado para referirse desde el bloque anodado a la variable mas externa habra que calificarla con la etiqueta del bloque donde se declaro la variable y un punto. 7 5. ESTRUCTURAS DE CONTROL 5.1 ESTRUCTURA IF –THEN-ELSE. Alternativa Simple: If condicion THEN Instrucciones; END IF; Alternativa doble: If condicion THEN Instrucciones; ELSE Instrucciones; END IF; Alternativa multiple: IF condicion THEN Instrucciones; ELSIF condición THEN Instrucción; ELSIF condición THEN Instrucción ... [ELSE instrucción;] END IF; 5.2 REPETITIVA: BUCLE SIMPLE SINTAXIS: LOOP Sentencia de ordenes; [EXIT [WHEN condición]] sentencia de ordenes; END LOOP; La sentencia EXIT WHEN permite salir del bucle cuando se cumpla la condición, puede aparecer en cualquier sitio dentro de la estructura repetitiva e incluso no aparecer. Ejerc: Crear una tabla llamada TEMP con una columna llamada NUM tipo NUMBER e insertarle los numeros naturales del 1 al 50; 8 1 2 3 4 5 6 7 8 9 DECLARE v_var number(2):=0; BEGIN LOOP v_var:=v_var+1; INSERT INTO TEMP VALUES(v_var); EXIT WHEN v_var=50; END LOOP; END; 5.3 REPETITIVA BUCLE WHILE Sintaxis: WHILE condicion LOOP Secuencia de ordenes; END LOOP; La condición se evalúa antes de cada interacción del bucles. La secuencia de órdenes se ejecuta mientras la condición es verdad, si es falsa o nula finaliza la ejecución del bucle. Se puede utilizar la orden EXIT o EXIT WHEN xa salir prematuramente del bucle. 5.4 REPETITIVA: BUCLE FOR Son bucles en los que el numero de iteraciones de la repetitiva se conoce de antemano,su sintaxis es: FOR contador_bucle IN [REVERSE] limiteinferior..limitesuperior LOOP Secuencia_de_órdenes; END LOOP; Donde contador bucle es una variable numerico entera que se declara de forma implícita No hay que declararla xo si se declara al llegar al bucle se declara otra variable con el mismo nombre pero local al bucle. Límite inferior es el valor inicial de la variable contador Límite superior es el valor final de la variable contador. En cada iteración la variable contador_bucle se implementa en 1,si se especifica la clausula REVERSE la variable contador bucle toma como valor inicial el límite superior y se decrementa en cada iteracion en 1 hasta llegar al valor del límite inferior. 1 2 3 4 5 BEGIN FOR v_contador IN 1..50 LOOP INSERT INTO TEMP VALUES(C_CONTADOR); END LOOP`; END; 9 5.5 SENTENCIA GOTO Es una bifurcación incondicional a otra parte del código. Sintaxis: GOTO etiqueta; La etiqueta indica el lugar del código que tiene esa etiqueta y al que se ha de bifurcar la ejecución del programa. Las etiquetas colocadas en el código han de estar encerradas entre corchete s angulares dobles: <<etiqueta>> xo para referirse a la etiqueta desde una instrucción, x ejemplo, GOTO ó EXIT no se añaden los corchetes dobles. Es ilegal realizar un salto al interior de un bucle, al interior de una sentencia IF y también es ilegal ir desde la sección de excepciones a la sección procedimental. Ejem: DECLARE v_acumulador number(2):=0; CTE_LIMITE constant number(2):=50; BEGIN LOOP v_acumulador:=v_acumulador+1; insert into temp values(v_acumulador); IF v_acumulador=CTE_LIMITE THEN GOTO despues_del_bucle; END IF; END LOOP; <<despues_del_bucle>> END; 5.6 SENTENCIA EXIT Hace que finalice un bucle antes de que se cumpla su condición de salida. Sintaxis: EXIT [etiqueta] [WHEN condicion]; Un bucle puede estar etiquetado antes de su inicio, esto permite a la sentecia EXIT indicar cual es el bucle del que se quiere salir. BEGIN <<bucle_externo>> FOR idx_i IN 1..50 LOOP ......... <<bucle_interno>> FOR cdx_j IN 1..100 LOOP ... 10 ... EXIT bucle_externo WHEN condicion; END LOOP; ..... .... END LOOP; .... .... END; 5.7 SENTENCIA NULL Indica que no realice ninguna acción. Ejem: IF condicion THEN NULL; ELSE Condicion; END IF; 5.8 ESTILO DE IDENTIFICADORES DE OBJETOS DE UN PROGRAMA PL/SQL Para clarificar la función que realizan las variables que aparecen en un programa y distinguirlas de las columnas de una tabla se sugieren los siguientes prefijos: v_nombrevariable -> variable de programa e_nombreexcepción -> excepciones definidas por el usuario tipo_nombretipo -> tipos definidos por el usuario p_nombreparametro -> parametros de procedimientos y funciones c_nombrecursor -> cursores cte_nombreconstante -> constantes i_nombreindice -> indices de bucles FOR y arrays t_nombrearray -> arrays reg_nombregistro -> datos compuestos de tipo RECORD 11 6. TIPOS COMPUESTOS DE DATOS: REGISTROS Son similares a las estructuras de C, es decir, es un tipo compuesto de varios campos de tipos ya definidos. 6.1 DECLARACIÓN DE REGISTRO: Al contrario q los tipos escalares q están predefinidos en el lenguaje los tipos compuestos han de ser definidos por el usuario. Para poder utilizar una variable compuesta primero ha de definirse el tipo y luego declarar variables de ese tipo. La sintasix para definir un tipo de registro es la siguiente: TYPE nombre_tipo_registro IS RECORD( campo1 tipo1 [NOT NULL] [:=valor1], campo2 tipo2 [NOT NULL] [:=valor2], . . . campon tipon [NOT NULL] [:=valorn]); ejem: DECLARE TYPE REG_ALUMNO IS RECORD( dni char(10), nomalumno varchar2(65), codrama char(3), codgrupo char(2)); v_alumno1 REG_ALUMNO; v_alumno2 REG_ALUMNO; 6.2 REFERENCIA A UN CAMPO Para hacer referencia a un campo de una variable de tipo registro es con la sintasix: variable.campo. Ejem: BEGIN v_alumno1.dni:=10; v_alumno2.codrama:=’DAI’; Para poder asignar una variable de tipo registro a otra han de ser del mismo tipo de registro. Se haría de la siguiente manera: v_alumno1:=v_alumno2; nota: aunque fueran dos registros con los mismos campos no se pueden asignar variables registros de distintos tipos, aunque si se puede realizar la asignación campo a campo. 12 También se puede asignar los valores a una variables registro mediante una sentencia SELECT Ejem: select * INTO v_alumno2 from alumno where dni=380; DBMS_OUTPUT.PUT_LINE (v_alumno2.numalum no); END; 6.3 ATRIBUTO %ROWTYPE Para facilitar la declaración de una variable de tipo registro con los mismos campos y tipos que una tabla de la base de datos se proporciona el atributo %ROWTYPE que devuelve un tipo de registro basandose en la definició n de una tabla: Ejem: DECLARE v_alumno alumno%ROWTYPE; BEGIN select * INTO v_alumno from alumno where dni=380; DBMS_OUTPUT.PUT_LINE (v_alumno.nomalumno); END; 7. TABLAS O ARRAYS (ver fotocopias). 8. UTILIZACIÓN DE SECUENCIAS SQL EN PL/SQL Las únicas órdenes SQL permitidas en PL/SQL son las DML(INSERT, UPDATE, DELETE, SELECT) y las de control de transacciones DCL (COMMIT, ROLLSEEK). 8.1 VARIABLES DE ACOPLAMIENTO: En las expresiones de una sentencia DML se podrá especificar variables de programa que al estar dentro de una sentencia SQL se llaman variables de acoplamiento. Ejem: DECLARE v_dni alumno.dni%TYPE; v_codigoasignatura asigna.codasigna%TYPE; v_nota estudia.notaf%TYPE; BEGIN v_dni:=380; v_codigoasignatura:=’CASE’; 13 v_nota:=6; update estudia s et nota1=v_nota where dni=v.dni AND codasigna=v_codigoasignatura; END; 8.2 SENTENCIA SELECT El formato de la sentencia SELECT en PL/SQL es: SELECT {*|columna[,columna]...} INTO {variable1[,variable2]...|variable.registro} FROM tabla1[,tabla2]... [WHERE predicado] [GROUP BY columna[,columna]...] [HAVING predicado] [ORDER BY columna[,columna]...]; La clausula INTO permite almacenar en variables de programa el resultado de una sentencia SELECT. Puede ser una variable de tipo registro o una lista de variables simples. La clausula WHERE ha de devolver una sola fila ya que el resultado se almacenará en variables, si devuelve varias filas la sentencia SELECT se produce el siguiente error: ORA-1427: Single-row query returns more than one row; Para recoger varias filas de una SELECT se ha de utilizar cursores. 8.3 SENTENCIA UPDATE Sentencia: UPDATE tabla SET { columna=expresion [,columna=expresion]|(columna[,columna]...)=(subconsulta)} WHERE {predicado|CURRENT OF nombre_cursor}] La sintasix CURRENT OF nombre_cursor se utiliza junto con la definición de un cursor actualizable. 8.4 SENTENCIA DELETE Sintasix: DELETE [FROM] tabla [WHERE {predicado|CURRENT OF nombre_cursor}] 8.5 CONTROL DE TRANSACCIONES Una transacción es una serie de órdenes SQL que se completan o fallan como una unidad y sirven para evitar la inconsistencia de los datos. En Oracle una transacción comienza con la primera orden SQL emitida después de terminar la transacción anterior o con la primera orden SQL después de iniciar una sesión. 14 8.5.1 COMMIT Y ROLLBACK Cuando se ejecuta una orden COMMIT termina la transacción y suceden : 1º Se hacen permanentes los cambios realizados en la transacción. 2º Otras sesiones pueden ver los cambios realizados por la transacción. 3º Se liberan todos los bloques establecidos por la transacción. Sintasix: COMMIT [WORK]; Cuando se ejecuta una orden ROLLBACK termina la transacción y suceden 2 cosas: 1º Todo trabajo echo x la transacción se deshace como si no se hubieran realizado cambios. 2º Se liberan todos los bloqueos establecidos por la transacción. Sintasix: ROLLBACK [WORK]; Si una sesión se desconecta accidentalmente de la BASE de DATOS sin haber echo ni COMMIT ni ROLLBACK el sistema ejecuta automaticamente un ROLLBACK. SQL PLUS ejecuta automaticamente un ROLLBACK cuando el usuario abandona una sesión en SQL PLUS. 8.5.2 PUNTOS DE SALVAGUARDA (SAVEPOINT): La orden ROLLBACK deshace todas las modificaciones de una transacción, xo si se utilizan puntos de salvaguarda puede deshacerse sólo parte de las modificaciones de la transacción. Para declarar un punto de salvaguarda se utiliza la siguiente sintasix: SAVEPOINT nombre_punto_salvaguarda; Una vez definido un punto de salvaguarda el programa puede deshacer parte de la transacción utilizando la sintasix de la sentencia: ROLLBACK [WORK] TO [SAVEPOINT] nombre_punto_salvaguarda; 1º Cualquier cambio desde el punto de salvaguarda se deshace. 2º Se libera cualquier bloqueo establecido por la transacción desde el punto de salvaguarda. 3º La transacción no finaliza ya que hay órdenes SQL pendientes. Ejem: BEGIN INSERT INTO alumno VALUES (951,’ANA’,’DAI’,’1A’); /* se graba */ SAVEPOINT punto1; INSERT INTO alumno VALUES (952,’CARMEN’,’DAI’,’2A’); /* se graba */ SAVEPOINT punto2; INSERT INTO alumno VALUES (953,’ERNESTO’,’DAI’,’1A’); /* no se graba */ SAVEPOINT punto3; INSERT INTO alumno VALUES (954,’OFELIA’,’DAI’,’1A’); /* no se graba */ ROLLBACK TO SAVEPOINT punto2; INSERT INTO alumno VALUES (955,’CARLOS’,’DAI’,’1A’); /* se graba */ COMMIT; 15 END; Se suele utiliza SAVEPOINT antes de una parte compleja de una transacción, así si esa parte de la transacción falla se puede deshacer permitiendo que continue el efecto de los cambios previos al SAVEPOINT. Es posible iniciar una transacción de sólo lectura mediante la sentencia SET TRANSACTION READ ONLY; 8.6 COMPARACION DE CADENAS DE CARACTERES Existen dos técnicas con relleno a blancos y sin relleno a blancos Con relleno a blancos:’ABC’ es igual ‘ABC ’ Sin relleno a blancos:’ABC’ es distinto’ABC ‘ DECLARE V_cadena1 char(4):= ‘ANA’; V_cadena2 varchar2(4):=’ANA’; BEGIN IF v_cadena1=v_cadena2 THEN DNMS_OUTPUT.PUT_LINE(‘LAS CADENAS SON IGUALES’); ELSE DBMS_OUTPUT.PUT_LINE(‘LAS CADENAS SON DISTINTAS’); END IF; END. PL/SQL utiliza la semántica con relleno a blancos sólo cuando las dos cadenas a comparar son de longitud fija,es decir datos declarados de tipo char,o bien constantes alfanuméricas entre comillas. Si alguna de las cadenas es de longitud variable entonces utiliza la semántica sin relleno a blancos.Para evitar problemas en la clausula WHERE de una sentencia SQL lo mas adecuado es declarar las variables PL/SQL del mismo tipo de las columnas a comparar utilizando el atributo %TYPE 9. CURSORES Cuando se ejecuta una sentencia SELECT en un programa PL/SQL el resultado de la consulta ha de ser recogido en variables de programa; sin embargo si la sentencia SELECT devuelve varias filas el resultado ha de ser gestionado por cursores. Cuando se procesa una orden SQL Oracle asigna un área de memoria que recibe el nombre de “Área de Contexto” que contiene el “Conjunto Activo” que es el conjunto de filas resultado de una consulta. Un cursor es un puntero al “Área de Contexto” que permite controlar los datos recuperados de la base de datos. Existen dos tipos de cursores: . Cursor explícito: que ha de ser creado por el programador y que se utiliza cuando una sentencia SELECT devuelve varias filas. 16 . Cursor implícito: que es creado y procesado automáticamente por PL/SQL y lo crea con sentencia SELECT que devuelve una sola fila y con las sentencias INSERT, UPDATE y DELETE. 9.1. CURSOR EXPLÍCITO: Los pasos para procesar un cursor explícito son: 1. 2. 3. 4. Declaración del cursor. Apertura del cursor. Extracción de los datos del cursor y su almacenamiento en variables de programa. Cierre del cursor. 9.1.1. DECLARACIÓN DE CURSORES: La declaración de un cursor le da un nombre y lo asocia con una sentencia SELECT; su sintaxis: CURSOR nombre_cursor IS sentencia_select: La sentencia_select puede ser cualquier consulta que puede incluir JOINS y operadores de conjuntos como UNION ó MINUS. En la clausula WHERE se puede hacer referencia a variables previamente declaradas. 9.1.2. APERTURA DEL CURSOR Sintaxis: OPEN nombre_cursor; Donde nombre_cursor ha de ser un cursor previamente declarado. Cuando se abre un cursor suceden 2 cosas: 1. Se examinan los valores de las variables de programa acopladas en la sentencia SELECT y se determina el conjunto activo basandose en los valores de la variable de acoplamiento si éstas existen, es decir, se ejecuta la sentencia SELECT almacenando los valores en el cursor. 2. Se hace apuntar el puntero del conjunto activo a la primera fila del cursor. La ejecución de la sentencia SELECT asociada al cursor solo se ejecuta cuando se abre el cursor, de tal manera que si las variables acopladas cambian posteriormente de valor el conjunto activo no varía. Para poder variar el conjunto activo se tendría que cerrar y volver a abrir el cursor. Se puede abrir un cursor que ya está abierto ya que el sistema lo cierra antes de volver a abrirlo. Ejemplos CURSOR 17 9.1.3. EXTRACCIÓN DE LOS DATOS DEL CURSOR. Para acceder a los datos de un cursor se utiliza la sentencia FETCH cuya sintaxis es: FETCH nombre_cursor INTO {variable1[,variable2]...|variable_registro}; donde nombre_cursor es un cursor previamente declarado y abierto; la clausula INTO almacena la fila activa del cursor en variables simples o en una variable de tipo registro; en cualquier caso ha de ser compatible con los datos extraidos del cursor en cuanto al número y tipo. Después de la ejecución de una sentencia FETCH se incrementa el puntero activo del cursor que apuntara a la siguiente fila. La sentencia FETCH se suele ejecutar dentro de una sentencia de control repetitiva y de esta forma cada ejecución de una sentencia FETCH devolverá filas sucesivas del conjunto activo hasta que se extraiga el conjunto completo, por tanto, la sentencia FETCH dentro de una repetitiva permite realizar un recorido secuencial del cursor. Los atributos de cursor %FOUND y %NOTFOUND se utilizan para detectar cuando se ha terminado de extraer el conjunto activo. La última orden FETCH no realizara asignación a la variable de programa, asique estas varialbes contendrán los valores de la última fila extraida del conjunto. Ejemplos CURSOR 9.1.4. CIERRE DEL CURSOR Cuando se ha terminado de extraer el conjunto activo debe cerrarse el cursor y se liberan los recursos utilizados. Sintasix: CLOSE nombre_cursor; No se pueden extraer datos de un cursor cerrado y no se puede cerrar un cursor ya cerrado. 9.1.5. ATRIBUTOS DEL CURSOR Los atributos de un cursor devuelven valores utilizados en expresiones. - %FOUND: Devuelve TRUE si la última sentencia FETCH ejecutada devolvió una fila y FALSE en caso contrario, es decir, si ya se han extraido todas las filas del cursor. %NOTFOUND: Devuelve TRUE si la última sentencia FETCH no devolvió ninguna fila y FALSE en caso contrario. %ISOPEN: Sirve para determinar si un cursor está abierto o cerrado. Devuelve TRUE si está abierto y FALSE en caso contrario. %ROWCOUNT: Devuelve el número de filas extraidas del cursor hasta el momento. 18 9.1.6. BUCLES DE EXTRACCIÓN DE UN CURSOR: La operación más común con un cursor es extraer todas las filas del conjunto activo. Para ello se utiliza un bucle de extracción que procesa una a una todas las filas del conjunto activo dependiendo del tipo de bucle utilizado se tendrán las siguientes implementaciones: . Bucle Simple: DECLARE CURSOR c_alumno IS Select * from alumno i Where codrama=’DAI’ v_alumno alumno%ROWTYPE; BEGIN OPEN c_alumno; LOOP FETCH c_alumno AND v_alumno; EXIT WHEN c_alumno%NOTFOUND; --Acciones DBMS... (v_alumno.nomalumno); END LOOP; CLOSE c_alumno; END; .Bucle WHILE: Parte declarativa igual. BEGIN OPEN c_alumno; FETCH c_alumno INTO v_alumno; WHILE c_alumno%FOUND LOOP --Acciones DBMS___(v_alumno.nomalumno); FETCH c_alumno INTO v_alumno; END LOOP; CLOSE c_alumno; END; BUCLE DE CURSOR FOR: El bucle de cursor FOR es un bucle especial para cursores que realiza su procesamiento de modo implícito; su sintaxis es la siguiente: FOR variable_registro IN nombre_cursor LOOP --Acciones a realizar con la fila extraida. END LOOP; Donde la variable registro no tiene que declararse en la sección DECLARE sino que la declara implícitamente la propia sentencia FOR; el tipo de esta variable será: variable_registro nombre_cursor%ROWTYPE; Nota: no se pueden asignar valores a la variable dentro del bucle. 19 El bucle FOR, cuando comienza su ejecución abre el cursor, extrae una fila del cursor en cada iteración depositándola en la variable registro, y cierra el cursor cuando acaba de leer el conjunto activo. DECLARE Cursor c_alumno IS SELECT * FROM alumno WHERE codrama=’DAI’; v_alumno alumno%ROWTYPE; BEGIN FOR v_alumno IN c_alumno LOOP DBMS_OUTPUT.PUT_LINE(v_alumno.nomalumno); END LOOP; END; 9.1.7. CURSORES PARAMETIZADOS. Existe otro método para utilizar variables de acoplamiento en los cursores, mediante los cursores parametizados, que admiten argumentos al igual que los procedimientos y las funciones. La sentencia OPEN es la que se utiliza para pasar los valores al cursor. >DECLARE CURSOR c_alum(p_codrama rama.codrama%TYPE) IS SELECT nomalumno FROM alumno WHERE codrama=p_codrama; v_alum c_alum%ROWTYPE; BEGIN OPEN c_alum(‘DAI’); FETCH c_alum INTO v_alum; WHILE c_alum%FOUND LOOP DBMS_OUTPUT.PUT_LINE(v_alum.nomalumno); FETCH c_alum INTO v_alum; END LOOP; CLOSE c_alum; END; 9.2. CURSORES FOR UPDATE. Si se quieren modificar las filas extraídas por un cursor se ha de declarar un cursor FOR UPDATE. La forma de utilizar estos cursores es especificando la cláusula FOR UPDATE en la declaración del cursor, y la cláusula WHERE CURRENT OF nombre_cursor en las sentencias UPDATE y DELETE. 20 9.2.1. CLAÚSULA FOR UPDATE. La cláusula FOR UPDATE es parte de la sentencia SELECT del cursor y se especifica después de todas las cláusulas de la sentencia SELECT, es decir después de la cláusula ORDER BY si esta existe. Su sintaxis es: CURSOR nombre_cursor IS SELECT ---------------- FROM -----------------WHERE ---------------- ORDER columna1[,columna2]...] [NOWAIT ]; BY ----------- FOR UPDATE[OF La cláusula OF indica las columnas del cursor que pueden ser modificadas. Si no se especifica la cláusula, todas las columnas podrán ser modificadas. Si la consulta del cursor hace referencia a varias tablas mediante un JOIN entonces es obligatorio especificar la cláusula OF con las columnas que se quieren modificar. Cuando se abre un cursor declarado FOR UPDATE las filas que forman el conjunto activo son bloqueadas para que no puedan acceder otras sesiones a esos datos y controlar el acceso concurrente a los datos. La cláusula NOWAIT hace que si las filas están bloqueadas por otra sesión, la sentencia OPEN termine inmediatamente devolviendo el siguiente error: ORA-54: Resource busy and acquire with NOWAIT specified. 9.2.2. WHERE CURRENT OF Se utiliza en UPDATE y DELETE cuando se declara un cursor FOR UPDATE. Su sintaxis es. WHERE CURRENT OF nombre_cursor; La cláusula WHERE CURRENT OF hace referencia a la fila recien extraída por el cursor.ℑ 9.3. CURSORES IMPLÍCITOS. Todas las órdenes SQL se jecutan dentro de un area de contexto que tiene un cursor asociado.El cursor implícito sirve para procesar las órdenes SELECT ........INTO INSERT UPDATE DELETE que devuelva una fila ℑ No se puede incluir un COMMIT dentro de un bucle de extracción ya que liberaría los bloqueos establecidos por la sesión como consecuencia de la cláusula FOR UPDATE y el cursor quedaría invalidado. 21 Los cursores implícitos se llaman SQL y son declarados,abiertos,procesados y cerrados por el propio SQL.No tiene sentido aplicar la sentencia OPEN FETCH CLOSE,pero si tiene sentido utilizar los atributos de cursor. LA SENTENCIA SELECT INTO cuando recibe varias filas produce un error(TOO_MANY_ROWS) o si no devuelve ninguna fila(NO_DATA_FOUND) Y por tanto la ejecucion del programa se bifurca a la seccion de secciones del bloque,por lo que no se puede comprobar el valor del atributo SQL %NOT FOUND DECLARE v_nombre alumno.nomalumno%TYPE BEGIN Select nomalumno INTO v_nombre from alumno Where dni=999; IF SQL%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE(‘Noexiste ningun alumno con esedni:SQL%NOTFOUND’); END IF; DBMS_OUTPUT.PUT_LINE(‘FIN DE PROGRAMA’); EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE(‘No existe ningun alumno con ese dni:NO_DATA_FOUND’); End, Una vez que el programa se bifurca a la seccion de secciones no se puede regresar a la secció n procedimental y por tanto acaba el programa, por tanto, PARA HACER CONSULTAS QUE DEVUELVAN UNA FILA SI NO SE TIENE LA CERTEZA DE QUE LA CONSULTA NO VA A SER VACIA Y EN EL CASO DE QUE SEA VACÍA SE QUIEREN REALIZAR OTRAS SENTENCIAS PROCEDIMENTALES Y CONTINUAR EL PROGRAMA, ENTONCES SE HA DE UTILIZAR UN CURSOR EXPLICITO NOTA(NUNCA UTILIZAR SENTANCIAS SELECT INTO Y EN SU LUGAR USAR UN SECTOR EXPLICITO) SERIA CON CURSOR EXPLICITO DECLARE CURSOR c_alum IS Select nomalumno from alumno Where dni=999; V_nombre alumno.nomalumn%TYPE; BEGIN OPEN c_alum; FETCH c_alumno INTO v_nombre; IF c_alum%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE(‘No existe ningun alumno con ese dni’); END IF; --resto de acciones procedimentales; DBMS_OUTPUT.PUT_LINE(‘FIN DE PROGRAMA’); END, 22 10. SUBPROGRAMAS: PROCEDIMIENTOS Y FUNCIONES. Los procedimientos en PL/SQL son similares a los de los lenguajes de 3ª Generación, es decir, son un conjunto de sentencias a las que se le ha asignado un nombre y pueden ser llamadas desde otros puntos del programa pasándoles unos valores llamados argumentos. >CREATE OR REPLACE PROCEDURE añadiralumno( p_dni alumno.dni%TYPE, p_nombre alumno.nomalumno%TYPE, p_codrama alumno.codrama%TYPE, p_codgrupo alumno.codgrupo%TYPE) IS BEGIN INSERT INTO alumno values (p_dni,p_nombre,p_codrama,p_codgrupo); COMMIT; END añadiralumno; Al ejecutar este código se compila y el código compilado se almacena como un objeto en la BD. Lo podemos comprobar consultando la vista del diccionario de datos “ALL,DBA,USER_OBJECTS”. Para ver los errores de compilación se utiliza el comando >Show errors; Para ser ejecutado este procedimiento ha de ser invocado de manera explícita dando el nombre del procedimiento y especificando los argumentos. 10.1. CREACIÓN DE UN PROCEDIMIENTO. La sintaxis para crear un procedimiento es: >CREATE [OR REPLACE] PROCEDURE nombre_procedimiento [(parámetro1[{IN | OUT| IN OUT}] tipo_dato [{:= | DEFAULT}valor_inicial], ... parametroN [{IN | OUT| IN OUT}] tipo_dato [{:= | DEFAULT}valor_inicial], {IS | AS} [/*sección declarativa*/] BEGIN [/*sección procedimental*/] [EXCEPTION /*sección de excepciones*/] END [nombre_parametro]; donde nombre_procedimiento es el nombre con el que se almacena en la BD y con el que será invocado. Entre paréntesis se indica la lista de parámetros especificando sus tipos de datos. Si el procedimiento no tuviese parámetros no se especifican paréntesis ni en la declaración del procedimiento ni en la llamada al procedimiento. En la declaración del procedimiento se indica para cada parámetro formal su tipo de dato , que no puede ser restringido, es decir, puede ser char pero no puede ser char(3), así que sus longitudes vendrán dadas por los tipos de los argumentos que 23 llaman al procedimiento. La única forma de restringir los valores de los parámetro formales es utilizando el atributo %TYPE y basarse en una columna de una tabla de la BD. IS o AS indistintamente indican el inicio del cuerpo del procedimiento. La sección declarativa es opcional y se encuentra entre las palabras IS | AS y BEGIN, y en ella se declaran las variables locales del procedimiento. La sección procedimental se encuentra entre las palabras BEGIN y EXCEPTION. La sección de excepciones es opcional y se encuentra entre las palabras EXCEPTION y END. Para modificar el código de un procedimiento hay que borrarlo de la BD y volverlo a crear, o bien utilizar la la opción OR REPLACE que lo hace automáticamente. El código de los subprogramas está almacenado en la vista “USER_SOURCE” en la columna TEXT. 10.2. LLAMADA A UN PROCEDIMIENTO Y PASO DE PARÁMETROS. La llamada a un procedimiento puede realizarse desde cualquier otro bloque PL/SQL indicando el nombre del procedimiento y entre paréntesis los valores que se van a pasar al procedimiento. Estos valores se denominan argumentos y las variables que reciben estos valores dentro del procedimiento se denominan parámetros formales . 10.2.1. Modo de paso de parámetros: En la creación del procedimiento se indica para cada procedimiento el modo en que se pasará el argumento, por defecto es IN. - - - IN: hace que el parámetro formal sea sólo de lectura, y su valor no puede ser modificado dentro del procedimiento. Aparece siempre en la parte derecha de una expresión de asignación. Es un paso de parámetros por valor con la singularidad de que el parámetro no puede ser modificado en el procedimiento. OUT: hace que el parámetro formal sea solo de escritura, por lo que no puede ser leído y sólo puede asignársele valor. Aparece siempre en la parte izquierda de una expresión de asignación. Es un paso por referencia con la singularidad de que no puede ser leído el valor dentro del procedimiento. IN OUT: hace que el parámetro formal pueda ser leído y escrito, puede aparecer en la parte derecha y en la parte izquierda de una expresión de asignación, es decir, es un paso por referencia. Al finalizar la ejecución del procedimiento, aquellos parámetros definidos como OUT o IN OUT se les asigna su valor en los argumentos asociados en la llamada al subprograma, mientras que los parámetros definidos como IN no alteran el valor del argumento asociado en la llamada al subprograma. El argumento asociado a un parámetro IN puede ser una variable o una constante. El argumento asociado a un parámetro OUT o IN OUT ha de ser obligatoriamente una variable. 24 10.2.2. Paso de parámetros posicional y nominal. Cuando se llama a un procedimie nto los valores de los argumentos se asignan a los parámetros formales mediante dos posibles mecanismos: - - E.j.: Paso de parámetros posicional: donde el valor del primer argumento se pasa al primer parámetro formal de la declaración del procedimiento, el valor de l segundo argumento al segundo parámetro formal, y así sucesivamente. Paso de parámetro nominal: donde en la llamada al subprograma se indica el nombre del parámetro formal y el valor del argumento que se pasará en la llamada. Esto permite pasar los argume ntos en distinto orden en que están declarados los parámetros formales. DECLARE v_nombre alumno.nomalumno%TYPE:=’PEREZ SALVADOR, GABRIEL’; BEGIN añadiralumno(p_codrama=>’DAI’, p_dni=>912,p_nombre=>v_nombre, p_codgrupo=>’2A'); END; En una misma llamada a un procedimiento se puede mezclar el paso de parámetros nominal y posicional. En este caso los primeros argumentos han de ser pasado de forma posicional y los últimos de forma nominal. E.j.: DECLARE v_nombre alumno.nomalumno%TYPE:=’PEREZ SALVADOR, GABRIEL’; BEGIN añadiralumno(p_codrama=>’DAI’, p_dni=>912,p_nombre=>v_nombre, p_codgrupo=>’2A'); añadiralumno(913,’JIMENEZ GARCIA, RAFAEL’, p_codgrupo=>’1A’,p_codrama=>’DAI’); END; 10.2.3. Valores predeterminados de los parámetros. Los parámetros formales pueden tener valores predeterminados, por lo que si no reciben valores de un argumento en una llamada toman el valor predeterminado. Si se usa el paso de parámetros posicional todos los parámetros predeterminados para los que no hay argumento asociado han de estar al final de la lista de parámetros formales. CREATE OR REPLACE PROCEDURE añadiralumno( p_dni alumno.dni%TYPE, p_nombre alumno.nomalumno%TYPE, p_codrama alumno.codrama%TYPE DEFAULT ‘DAI’, p_codgrupo alumno.codgrupo%TYPE DEFAULT ‘1A') IS BEGIN INSERT INTO alumno values (p_dni,p_nombre,p_codrama,p_codgrupo); COMMIT; END añadiralumno; 25 Si el parámetro con valor por defecto no es el último(s) de la lista, entonces se necesita un paso de parámetros nominal. 10.3. Creación de funciones: Las funciones son subprogramas como los procedimientos y tienen las mismas características. Sólo se diferencian en que las funciones devuelven un valor y los procedimientos no devuelven valores, por tanto, la llamada a una función formará parte de una expresión mientras que la llamada a un procedimiento es una sentencia por si misma. La sintaxis para declarar una función es la siguiente: CREATE [OR REPLACE] FUNCTION nombre_funcion [(parametro1[{IN|OUT|IN OUT}] tipo_dato[{:=|DEFAULT}valor_inicial], . . . parametroN[{IN|OUT|IN OUT}] tipo_dato[{:=|DEFAULT}valor_inicial])] RETURN tipo_dato_retorno {IS|AS} [/*seccion declarativa*/] BEGIN /*seccion procedimental*/ [EXCEPTION /*seccion de excepciones*/] END [nombre_funcion]; 11. PAQUETES. Es un bloque PLSQL nominado que permite almacenar juntos una serie de objetos relacionados,tales como un conjunto de procedimientos y funciones,tipos de datos,variables,etc. que tengan que ver con una tarea concreta. Un paquete se compone de tres partes:La especificacion o cabecera y el cuerpo Cada una de estas partes se almacena por separado en el diccionario de datos. 11.1. Especificación de un paquete La especificacion o cabecera de un paquete contiene informacion acerca del contenido del paquete pero no contiene el codigo de los subprogramas,en la cabecera aparecen las especificaciones formales de procediminetos y funciones,declaraciones de variable,de cursor,de tipos de dato y de excepciones. La especificacion de un subprograma consiste en especificar el tipo:PROCEDURE O FUNCTION,el nombre del subprograma,los parámetros formales entre paréntesis en cada uno indicando el nombre,modo y tipo y en el caso de ser una funcion el tipo de valor que devuelve. SINTAXIS {PROCEDURE|FUNCTION 26 Las variables,tipos de datos de funciones definidas en la cabecera de un paquete son visibles desde cualquier otro bloque PLSQL,pues lo que la forma de disponer de variables globales es declarándolas en la cabecera de un paquete. La sintaxis para la cración de la cabecera de un paquete es la siguiente: CREATE [OR REPLACE] PACKAGE nombre_paquete {IS|AS} {Especificacion de funcion| Especificacion de procedimiento| Declaracion_variable| Definicion de bajo| Decllaracion de excepcion| Declaracion de cursor}... End [nombre_paquete] 11.2. Cuerpo de paquete El cuerpo de un paquete contiene el código de los procedimientos y funciones especificados en la cabecera de un paquete.El cuerpo de un paquete es opcional,pues si en la cabecera de un paquete no hay declaraciones formales de procedimeintos ni de funciones,es deci sólo hay declaraciones de variables,cursores y tipos de datos,entonces no es necesario que el cuerpo este presente.En el cuerpo del paquete las especificaciones de los procediminos y funciones tiene que ser la misma que en la cabecera del paquete,coincidiendo el nombre del paquete,los nombres de sus parámetros,sus tipos y sus modos . El cuerpo del paquete no puede ser compilado a menos que haya sido compilado previamente la cabecera del paquete. La sintaxis para el cuerpo de un paquete es la siguiente: CREATE [OR REPLACE]PACKAGE BODY nombre_paquete{IS|AS} {FUNCTION nombre_funcion [(parámetro1[modo]tipo[,parámetro2[modo]tipo...)]RETURN tipo_dato_retorno{IS|AS} [/*declariaciones locales*/] BEGIN /*seccion procedimental*/ [EXCEPTION /*seccio n excepciones*/] END [nombre_funcion]; PRECODURE nombre_procedimiento [(parametro1[modo] tipo [, parametro2 [modo] tipo]...)] {IS|AS} [*/declaracion procedimental*/] BEGIN /*seccion procedimental*/ [EXCEPTION /*seccion excepciones*/] END [nombre_procedimie nto]; [BEGIN /*seccion de inicialización del paquete*/] END [nombre_paquete]; 27 Ejem: (3 versiones) Construir un paquete llamado CentroPaquete que realice las siguientes tareas: 1. Procedimiento llamado AñadirCentro. 2. Función BorrarCentro que recibe un código de centro y lo borra de la tabla centro. Esta función devuelve 0 si se ha borrado el centro y –1 si el centro no existe. 3. Otra función que se llama CuantosCentrosAlta que devuelve el número de centros que se han dado de alta en la sesión. 4. Funcion CuantosCentrosBaja, que devuelve cuantos centros se han dado de baja en la sesión. 5. Función DiferenciaCentros que devuelve cuantos centros hay de diferencia desde el inicio de la sesión. 6. Procedimiento ListarDepartamentos que recibe un código de centro y visualiza los nombres de los departamentos hubicados en ese centro. 7. Procedimiento ListarDepartamentos que recibe como parámetro un nombre de centro y visualiza los nombres de los departamentos ubicados en ese centro. CREATE OR REPLACE PACKAGE CentroPaquete IS PROCEDURE AñadirCentro(p_codce dai2.centro.codce%TYPE, p_nomce dai2.centro.nomce%TYPE, p_dirce dai2.centro.dirce%TYPE); FUNCTION BorrarCentro (p_codce dai2.centro.codce%TYPE) RETURN number; FUNCTION CuantosCentrosAlta RETURN number; FUNCTION CuantosCentrosBaja RETURN number; g_numcentrosalta number:=0; g_numcentrosbaja number:=0; END CentroPaquete; CREATE OR REPLACE PACKAGE BODY CentroPaquete IS PROCEDURE AñadirCentro(p_codce dai2.centro.codce%TYPE, p_nomce dai2.centro.nomce%TYPE, p_dirce dai2.centro.dirce%TYPE) IS BEGIN INSERT INTO centro VALUES(p_codce, p_nomce, p_dirce); COMMIT; g_numcentrosalta:=g_numcentrosalta+1; END AñadirCentro; FUNCTION BorrarCentro(p_codce dai2.centro.codce%TYPE) RETURN number IS BEGIN DELETE FROM centro WHERE codce=p_codce; IF sql%NOTFOUND THEN RETURN -1; ELSE g_numcentrosbaja:=g_numcentrosbaja+1; RETURN 0; END IF; END BorrarCentro; FUNCTION CuantosCentrosAlta RETURN number IS BEGIN RETURN g_numcentrosalta; END CuantosCentrosAlta; 28 FUNCTION CuantosCentrosBaja RETURN number IS BEGIN RETURN g_numcentrosbaja; END CuantosCentrosBaja; END CentroPaquete; 11.3. Ámbito y llamada a los subprogramas de un paquete: Cualquier objeto declarado en la cabecera de un paquete es visible desde cualquier parte, es decir, para referenciar un objeto especificado en la cabecera de un paquete hay que calificarlo con el nombre del paquete. nombre_paquete.nombre_subprograma BEGIN CentroPaquete.AñadirCentro (50,’NEW CENTER’,’AVD. DEL OESTE’); END; BEGIN DBMS_OUTPUT.PUT_LINE (‘Se han dado de alta en la sesion: ‘||CentroPaquete.CuantosCentrosAlta); END; BEGIN IF CentroPaquete.BorrarCentro(60)=0 THEN DBMS_OUTPUT.PUT_LINE (‘Se ha borrado el centro 60’); ELSE DBMS_OUTPUT.PUT_LINE (‘Centro Inexistente’); END; 11.4. Incialización de un paquete. La primera vez que se llama a un paquete dentro de una sesión éste es instanciado, es decir, es leido del disco y almacenado en memoria RAM. Hay ocasiones en que la primera vez en que se hace referencia al paquete hay que inicializar algunas variables o realizar otros procesos de inicialización, para ello hay una sección de inicialización que se especifica al final del cuerpo del paquete y sólo se ejecuta la primera vez que es referenciado. CREATE OR REPLACE PACKAGE CentroPaquete IS PROCEDURE AñadirCentro(p_codce dai2.centro.codce%TYPE, p_nomce dai2.centro.nomce%TYPE, p_dirce dai2.centro.dirce%TYPE); FUNCTION BorrarCentro (p_codce dai2.centro.codce%TYPE) RETURN number; FUNCTION CuantosCentrosAlta RETURN number; FUNCTION CuantosCentrosBaja RETURN number; g_numcentrosalta number:=0; 29 g_numcentrosbaja number:=0; FUNCTION DiferenciaCentros RETURN number; g_numcentrosinicio number; END CentroPaquete; CREATE OR REPLACE PACKAGE BODY CentroPaquete IS PROCEDURE AñadirCentro(p_codce dai2.centro.codce%TYPE, p_nomce dai2.centro.nomce%TYPE, p_dirce dai2.centro.dirce%TYPE) IS BEGIN INSERT INTO centro VALUES(p_codce, p_nomce, p_dirce); COMMIT; g_numcentrosalta:=g_numcentrosalta+1; END AñadirCentro; FUNCTION BorrarCentro(p_codce dai2.centro.codce%TYPE) RETURN number IS BEGIN DELETE FROM centro WHERE codce=p_codce; IF sql%NOTFOUND THEN RETURN -1; ELSE g_numcentrosbaja:=g_numcentrosbaja+1; RETURN 0; END IF; END BorrarCentro; FUNCTION CuantosCentrosAlta RETURN number IS BEGIN RETURN g_numcentrosalta; END CuantosCentrosAlta; FUNCTION CuantosCentrosBaja RETURN number IS BEGIN RETURN g_numcentrosbaja; END CuantosCentrosBaja; FUNCTION DiferenciaCentros RETURN number IS BEGIN RETURN g_nunmcentroinicio+g_numcentrosalta-g_numcentrosbaja; END DiferenciaCentros BEGIN SELECT count(*) INTO g_numcentroinicio from centro; END CentroPaquete; 30 11.5. SOBRECARGA DE SUBPROGRAMAS DE UN PAQUETE. Dentro de un paquete pueden sobrecargarse los procedimientos y funciones, es decir, puede haber más de un procedimiento o función con el mismo nombre pero con distintos parámetros. Esta operación es muy útil en programación ya que permite aplicar una misma operación a objetos de tipos diferentes, realizando tratamentos distintos dependiendo del tipo de objeto. Tiene las siguientes restricciones: - No se pueden sobrecargar dos programas si solo difieren en el nombre o modo de sus parámetros. No se pueden sobrecargar funciones que solo difieran en el tipo de retorno. No se pueden sobrecargar dos subprogramas cuyos tipos de dato de los parámetros no difieran en la familia de tipos de datos (p.ej.: char y varchar2) CREATE OR REPLACE PACKAGE centropaquete IS ---------------------; ---------------------; ---------------------; PROCEDURE listardepartamentos(p_codce centro.codce%TYPE); PROCEDURE listardepartamentos(p_nomce centro.nomce%TYPE); END centropaquete; EX: CREATE OR REPLACE PACKAGE BODY centro paquete IS PROCEDURE listadepartamentos(p_codce codce%TYPE) IS CURSOR c_depto IS SELECT nomde FROM depto WHERE codce=p_codce; v_depto c_depto%ROWTYPE; BEGIN OPEN c_depto; FETCH c_depto v_depto; IF c_depto%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE(‘Código inexistente o no hay departamento’); ELSE WHILE c_depto%FOUND LOOP DBMS_OUTPUT.PUT_LINE(v_depto); FETCH c_depto INTO v_depto; END LOOP; END IF; CLOSE c_depto; END listadepartamentos; PROCEDURE listadepartamentos(p_nomce centro.nomce%TYPE) IS CURSOR c_depto IS SELECT nomde FROM depto, centro WHERE depto.codce=centro.codce AND 31 BEGIN nomce=p_nomce; v_depto depto.nomde%TYPE ; OPEN c_depto; FETCH c_depto v_depto; IF c_depto%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE(‘Código inexistente o no hay departamento’); ELSE WHILE c_depto%FOUND LOOP DBMS_OUTPUT.PUT_LINE(v_depto); FETCH c_depto INTO v_depto; END LOOP; END IF; CLOSE c_depto; END listadepartamentos; 12. DISPARADORES O TRIGGERS DE LA BD. Un disparador es un bloque PL/SQL nominado que se almacena en la BD y se ejecuta cuando tiene lugar un suceso disparo. Un suceso o disparo es una operación INSERT, UPDATE o DELETE sobre una tabla de la BD. Se utiliza entre otras cosas para: 1. Mantenimiento de restricciones de integridad complejas que no es posible declararlas como constraint. Por ejemplo, un alumno solo se puede matricular de asignaturas que son de la rama en la que está matriculado el alumno. 2. Auditoria de la informacion de la BD, registrando los cambios realizados en la BD y la identidad del usuario que los realiza. Los disparadores se pueden encontrar en la vista del diccionario de datos user_triggers. 12.1. CREACIÓN DE TRIGGERS. La sintaxis para crear un disparador es la siguiente: CREATE [OR REPLACE] TRIGGER nombre_disparador {BEFORE | AFTER} suceso_disparo [OF nombre_de_columnas] ON nombre tabla [FOR EACH ROW [WHEN(condicion_disparo)]] [DECLARE /*seccion declarativa*/] BEGIN /*sección procedimental*/ [EXCEPTION /*sección de excepciones*/] END[nombre_disparador]; donde suceso_disparo especifica con que acciones se dispara el trigger; si son varias van separadas por la palabra OR. BEFORE y AFTER indican que se ejecutará el disparador antes o 32 después de que se realice la operación del suceso disparo sobre la BD. La cláusula ON especifica la tabla o vista sobre la que está asociada el disparo. La cláusula OF especifica las columnas sobre las que está asociada el disparo cuando el suceso disparo es UPDATE. El disparador puede ser a nivel de fila o a nivel de sentencia. Un disparador a nivel de fila se ejecutará por cada fila afectada por la sentencia DML que provocó el disparo. Un disparador es a nivel de fila si se especifica la cláusula FOR EACH ROW. Un disparador a nivel de sentencia se ejecutara solo una vez por cada sentencia que provoca el disparo. Se declara un disparador a nivel de fila cuando se encesite acceder a los valores de las filas que están siendo procesadas por la sentencia DML que provocó el disparo. La cláusula WHEN indica que sólo se ejecutará el disparador si se cumple la condición especificada en esta cláusula. Esta cláusula sólo es válida para disparadores a nivel de fila. El cuerpo de un disparador tiene las siguientes restricciones: 1. Cuando la orden que provoca el disparo es cancelada se cancela el trabajo realizado por el disparador. Esto puede suceder, por ejemplo, al realizar un rollback de la acción que provocó el disparo. 2. No se pueden emitir sentencias de control de transacciones (commit, rollback, savepoint) dentro del código de un trigger. Características de los disparadores: 1. Si en un trigger se provoca un error, como resultado de la ejecución del código o bien planificado e introducido por el programador, entonces el suceso disparo que provocó su ejecución se aborta. ℑ 2. La orden para eliminar un disparador es DROP TRIGGER nombre_disparador. 3. Un disparador se puede alterar con la orden ALTER TRIGGER nombre_disparador {DISABLE | ENABLE}. 12.2. PSEUDOREGISTROS :OLD Y :NEW. Como un disparador se ejecuta una vez por cada fila procesada por la orden que provocó el disparo. Dentro de un disparador a nivel de fila puede accederse a los valores de la fila que está siendo procesada utilizando los pseudoregistros :OLD y :NEW. Los valores de :old y :new según la orden que provoca el disparo son: :NEW ℑ :OLD INSERT Valores de la fila despues de a insercción. No definido UPDATE Valores de la fila despues de la actualización Valores de la fila antes de la actualizacion DELETE No definido Valores originales de la fila. No se aborta si es captado dentro del trigger en la sección de excepciones. 33 Los valores de los campos :old no se pueden modificar, sólo pueden ser leídos. Los valores de los campos :new sólo se podrán modificar si es un disparador previo, es decir, está definido como BEFORE. En la condición de la cláusula WHEN del disparador se puede hacer referencia a old y new, sin utilizar los dos puntos de prefijo. Estos pseudoregistros sólo pueden utilizarse en disparadores a nivel de fila. 12.3. PREDICADOS INSERTING, UPDATING, DELETING. Un mismo disparador puede estar asociado a varios sucesos disparo. En ese caso se pueden utilizar los predicados inserting, updating y deleting para determinar cuales de los sucesos disparo provocó la ejecución del disparador. Estos predicados devuelven los valores TRUE o FALSE según sea o no el suceso que disparó el trigger. E.j.: Crear un trigger que visualice en pantalla el tipo de acción que se realiza sobre la tabla centro. >CREATE OR REPLACE TRIGGER disparadorejemplo AFTER INSERT OR DELETE OR UPDATE ON centro BEGIN IF DELETING THEN DBMS_OUTPUT.PUT_LINE(‘Se ha producido una baja’); END IF; IF INSERTING THEN DBMS_OUTPUT.PUT_LINE(‘Se ha producido una insercción’); END IF; IF UPDATING THEN DBMS_OUTPUT.PUT_LINE(‘Se ha producido una actualización’); END IF; END disparadorejemplo; 13. TRATAMIENTO DE ERRORES. El mecanismo de tratamiento de errores de ejecución en PL/SQL se implementa mediante excepcio nes y gestores de excepciones. Cuando se produce un error se genera una excepción y el control del programa pasa a la sección de excepciones del bloque, separando de esta manera la lógica del programa del tratamiento de situaciones erróneas. 13.1. DECLARACIÓN DE EXCEPCIONES. La excepciones se declaran en la sección declarativa del bloque. Se producen en la sección procedimental, y se procesan en la sección de excepciones del bloque. Existen dos tipos de excepciones: - Excepciones definidas por el usuario. Excepciones predefinidas del sistema. 34 Una excepción definida por el usuario es un error cuya definición se realiza en el programa, y define un error lógico de la semántica de los datos del sistema de información. Se declara en la sección declarativa de un bloque PL/SQL y al igual que las variables de programa tiene un tipo y un ámbito. La sistaxis para declarar una expresión es: nombre_excepción EXCEPTION; E.J.: DECLARE e_DemasiadosAlumnos EXCEPTION; v_var1 number; BEGIN ... CURSOR_ALREADY_OPEN END; e_DemasiadosAlumnos es un identificador de excepción cuyo ámbito se halla hasta el final del bloque en que ha sido definido. Las excepciones predefinidas del sistema se corresponden a errores SQL y errores ORACLE comunes. Al igual que los tipos predefinidos, como number, char, boolean, etc., los identificadores de estas excepciones están definidas en el paquete standard.sql. Algunas de la excepciones mas comunes: - INVALID_CURSOR: se genera cuando se realiza una operación ilegal sobre un cursor, como cerrar dos veces un cursor. CURSOR_ALREADY_OPEN: intentando abrir un cursor ya abierto. NO_DATA_FOUND: Se genera en dos casos: 1. Una SELECT INTO no devuelve ninguna fila. 2. Cuando se hace referencia a un elemento de array inexistente. - - TOO_MANY_ROWS: cuando una orden SELECT INTO devuelve varias filas. INVALID_NUMBER: cuando falla un intento de conversión de una cadena de caracteres a un numérico en una sentencia SQL. VALUE_ERROR: se genera cuando se produce un error aritmético o de conversión o de truncamiento o de restricción en una orden procedimental o en una sentencia SELECT INTO al asignar valor a la variable del programa. DUP_VAL_ON_INDEX: es ante una violación de una restricción de unicidad. 13.2. GENERACIÓN DE EXCEPCIONES. Cuando se produce un eror asociado a una excepción se genera la excepción. Las excepciones definidas por el usuario se generan de forma explícita, mediante la sentencia RAISE. La sintaxis es: RAISE nombre_excepcion;. Las excepciones predefinidas se generan de forma implícita cuando se produce el error ORACLE asociado,, aunque también pueden ser generadas de manera explícita mediante la sentencia RAISE si así se desea. Cuando se genera una excepción el control se pasa inmediatamente a la sección de excepciones del bloque. Si esta excepcion no está definida o no se contempla esa excepción se propaga a la sección de excepciones del bloque de nivel superior. 35 Una vez que se pasa el control al gestor de excepciones no hay forma de volver a la seción procedimental. 13.3. TRATAMIENTO DE EXCEPCIONES. Cuando se genera un error el control del programa pasa a la sección de excepciones del bloque, que está compuesta por gestores para las distintas excepciones. Un gestor de excepción contiene el código que se ejecutará cuando ocurra el error asociado a la excepción. La sintaxis de la sección de excepciones es: EXCEPTION WHEN nombre_excepción [,nombre_excepción]... THEN secuencia_de_órdenes WHEN nombre:excepción[,nombre_excepción]... THEN secuencia_de_órdenes [WHEN OTHERS THEN secuencia_de_órdenes;] END[nombre_bloque]; La cláusula WHEN identifica la excepción correspondiente a cada gestor. Un mismo gestor se puede utilizar para más de una excepción separando en la cláusula WHEN los nombres de excepciones con comas. El gestor OTHERS se ejecutará para todas las excepciones generadas para las que no se haya declarado previamente un gestor. Es una buena práctica de programación definir un gestor OTHERS en el bloque de mayor nivel superior para que no quede ningún error sin tratar. Las funciones predefinidas SQLCODE y SQLERRM permiten determinar cual es el error producido dentro del gestor OTHERS. SQLCODE devuelve el código del error producido. SQLERRM devuelve el mensaje de error del sistema asociado al error, con una longitud máxima de 512 caracteres. Estas dos funciones, si se invocan sin argumento, devuelven los valores del error producido; sin embargo, si se les pasa parámetros devuelven el mensaje asociado al error que pasamos como parámetro. Los códigos de error son: - ℑ 0: indica ejecución normal. 100: está asociado a la excepción NO DATA FOUND. RESTO DE ERRORES: son todos negativos. ℑ El error –1403 y el error 100 están asociados al mismo error. 36 APÉNDICES 37 38 1. Teniendo en cueta el valor de las variables viusualizar los sig mensajes, sila edad es <4 visualizar bebe si es menor de 12 niño, si es menor de 14 visualizar v, si es menor de 18 adolescente, si esta entre 18 y 65 adulto activo, si es mayor de 65 adulto jubilado. 1 declare 2 v_edad number(2):=7; 3 v_mensaje varchar2(30); 4 BEGIN 5 IF v_edad < 4 then 6 v_mensaje:='BEBE'; 7 ELSIF v_edad <12 then 8 v_mensaje:='NIÑO'; 9 ELSIF v_edad <14 then 10 v_mensaje:='PUBERTAD'; 11 ELSIF v_edad <18 then 12 v_mensaje:='ADOLESCENTE'; 13 ELSIF v_edad <65 then 14 v_mensaje:='ADULTO'; 15 ELSE v_mensaje:='MAYOR'; 16 END IF; 17 DBMS_OUTPUT.PUT_LINE (v_mensaje); 18* END; 2. El mismo de antes xo con while 1 DECLARE 2 v_acumulador number(2); 3 LIMITE constant number(2):=51; 4 BEGIN 5 v_acumulador:=1; 6 WHILE v_acumulador<LIMITE LOOP 7 INSERT INTO TEMP VALUES(v_acumulador); 8 v_acumulador:=v_acumulador+1; 9 END LOOP; 10* END; 3. Escribir un bloque PL/SQL anónimo que obtenga los 20 primeros número primos (1 es primo). DECLARE cte_limite constant number(2):=20; v_numero number(2):=0; v_divisor number(2); v_contador number(2):=0; v_primo boole an; BEGIN WHILE v_contador<cte_limite LOOP v_numero:=v_numero+1; 39 v_divisor:=v_numero-1; v_primo:=TRUE; WHILE v_divisor>1 LOOP IF mod(v_numero,v_divisor)=0 THEN v_primo:=FALSE; EXIT; --optimizar procesamiento END IF; v_divisor:=v_divisor-1; END LOOP; IF v_primo=TRUE THEN DBMS_OUTPUT.PUT_LINE(v_numero); v_contador:=v_contador+1; END IF; END LOOP; END; 4. Escribir un bloque PL/SQL anónimo q pase el contenido de una cadena de caracteres d euna variable alfanumerica a una segunda variable alfanumerica invirtiendo la cadena y visualice ambas variables. Para dar valor a la primera cadena se utilizara una variable de sustitución (&) Con FOR: DECLARE v_cadena varchar2(40) v_reves varchar2(40) v_longitud number; BEGIN v_longitud:=LENGTH(v_cadena); FOR v_contador IN REVERSE 1..v_longitud LOOP v_reves:=v_reves||SUBSTR(v_cadena,v_contador,1); END LOOP; DBMS_OUTPUT.PUT_LINE ('v_cadena: '|| v_cadena); DBMS_OUTPUT.PUT_LINE ('v_reves: '|| v_reves); END; Con WHILE: DECLARE v_cadena varchar2(40):='&cadena'; v_reves varchar2(40); v_indice number; BEGIN v_indice:=LENGTH(v_cadena); WHILE v_indice>=1 LOOP v_reves:=v_reves||SUBSTR(v_cadena,v_indice,1); v_indice:=v_indice-1; 40 END LOOP; DBMS_OUTPUT.PUT_LINE ('v_cadena: '|| v_cadena); DBMS_OUTPUT.PUT_LINE ('v_reves: '|| v_reves); END; 5. Escribir un bloque anónimo que visualice los treinta primeros numeros de la SERIE DE FIBONALLI (Base: los 2 primeros son el 0 y el 1 y cada numero se obtiene a partir de la suma de los dos anteriores numeros: ejem: 0,1,1,2,3,5,8). Con FOR: DECLARE v_siguiente number; v_penultimo number:=0; v_ultimo number:=1; BEGIN DBMS_OUTPUT.PUT_LINE (0); DBMS_OUTPUT.PUT_LINE (1); for v_i in 1..28 loop v_siguiente:=v_penultimo+v_ultimo; DBMS_OUTPUT.PUT_LINE (v_siguiente); v_penultimo:=v_ultimo; v_ultimo:=v_siguiente; end loop; end; Con WHILE: DECLARE v_penultimo number:=0; v_ultimo number:=1; v_siguiente number; v_i number:=1; BEGIN DBMS_OUTPUT.PUT_LINE(v_penultimo); DBMS_OUTPUT.PUT_LINE(v_ultimo); while v_i<=28 loop v_siguiente:=v_penultimo+v_ultimo; DBMS_OUTPUT.PUT_LINE(v_siguiente); v_penultimo:=v_ultimo; v_ultimo:=v_siguiente; v_i:=v_i+1; end loop; end; 41 6. Dada una cadena de caracteres a la q se le da valor mediante una variable de sustitución determinar si es un palíndromo. 1 DECLARE 2 v_cadena varchar2(50):='&cadena'; 3 v_f number; 4 v_comp boolean; 5 BEGIN 6 v_f:=length(v_cadena); 7 for v_i in 1..v_f/2 loop 8 v_comp:=TRUE; 9 if substr(v_cadena,v_i,1)<>substr(v_cadena,v_f,1) THEN 10 v_comp:=FALSE; 11 EXIT; 12 END IF; 13 v_f:=v_f-1; 14 end loop; 15 if v_comp=TRUE then 16 dbms_output.put_line (v_cadena || ' si es un palindromo'); 17 else 18 dbms_output.put_line (v_cadena || ' no es un palindromo'); 19 end if; 20* end; Otra manera: DECLARE v_cadena varchar2(40):='&cadena'; v_reves varchar2(40); v_longitud number; BEGIN v_longitud:=LENGTH(v_cadena); FOR v_contador IN REVERSE 1..v_longitud LOOP v_reves:=v_reves||SUBSTR(v_cadena,v_contador,1); END LOOP; if v_reves=v_cadena then DBMS_OUTPUT.PUT_LINE (v_cadena || ' Es palindromo'); else DBMS_OUTPUT.PUT_LINE (v_cadena || ' No es palidromo'); end if; end; 42 7. Obtener el factorial de los numeros comprendidos entre el 1 y el 20. DECLARE v_resul integer; CTE_LIMITE constant number(2):=20; BEGIN for v_i in 1..CTE_LIMITE loop v_resul:=1; for v_fac in 1..v_i loop v_resul:=v_resul*v_fac; end loop; dbms_output.put_line ('El factorial de ' || v_i || ' es ' || v_resul); end loop; end; 8. Obtener el nombre y salario de un empleado cuyo codigo se obtiene mediante una variable de sustitución. DECLARE v_codigo emple.codem%TYPE:=&codigo; v_nombre emple.nomem%TYPE; v_salario emple.salar%TYPE; BEGIN SELECT nomem,salar INTO v_nombre, v_salario FROM emple WHERE codem=v_codigo; DBMS_OUTPUT.PUT_LINE (v_nombre || ' ' || v_salario); END; 9. Visualizar todos los datos de la asignatura CASE. DECLARE v_nombre emple.nomem%TYPE; v_codde emple.codde%TYPE:=&CODIGO; BEGIN SELECT NOMEM INTO v_nombre from emple where codde=some(select codde from depto where codde=v_codde) AND salar>=some(select salar from emple); DBMS_OUTPUT.PUT_LINE(v_nombre); end; DECLARE v_asignatura asigna%ROWTYPE; BEGIN SELECT * 43 INTO v_asignatura FROM asigna WHERE codasigna=’CASE’; DBMS_OUTPUT.PUT_LINE (v_asignatura.codasigna|| ' ' || v_asignatura.denasigna|| ' ' || v_asignatura.codrama|| ' ' || v_asignatura.curso|| ' ' || v_asignatura.coddepar); END; 10. Obtener el nombre del empleado que tenga el sueldo máximo de aquel departamento cuyo código se introduce mediante una variable de sustitución DECLARE v_nombre emple.nomem%TYPE; v_codde emple.codem%TYPE:=&CODIGO; v_nomde depto.nomde%TYPE; BEGIN SELECT NOMEM, nomde INTO v_nombre,v_nomde from emple, depto where emple.codde=depto.codde and emple.codde=v_codde and salar+nvl(comis,0)>=all(select salar+nvl(comis,0) from emple where codde=v_codde); dbms_output.put_line (v_nombre || ' ' || v_nomde); end; 10. Cursor para obtener todos los datos de los alumnos del DAI DECLARE CURSOR c_alumno_DAI IS SELECT * FROM alumno WHERE codrama=’DAI’; v_alumno alumno%ROWTYPE; BEGIN OPEN c_alumno_DAI; FETCH c_alumno_DAI INTO v_alumno; WHILE c_alumno_DAI%FOUND LOOP DBMS_OUPUT.PUT_LINE (v_alumno.dni || ‘ ‘ || v_alumno.nomalumno || ‘ ‘ || v_alumno.codrama || ‘ ‘ || v_alumno.codgrupo); FETCH c_alumno DAI INTO v_alumno; END LOOP; CLOSE c_alumno_DAI; 11. Cursor para obtener el DNI y el nombre de todos los alumnos del DAI con variable de programa DECLARE v_codrama rama.codrama%TYPE; CURSOR c_alumno_codrama IS SELECT dni,nomalumno FROM alumno WHERE codrama:=v_codrama; 44 v_dni alumno.dni%TYPE; v_nombre alumno.nomalumno%TYPE; BEGIN v_codrama:=’DAI’; OPEN c_alumno_codrama; FETCH c_alumno_codrama INTO v_dni, v_nombre; WHILE c_alumno_codrama%FOUND LOOP DBMS_OUPUT.PUT_LINE (v_dni || ‘ ‘ || v_nombre); FETCH c_alumno_codrama DAI INTO v_dni, v_nombre; END LOOP; CLOSE c_alumno_codrama; 12. Obtener los nombres de los empleados y los nombres de los departamentos en que trabajan de los empleados que trabajan en la calle alcala. Presentarlos ordenados por departamentos y dentro de departamentos por orden alfabetico de nombre. DECLARE CURSOR c_depto_alcala IS SELECT nomde, nomem from depto,emple where emple.codde=depto.codde and codce=some (select codce from centro where dirce like 'C/ALCALA%'); v_nomde depto.nomde%TYPE; v_nomem emple.nomem%TYPE; BEGIN OPEN c_depto_alcala; FETCH c_depto_alcala INTO v_nomde,v_nomem; while c_depto_alcala%FOUND LOOP DBMS_OUTPUT.PUT_LINE (v_nomde || ' ' || v_nomem); FETCH c_depto_alcala INTO v_nomde,v_nomem; end loop; close c_depto_alcala; end; 13. Obtener los nombres de los empleados y los nombres de los departamentos en que trabajan de los empleados que trabajan en la calle alcala y que cobren más que su jefe. Presentarlos ordenados por departamentos y dentro de departamentos por orden alfabetico de nombre. DECLARE CURSOR c_depto_alcala IS SELECT nomde, nomem from depto,emple where emple.codde=depto.codde and codce=some (select codce from centro where dirce like 'C/ALCALA%') and 45 salar>(select salar from emple where codem=depto.codjefe) order by nomde, nomem; v_nomde depto.nomde%TYPE; v_nomem emple.nomem%TYPE; BEGIN OPEN c_depto_alcala; FETCH c_depto_alcala INTO v_nomde,v_nomem; while c_depto_alcala%FOUND LOOP DBMS_OUTPUT.PUT_LINE (v_nomde || ' ' || v_nomem); FETCH c_depto_alcala INTO v_nomde,v_nomem; end loop; close c_depto_alcala; end; 14. Desarrollar un algoritmo que muestre los nombres de los departamentos de la empresa y cuantos empleados trabajan en cada uno de los departamentos. DECLARE CURSOR c_nomde IS SELECT NOMDE,COUNT(*) FROM DEPTO,EMPLE WHERE DEPTO.CODDE=EMPLE.CODDE GROUP BY NOMDE; V_NUMEROEMPLE NUMBER; V_NOMDE DEPTO.NOMDE%TYPE; BEGIN OPEN c_nomde; FETCH c_nomde into V_NOMDE,V_NUMEROEMPLE; WHILE C_NOMDE%FOUND LOOP DBMS_OUTPUT.PUT_LINE(V_NUMEROEMPLE||V_NOMDE); FETCH c_nomde into v_nomde,v_numeroemple; end loop; CLOSE c_nomde; end; 15. Dado un codigo de empleado cuyo valor se obtiene mediante una variable de sustitución determinar si es jefe en funciones, en cuyo caso se visualizará un mensaje indicando que si es jefe y se retirará la comisión a todos los empleados del departamento del que es jefe y se visualizará un mensaje indicando a cuantos empleados se ha retirado la comisión, en el caso contrario se pondrá un mensaje de que no se ha retirado. DECLARE v_codigo emple.codem%TYPE:=&kike; CURSOR c_jefe IS SELECT CODJEFE FROM DEPTO WHERE TIDIR='F'; v_emple emple.codem%TYPE; 46 v_contador number; v_valor boolean; BEGIN OPEN c_jefe; FETCH c_jefe INTO v_emple; WHILE c_jefe%FOUND LOOP IF v_codigo=v_emple then v_valor:=TRUE; EXIT; ELSE v_valor:=FALSE; END IF; FETCH c_jefe INTO v_emple; END LOOP; if v_valor=TRUE then SELECT COUNT(*) into v_contador FROM EMPLE WHERE CODDE=SOME(select codde from depto WHERE codjefe=v_codigo and tidir='F') AND COMIS IS NOT NULL; UPDATE emple SET comis=NULL WHERE codde=SOME(select codde from depto WHERE codjefe=v_codigo and tidir='F'); DBMS_OUTPUT.PUT_LINE('ESTE EMPLEADO ES JEFE EN FUNCIONES.'||'SE HAN ACTUALIZADO '|| v_contador); else DBMS_OUTPUT.PUT_LINE('NO SE HA RETIRADO'); end if; CLOSE c_jefe; end; DECLARE v_codigo emple.codem%TYPE:=&codigo; CURSOR c_depto IS select codde from depto where codjefe=v_codigo AND tidir='F'; v_depto depto.codde%TYPE; v_cuantos number; v_cuantos_total number:=0; BEGIN OPEN c_depto; FETCH c_depto INTO v_depto; IF c_depto%FOUND THEN 47 DBMS_OUTPUT.PUT_LINE ('El emp leado es jefe de departamento'); WHILE c_depto%FOUND LOOP SELECT count(*) INTO v_cuantos from emple where codde=v_depto and comis is not null; update emple set comis=NULL where codde=v_depto and comis is not null; v_cuantos_total:=v_cuantos_total+v_cuantos; FETCH c_depto INTO v_depto; END LOOP; DBMS_OUTPUT.PUT_LINE ('Se ha retirado la comision a '||v_cuantos||' empleados'); ELSE DBMS_OUTPUT.PUT_LINE ('EL empleado no es jefe en funciones'); END IF; CLOSE c_depto; END; 16. Obtener el nombre y salario de los 5 empleados que tienen los salarios más altos de la empresa. DECLARE CURSOR c_salario is select nomem,salar from emple order by salar desc; v_nomem c_salario%rowtype; BEGIN open c_salario; fetch c_salario into v_nomem; FOR i IN 1..5 LOOP dbms_output.put_line(v_nomem.nomem||' '||v_nomem.salar); fetch c_salario into v_nomem; END LOOP; close c_salario; END; 17. Obtener los nombres de los empleados que cobren los 5 salarios más altos de la empresa. DECLARE CURSOR c_emple IS SELECT nomem, salar FROM emple ORDER BY salar DESC; v_emple c_emple%rowtype; v_contador number:=0; v_salar_ant emple.salar%type; BEGIN OPEN c_emple; FETCH c_emple INTO v_emple; WHILE v_contador<5 and c_emple%FOUND LOOP v_salar_ant:=v_emple.salar; WHILE v_emple.salar=v_salar_ant and c_emple%FOUND LOOP DBMS_OUTPUT.PUT_LINE (v_emple.nomem||’ ‘||v_emple.salar); 48 FETCH c_emple INTO v_emple; END LOOP; v_contador:=v_contador+1; END LOOP; CLOSE c_emple; END; 18. Obtener los nombres y salario de los dos empleados que menos ganen de cada categoría laboral, especificando el nombre de la categoría laboral. DECLARE CURSOR c_emple IS SELECT nomcat, nomem, salar FROM emple e, catego c WHERE c.codcat=e.codcat ORDER BY nomcat, salar; v_emple c_emple%rowtype; v_contador number; v_nomcat_ant catego.nomcat%type; BEGIN OPEN c_emple; FETCH c_emple INTO v_emple; WHILE c_emple%FOUND LOOP DBMS_OUTPUT.PUT_LINE (v_emple.nomcat); v_nomcat_ant:=v_emple.nomcat; v_contador:=0; WHILE v_emple.nomcat=v_nomcat_ant and c_emple%FOUND and v_contador<2 LOOP DBMS_OUTPUT.PUT_LINE(v_emple.nomem||' '||v_emple.salar); v_contador:=v_contador+1; FETCH c_emple into v_emple; END LOOP; WHILE v_emple.nomcat=v_nomcat_ant and c_emple%FOUND LOOP FETCH c_emple INTO v_emple; END LOOP; END LOOP; CLOSE c_emple; END; 49 19. Realizar un programa que visualice de cada categoría laboral los empleados con los dos salarios más bajos. (dos salarios mñas bajos, no dos empleados...) DECLARE CURSOR c_emple IS SELECT nomcat, nomem, salar FROM emple e, catego c WHERE c.codcat=e.codcat ORDER BY nomcat, salar; v_emple c_emple%rowtype; v_contador number; v_nomcat_ant catego.nomcat%type; v_salar_ant emple.salar%type; BEGIN OPEN c_emple; FETCH c_emple INTO v_emple; WHILE c_emp le%found LOOP DBMS_OUTPUT.PUT_LINE (v_emple.nomcat); v_nomcat_ant:=v_emple.nomcat; v_contador:=0; v_salar_ant:=v_emple.salar; WHILE v_emple.nomcat=v_nomcat_ant and c_emple%FOUND and v_contador<2 LOOP WHILE v_emple.salar=v_salar_ant and v_emple.nomcat=v_nomcat_ant and c_emple%FOUND LOOP DBMS_OUTPUT.PUT_LINE(v_emple.nomem||' '||v_emple.salar); FETCH c_emple INTO v_emple; END LOOP; v_contador:=v_contador+1; v_salar_ant:=v_salar.emple; END LOOP; WHILE v_emple.nomcat=v_nomcat_ant and c_emple%FOUND LOOP FETCH c_emple INTO v_emple; END LOOP; END LOOP; CLOSE c_emple; END; 20. Aquellos alumnos matriculados en la asignatura GRAF que tengan en la primera evaluación entre un 4’5 y un 4’9 y han obtenido en la segunda evaluación 7 o más de 7 asignarles en la primera evaluación un 5, visualizando el mensaje: “El alumno con DNI: xxxxxxxxx-x se le ha asignado 5 en la primera evaluación al tener xx en la primera evaluación y xx en la segunda evaluación”. DECLARE CURSOR c_alumno IS select dni, nota1, nota2 from estudia where codasigna='GRAF' FOR UPDATE OF nota1; v_alumno c_alumno%rowtype; v_nota_ant estudia.nota1%type; 50 BEGIN END; OPEN c_alumno; FETCH c_alumno INTO v_alumno; WHILE c_alumno%found LOOP IF v_alumno.nota1>=3.5 and v_alumno.nota1<=4.9 and v_alumno.nota2>=5 THEN v_nota_ant:=v_alumno.nota1; UPDATE estudia SET nota1=5; DBMS_OUTPUT.PUT_LINE ('El alumno con dni '||v_alumno.dni||' se le ha asignado un 5 en la primera evaluación al tener un '||v_nota_ant||' en la primera evaluación y un '||v_alumno.nota2||' en la segunda'); END IF; FETCH c_alumno INTO v_alumno; END LOOP; CLOSE c_alumno; 21. Los salarios de los empleados van a ser actualizados con los siguientes criterios: - A los empleados sin comisión se les incrementa el salario un 5%. A los empleados con comisión superior a 500€ se incrementa el salario en un 4% y la comisión en un 3%. A los empleados con comisión inferior o igual a 500€ se les incrementa el salario en un 5% y la comisión un 4%. Además, previamente se ha declarado una tabla llamada ‘empleados’ con los siguientes campos: - CODEM NOMEM SALARANT SALARNUE COMISANT COMISNUE y se ha de insertar una fila por cada empleado de la tabla emple con los anteriores y nuevos salarios y comisiones. DECLARE CURSOR c_actualiza IS SELECT codem, nomem, salar, comis FROM emple for update of salar, comis; v_actualiza c_actualiza%rowtype; BEGIN OPEN c_actualiza; FETCH c_actualiza INTO v_actualiza; WHILE c_actualiza%found LOOP IF v_actualiza.comis is null THEN INSERT INTO empleados VALUES (v_actualiza.codem,v_actualiza.nomem, v_actualiza.salar,v_actualiza.salar*1.05,v_actualiza.comis, v_actualiza.comis); 51 UPDATE emple SET salar=salar*1.05; END IF; IF_actualiza.comis>500 THEN INSERT INTO empleados values (v_actualiza.codem,v_actualiza.nomem, v_actualiza.salar,v_actualiza.salar*1.04,v_actualiza.comis, v_actualiza.comis*1.05); UPDATE emple SET salar=salar*1.04 ,comis=comis*1.05; END IF; IF v_actualiza.comis<=500 THEN INSERT INTO empleados values (v_actualiza.codem,v_actualiza.nomem, v_actualiza.salar, v_actualiza.salar*1.05,v_actualiza.comis, v_actualiza.comis*1.04); UPDATE emple set salar=salar*1.05 ,comis=comis*1.04; END IF; FETCH c_actualiza INTO v_actualiza; END LOOP; CLOSE c_actualiza; END; 22. Actualizar la nota final de los alumno de DAI, además dar de baja en la asignatura a aquello alumnos que tengan las tres evaluaciones suspensas en la asignatura, almacenando en una tabla llamada “alumnosbaja” el DNI del alumno, nombre del alumno, codrama que estudia, curso en que esta matriculado y el codigo de asignatura en el que se ha dado de baja. 23. A los empleados incrementarles el salario en un 5 por % si cobran comision y en un 9 % si no cobran comision,de cada departamento. (copiado en papel) 24. Escribir una función en la que se pasa un código de asignatura y se visualiza cuantos alumnos hay matriculados en esa asignatura y la nota final más alta. v.1.0.: La visualización se realizará dentro del procedimiento. v.2.0: La visualización se realizará en la llamada al procedimiento. CREATE OR REPLACE PROCEDURE alumnos( p_codasigna asigna.codasigna%TYPE) IS v_codasigna asigna.codasigna%TYPE; v_contador number; v_nota estudia.notaf%TYPE; BEGIN SELECT count(*),max(NOTAF) notas into v_contador, v_nota from asigna a, estudia e WHERE e.codasigna=p_codasigna AND e.codasigna=a.codasigna GROUP BY denasigna; DBMS_OUTPUT.PUT_LINE(v_contador||' '||v_nota); END alumnos; v.1.0 52 CREATE OR REPLACE PROCEDURE alumnos( p_codasigna asigna.codasigna%TYPE) IS v.1.1 CURSOR c_alumnos IS SELECT count(*) contador,max(NOTAF) notas from asigna a, estudia e WHERE e.codasigna=p_codasigna AND e.codasigna=a.codasigna GROUP BY denasigna; v_alumnos c_alumnos%ROWTYPE; BEGIN OPEN c_alumnos; FETCH c_alumnos INTO v_alumnos; IF c_alumnos%FOUND THEN DBMS_OUTPUT.PUT_LINE(v_alumnos.contador||' '||v_alumnos.notas); ELSE DBMS_OUTPUT.PUT_LINE(‘Esa asignatura no se imparte en este centro’); END IF; END alumnos; CREATE OR REPLACE PROCEDURE alumnos( v.1.2 p_codasigna asigna.codasigna%TYPE) IS CURSOR c_alumnos IS SELECT count(*) contador,max(NOTAF) notas from asigna a, estudia e WHERE e.codasigna=p_codasigna AND e.codasigna=a.codasigna GROUP BY denasigna; v_alumnos c_alumnos%ROWTYPE; BEGIN OPEN c_alumnos; FETCH c_alumnos INTO v_alumnos; IF c_alumnos%FOUND THEN DBMS_OUTPUT.PUT_LINE(v_alumnos.contador||' '||v_alumnos.notas); ELSE DBMS_OUTPUT.PUT_LINE('Esa asignatura no se imparte en este centro'); END IF; CLOSE c_asigna; CLOSE c_alumnos; END alumnos; CREATE OR REPLACE PROCEDURE alumnos2( p_codasigna in asigna.codasigna%TYPE, p_denasigna out asigna.denasigna%TYPE, p_contador out number, p_nota out estudia.notaf%TYPE) IS BEGIN SELECT denasigna, count(*), max(notaf) INTO v.2.0 53 END alumnos2; p_denasigna, p_contador, p_nota FROM estudia e, asigna a WHERE a.codasigna=e.codasigna AND e.codasigna=p_codasigna GROUP BY denasigna; DECLARE v_denasigna asigna.denasigna%TYPE; v_contador number; v_nota estudia.notaf%TYPE; BEGIN alumnos2('FOL',v_denasigna,v_contador,v_nota); DBMS_OUTPUT.PUT_LINE(v_denasigna||' '||v_contador||' '|| v_nota); END; CREATE OR REPLACE PROCEDURE alumnos2( p_codasigna IN asigna.codasigna%TYPE, p_denasigna OUT asigna.denasigna%TYPE, p_cuantos OUT number, p_nota OUT estudia.notaf%TYPE) IS v.2.1 CURSOR c_asigna IS SELECT denasigna FROM asigna WHERE codasig na=p_codasigna; v_denasigna asigna.denasigna%TYPE; CURSOR c_estudia IS SELECT count(*) cuantos, max(notaf) media FROM estudia WHERE codasigna=p_codasigna; v_estudia c_estudia%ROWTYPE; BEGIN OPEN c_asigna; FETCH c_asigna INTO v_denasigna; IF c_asigna%NOTFOUND THEN p_denasigna:=NULL; ELSE p_denasigna:=v_denasigna; OPEN c_estudia; FETCH c_estudia INTO v_estudia; p_cuantos:=v_estudia.cuantos; p_nota:=v_estudia.media; CLOSE c_estudia; END IF; CLOSE c_asigna; END alumnos2; 54 DECLARE v_codasigna asigna.codasigna%TYPE:=’&Código’; v_denasigna asigna.denasigna%TYPE; v_cuantos number; v_nota estudia.notaf%TYPE; BEGIN alumnos2(v_codasigna,v_denasigna,v_cuantos,v_nota); IF v_denasigna IS NULL THEN DBMS_OUTPUT.PUT_LINE(‘Código de asig natura inexistente’); ELSE DBMS_OUTPUT.PUT_LINE(v_denasigna||’ ‘||v_cuantos||’ ‘||v_nota); END IF; END; 25. Realizar un procedimiento que introduciendo el parametro DNI, visualize el nombre del alumno y el nombre de las asignaturas q cursa. CREATE OR REPLACE PROCEDURE alumno_asignaturas (p_dni in alumno.dni%TYPE) IS CURSOR c_alumno IS SELECT nomalumno FROM alumno WHERE dni=p_dni; v_nomalumno alumno.nomalumno%TYPE; CURSOR c_asigna IS SELECT denasigna from estudia, asigna WHERE dni=p_dni AND estudia.codasigna=asigna.codasigna; v_denasigna asigna.denasigna%TYPE; BEGIN OPEN c_alumno; FETCH c_alumno INTO v_nomalumno; IF c_alumno%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE('DNI inexistente'); ELSE DBMS_OUTPUT.PUT_LINE (v_nomalumno); OPEN c_asigna; FETCH c_asigna INTO v_denasigna; IF c_asigna%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE('El alumno con DNI '||p_dni||' no esta matriculado de ninguna asignatura’); ELSE WHILE c_asigna%FOUND LOOP DBMS_OUTPUT.PUT_LINE(v_denasigna); FETCH c_asigna INTO v_denasigna; END LOOP; END IF; 55 CLOSE c_asigna; END IF; CLOSE c_alumno; END alumno_asignaturas; 26. Escribir una función que reciba como parámetros u código de empleado y devuelva el nombre de departamento o NULL si el emple no existe. CREATE OR REPLACE FUNCTION empleado_departamento(p_codem emple.codem%TYPE) RETURN depto.nomde%TYPE IS CURSOR c_emple IS SELECT nomde FROM depto WHERE codde=(select codde from emple where codem=p_codem); v_nomde depto.nomde%TYPE ; BEGIN OPEN c_emple ; FETCH c_emple INTO v_nomde; IF c_emple%NOTFOUND THEN RETURN NULL; ELSE RETURN v_nomde; END IF; END empleado_departamento; DECLARE v_nombre depto.nomde%TYPE; BEGIN v_nombre :=empleado_departamento(280) ; IF v_nombre IS NULL THEN DBMS_OUTPUT.PUT_LINE(‘Código de empleado inexistente’); ELSE DBMS_OUTPUT.PUT_LINE(v_nombre); END IF; END; 27. Escribir una función en la que se pide un DNI de alumno y un código de asignatura y devuelve la nota media o bien los siguientes códigos de incidencia: -1 alumno inexistente. -2 asignatura inexistente. -3 alumno no matriculado en esa asignatura. CREATE OR REPLACE FUNCTION nota_media(p_dni alumno.dni%TYPE, p_codasigna asigna.codasigna%TYPE) RETURN estudia.notaf%TYPE IS CURSOR c_alum IS SELECT dni FROM alumno WHERE dni=p_dni; v_alum c_alum%ROWTYPE; 56 CURSOR c_asigna IS SELECT codasigna FROM asigna WHERE codasigna=p_codasigna; v_asigna c_asigna%ROWTYPE; CURSOR c_estudia IS SELECT notaf FROM estudia WHERE dni=p_dni AND codasigna=p_codasigna; v_nota estudia.notaf%TYPE; BEGIN OPEN c_alum; FETCH c_alum INTO v_alum; IF c_alum%NOTFOUND THEN RETURN –1; ELSE OPEN c_asigna; FETCH c_asigna INTO v_asigna; IF c_asigna%NOTFOUND THEN RETURN –2; END IF; END IF; OPEN c_estudia; FETCH c_estudia INTO v_nota; IF c_estudia%NOTFOUND THEN RETURN –3; ELSE RETURN v_nota; END IF; END nota_media; DECLARE v_notamedia estudia.notaf%TYPE; BEGIN v_notamedia:=nota_media(280,’CASE’); IF v_notamedia= -1 THEN DBMS_OUTPUT.PUT_LINE(‘DNI de alumno inexistente’); ELSIF v_notamedia=-2 THEN DBMS_OUTPUT.PUT_LINE(‘Código de asignatura inexistente’); ELSIF v_notamedia=-3 THEN DBMS_OUTPUT.PUT_LINE(‘El alumno no está matriculado en la asignatura’); ELSE DBMS_OUTPUT.PUT_LINE(‘La nota media es ‘||v_notamedia); END IF; END; 57 28. Escribir un procedimiento que suba el salario de un empleado cuyo código se pasa como parámetro si su salario es menos que el salario medio de su categoría profesional. La subida será del 50% de la diferencia entre el salario del empleado y la media de su categoría laboral. CREATE OR REPLACE PROCEDURE subida (p_codem emple.codem%TYPE) IS CURSOR c_emple IS SELECT codcat, codem, salar FROM emple WHERE codem=p_codem; v_emple c_emple%ROWTYPE; CURSOR c_catego IS SELECT codcat,avg(salar) media FROM emple where codcat=v_emple.codcat; v_catego c_catego%ROWTYPE; BEGIN OPEN c_emple; FETCH c_emple INTO v_emple; IF c_emple%NOTFOUND THEN DBMS_OUTPUT.PUT_LINE(‘Codem inexistente’); ELSE OPEN c_catego; FETCH c_catego INTO v_catego; WHILE v_catego.codcat<>v_emple.codcat LOOP FETCH c_catego INTO v_catego; END LOOP; IF v_emple.salar<v_catego.media THEN UPDATE emple SET salar=(v_catego.media-v_emple.salar)*0.5+v_emple.salar WHERE codem=p_codem; DBMS_OUTPUT.PUT_LINE(v_emple.codem||’ ‘||v_emple.salar||’ ‘||v_catego.media) ; ELSE DBMS_OUTPUT.PUT_LINE(‘Imposible actualizar’); END IF; END IF; END subida; 29. Realizar un procedimiento llamado calculo_trienios que calcule los trienios de un determinado empleado, el programa ha de tener el siguiente diseño. El procedimiento recibe como parámetro un código de empleado, los trienios los debe calcular una función llamada número_ trienios y el procedimiento visulizara el siguiente mensaje: “El empleado nombreempleado tiene XX trienios”. CREATE OR REPLACE PROCEDURE calculo_trienios (p_codem emple.codem%TYPE) IS CURSOR c_emple IS SELECT nomem, fecing FROM emple WHERE codem=p_codem; v_emple c_emple%ROWTYPE; v_trienio number(2); BEGIN OPEN c_emple; FETCH c_emple INTO v_emple; IF c_emple%NOTFOUND THEN 58 DBMS_OUTPUT.PUT_LINE(‘Código de empleado inexistente’); ELSE v_trienio:=numero_trienios(v_emple.fecing); DBMS_OUTPUT.PUT_LINE(‘El empleado ‘||v_emple.nomem||’ lleva ‘||v_trienio||’ trienios en la empresa’); END IF; END calculo_trienios; CREATE OR REPLACE FUNCTION numero_trienios(p_fecha date) RETURN number AS v_años number; BEGIN RETURN TRUNC(((sysdate-p_fecha)/365)/3); END numero_trienios; 30. Escribir un procedimiento que suba el salario a todos los empleados que ganen menos que el salario medio de su categoría profesional. la subida será del 50% de la diferencia entre el salario del empleado y la media de su categoría laboral. CREATE OR REPLACE PROCEDURE subidon IS CURSOR c_catego IS SELECT codcat, avg(salar) media FROM emple GROUP BY codcat; v_catego c_catego%ROWTYPE; CURSOR c_emple IS SELECT codem,codcat,salar FROM emple WHERE catego=v_catego.codcat; v_emple c_emple%ROWTYPE; BEGIN OPEN c_catego; FETCH c_catego INTO v_catego; WHILE c_catego%FOUND LOOP OPEN c_emple; FETCH c_emple INTO v_emple; WHILE v_emple.codcat=v_catego.codcat AND v_emple.salar<v_catego.media loop UPDATE emple SET salar=(v_catego.mediav_emple.salar)/2+v_emple.salar WHERE codem=v_emple.codem; DBMS_OUTPUT.PUT_LINE(v_emple.codem||’ ‘||v_emple.salar); FETCH c_emple INTO v_emple; END LOOP; FETCH c_catego INTO v_catego; END LOOP; CLOSE c_catego; CLOSE c_emple; END; ************TERMINAR PARA CORRECTO FUNCIONAMIENTO********************* 59 CREATE OR REPLACE PROCEDURE subir_salario_a_todos IS CURSOR c_emple IS SELECT codcat,salar from emple FOR UPDATE OF salar; v_emple c_emple%ROWTYPE; v_salar_medio emple.salar%TYPE; BEGIN OPEN c_emple; FETCH c_emple INTO v_emple; WHILE c_emple%FOUND LOOP SELECT avg(salar) into v_salar_medio FROM EMPLE WHERE codcat=v_emple.codcat; IF v_emple.salar<v_salar_medio THEN UPDATE emple ST SALAR=SALAR+(v_salar.medio-v_emple.salar)/2 WHERE CURRENT OF c_emple; END IF; FETCH c_emple INTO v_salar; END LOOP; CLOSE c_emple; END; 31. Escribir un procedimie nto que reciba como parámetros código de departamento, importe y porcentaje y que suba el salario a todos los empleados del departamento. La subida será el porcentaje sobre su salario o bien el importe si es más beneficioso. CREATE OR REPLACE PROCEDURE subida_porcentaje(p_codde depto.codde%TYPE, p_importe number, p_porcentaje number) IS CURSOR c_emple IS SELECT codem, salar FROM emple WHERE codde=p_codde FOR UPDATE OF salar; v_emple c_emple%ROWTYPE; BEGIN OPEN c_emple; FETCH c_emple INTO v_emple; WHILE c_emple%FOUND LOOP IF p_importe>(salar*(p_porcentaje/100)) THEN UPDATE emple SET salar=salar+p_importe WHERE CURRENT OF c_emple; DBMS_OUTPUT.PUT_LINE(v_emple.codem||’ ‘||v_emple.salar); ELSE UPDATE emple SET salar=salar+(salar*p_porcentaje/100) WHERE CURRENT OF c_emple; DBMS_OUTPUT.PUT_LINE(v_emple.codem||’ ‘||v_emple.salar); END IF; FETCH c_emple INTO v_emple; END LOOP; CLOSE c_emple; 60 END subida_porcentaje; 32. Escribir un procedimiento que reciba como parámetro un código de rama y obtenga por cada grupo de esa rama el nombre del alumno que tiene la mayor nota global. CREATE OR REPLACE PROCEDURE nota_global(p_codrama rama.codrama%TYPE) IS CURSOR c_nota IS SELECT nomalumno, a.codgrupo, avg(notaf) notaza FROM estudia e, alumno a WHERE e.dni=a.dni AND a.codrama=p_codrama GROUP BY a.codgrupo, nomalumno ORDER BY a.codgrupo,notaza DESC; v_nota c_nota%ROWTYPE; v_codgrupo grupo.codgrupo%TYPE; BEGIN OPEN c_nota; FETCH c_nota INTO v_nota; WHILE c_nota%FOUND LOOP DBMS_OUTPUT.PUT_LINE(v_nota.nomalumno||' '||v_nota.codgrupo||' '||v_nota.notaza); v_codgrupo:=v_nota.codgrupo; WHILE v_nota.codgrupo=v_codgrupo AND c_nota%FOUND LOOP FETCH c_nota INTO v_nota; END LOOP; END LOOP; CLOSE c_nota; END nota_global; ************************************************************************* 33. Escribir un procedimiento q reciba como parámetro un codigo de rama y obtenga por cada grupo de esa rama el nombre del alumno q tiene la mayor nota global (avg(notaf) de todas las asignaturas) CREATE OR REPLACE PROCEDURE nota_medi( p_codrama alumno.codrama%TYPE) IS CURSOR c_alumno IS SELECT nomalumno,avg((nota1+nota2+nota3)/3) media,codgrupo FROM alumno,estudia where alumno.dni=estudia.dni and codrama=p_codrama group by nomalumno,codgrupo order by 3,2 desc; v_alumno c_alumno%ROWTYPE; v_codgrupo alumno.codgrupo%TYPE:='0'; BEGIN OPEN c_alumno; FETCH c_alumno INTO v_alumno; WHILE c_alumno%FOUND LOOP 61 IF v_alumno.codgrupo<>v_codgrupo THEN DBMS_OUTPUT.PUT_LINE ('Alumno: '||v_alumno.nomalumno||' del grupo: '||v_alumno.codgrupo||'con nota media: '||v_alumno.media); v_codgrupo:=v_alumno.codgrupo; END IF; FETCH c_alumno INTO v_alumno; END LOOP; END nota_medi; 34. Construir el paquete “alumnopaquete” que contenga los siguientes subprogramas: 1. Listado asignatura alumnos (proc listadoasignaturaalumnos), que recibe como parámetro un codigo de asignatura y visualiza la denominacion de la asignatura y los nombres de los alumnos matriculados en ella en orden alfabético. 2. Listadoalumnoasignaturas, que recibe como parametro un dni de alumno y visualiza el nombre del alumno y los nombres de las asignaturas en que está matriculado con sus calificaciones. 3. Función: matricularalumnoasignatura, que recibe como parámetros un dni de alumno y un codigo de asignatura, y ha de matricular al alumno en la asignatura, realizando el siguiente control de situaciones: - Comprobar si el alumno está matriculado en el centro. Si no devolver –1. - Comprobar que el codigo de asignatura existe. Si no devolver –2. - Comprobar que el alumno no esté ya matriculado en esa asignatura. Si no devolver –3. - En caso que se pueda matricular el alumo en la asignatura devolver 0. 4. Procedimiento: estadisticamatriculas. Se da un curso o un turno y visualiza el siguiente mensaje: “En el curso xx hay xx alumnos” ó “En el turno xx hay xx alumnos”. 5. Procedimiento: listadoalumnos. Donde puede recibir como parámetro o bien un código de rama, en cuyo caso se presentan los alumnos de la rama clasificados por turnos, grupos y en orden alfabético, o bien recibir un codigo de rama y un turno, en cuyo caso visualiza los alumnos de la rama y turno especificados clasificados por grupos, y dentro de ellos, en orden alfabético. CREATE OR REPLACE PACKAGE alumnopaquete IS PROCEDURE listadoasignaturaalumnos(p_codasigna asigna.codasigna%TYPE); PROCEDURE listadoalumnoasignaturas(p_dni alumno.dni%TYPE); FUNCTION matricularalumnoasignatura(p_dni alumno.dni%TYPE, p_codasigna asigna.codasigna%TYPE) RETURN number; PROCEDURE estadisticamatriculas(p_turno grupo.turno%TYPE); PROCEDURE estadisticamatriculas (p_curso grupo%TYPE); PROCEDURE listadoalumnos(p_codrama rama.codrama%TYPE); PROCEDURE listadoalumnos(p_codrama rama.codrama%TYPE, p_turno grupo.turno%TYPE); v_curso number v_turno char; END alumnopaquete; CREATE OR REPLACE PACKAGE BODY alumnopaquete IS PROCEDURE listadoasignaturaalumnos(p_codasigna asigna.codasigna%TYPE) IS 62 CURSOR c_listado IS SELECT nomalumno FROM alumno al, estudia e WHERE al.dni=e.dni AND e.codasigna=p_codasigna ORDER BY nomalumno; v_listado alumno.nomalumno%TYPE; v_denasigna asigna.denasigna%TYPE; BEGIN SELECT denasigna INTO v_denasigna FROM asigna WHERE codasigna=p_codasigna; DBMS_OUTPUT.PUT_LINE(v_denasigna); OPEN c_listado; FETCH c_listado INTO v_listado; WHILE c_listado%FOUND LOOP DBMS_OUTPUT.PUT_LINE(v_listado.nomalumno); FETCH c_listado INTO v_listado; END LOOP; CLOSE c_listado; END listadoasignaturaalumnos; PROCEDURE listadoalumnoasignaturas(p_dni alumno.dni%TYPE) IS CURSOR c_alumno IS SELECT denasigna, notaf FROM asigna a, estudia e WHERE a.codasigna=e.codasigna AND e.dni=p_dni ORDER BY 1; v_alumno c_alumno%ROWTYPE; v_nomalumno alumno.nomalumno%TYPE; BEGIN SELECT nomalumno INTO v_nomalumno FROM alumno WHERE dni=p_dni; DBMS_OUTPUT.PUT_LINE(v_nomalumno); OPEN c_alumno; FETCH c_alumno INTO v_alumno; WHILE c_alumno%FOUND LOOP DBMS_OUTPUT.PUT_LINE(v_alumno.denasigna||’ ‘||v_alumno.notaf); FETCH c_alumno INTO v_alumno; END LOOP; CLOSE c_alumno; END listadoalumnoasignaturas; FUNCTION matricularalumnoasignatura (p_dni alumno.dni%TYPE, p_codasigna asigna.codasigna%TYPE) RETURN number IS CURSOR c_dni IS SELECT dni FROM alumno WHERE dni=p_dni; v_dni alumno.dni%TYPE; CURSOR c_codasigna IS SELECT codasigna FROM asigna WHERE codasigna=p_codasigna; 63 v_codasigna asigna.codasigna%TYPE; CURSOR c_existe IS SELECT dni, codasigna FROM estudia WHERE dni=p_dni AND codasigna=p_codasigna; v_existe c_existe%ROWTYPE ; BEGIN OPEN c_existe; FETCH c_existe INTO v_existe; IF c_existe%FOUND THEN RETURN –3; ELSE OPEN c_dni; FETCH c_dni INTO v_dni; IF c_dni%FOUND THEN OPEN c_codasigna; FETCH c_codasigna INTO v_codasigna; IF c_codasigna%FOUND THEN INSERT INTO estudia p_codasigna, null, null, null, null); RETURN 0; ELSE RETURN –2; END IF; CLOSE c_codasigna; ELSE RETURN –1; END IF; CLOSE c_dni; END IF; CLOSE c_existe; END matricularalumnoasignatura; VALUES(p_dni, PROCEDURE estadisticamatriculas (p_turno grupo.turno%TYPE) IS v_cuantos_alumnos number; BEGIN SELECT count(*) INTO v_cuantos_alumnos FROM alumno,grupo WHERE alumno.codgrupo=grupo.codgrupo AND curso=p_curso; DBMS_OUTPUT.PUT_LINE('En el curso '||p_curso||' hay '||v_cuantos_alumnos); END EstadisticaMatriculas; PROCEDURE EstadisticaMatriculas(p_turno asigna.curso%TYPE)IS v_cuantos_alumnos number; BEGIN SELECT count(*) INTO v_cuantos_alumnos FROM alumno,grupo WHERE alumno.codgrupo=grupo.codgrupo AND turno=p_turno; 64 DBMS_OUTPUT.PUT_LINE('En el turno '||p_turno||' hay '||v_cuantos_alumnos); END EstadisticaMatriculas; PROCEDURE listadoalumnos(p_codrama rama.codrama%TYPE) IS CURSOR c_alum IS SELECT turno, al.codgrupo codigogrupo, nomalumno FROM alumno al, grupo g WHERE al.codgrupo=g.codgrupo AND al.codrama=p_codrama ORDER BY 1,2,3; BEGIN FOR v_alum In c_alum LOOP DBMS_OUTPUT.PUT_LINE(v_alum.turno||’ ‘||v_alum.codigogrupo||’ ‘||v_alum.nomalum); END LOOP; END listadoalumnos; PROCEDURE listadoalumnos(p_codrama rama.codrama%TYPE, p_turno grupo.turno%TYPE) IS CURSOR c_alum IS SELECT al.codgrupo codigogrupo, nomalumno FROM alumno al, grupo g WHERE al.codgrupo=g.codgrupo AND al.codrama=p_codrama AND turno=p_turno ORDER BY 1,2; BEGIN FOR v_alum IN c_alum LOOP DBMS_OUTPUT.PUT_LINE(v_alum.codigogrupo||’ ‘||v_alum.nomalumno); END LOOP; END listadoalumnos; END alumnopaquete; **************CORREGIR ERRORES********************* 35. Crear un trigger ControlAsignaturaRama que controle que un alumno sólo se pueda matricular de asignaturas que sean de la rama en la que está matriculado. >CREATE OR REPLACE TRIGGER ControlAsignaturaRama BEFORE INSERT ON estudia FOR EACH ROW DECLARE v_rama_alumno rama.codrama%TYPE; v_rama_asignatura rama.codrama%TYPE; BEGIN SELECT codrama INTO v_rama_alumno FROM alumno 65 WHERE dni=:new.dni; SELECT codrama INTO v_rama_asignatura FROM asigna WHERE codasigna=:new.codasigna; IF v_rama_alumno<>v_rama_asignatura THEN RAISE_APPLICATION_ERROR(-20001,’No se puede matricular un alumno en una asignatura que no sea de la rama en la que está matriculado... ¡¡Melón!!’); END IF; END ControlAsignaturaRama; 36. Crear un trigger ControlAsignaturaMatricula que controle que un alumno sólo se pueda matricular de asignaturas de la rama en la que el está matriculado, y de asignaturas del curso anterior al que está matriculado. Además, diseñar el trigger de manera que las constraints que haya definidas sobre la tabla estudia se accionen por si mismas y no sean interceptadas dentro del trigger. CREATE OR REPLACE TRIGGER ControlAsignaturaMatricula BEFORE INSERT ON estudia FOR EACH ROW DECLARE CURSOR c_rama IS SELECT codrama FROM alumno WHERE dni=:new.dni; CURSOR c_curso IS SELECT curso FROM grupo g, alumno al WHERE g.codgrupo=al.codgrupo AND dni=:new.dni; CURSOR c_asigna IS SELECT codrama, curso FROM asigna WHERE codasigna=:new.codasigna; v_rama c_rama%ROWTYPE; v_curso c_curso%ROWTYPE; v_asigna c_asigna%ROWTYPE; BEGIN OPEN c_asigna; FETCH c_asigna INTO v_asigna; OPEN c_rama; FETCH c_rama INTO v_rama; OPEN c_curso; FETCH c_curso INTO v_curso; IF c_curso%FOUND AND c_asigna%FOUND AND c_rama%FOUND THEN IF v_rama.codrama<> v_asigna.codrama THEN RAISE_APPLICATION_ERROR(-20002,'No se puede matricular a un alumno en un a asignatura que no es de su rama'); END IF; IF v_asigna.curso<>v_curso.curso AND v_asigna.curso<>v_curso.curso-1 THEN RAISE_APPLICATION_ERROR(-20003,'No se puede matricular a un alumo de una asignatura que no es de su curso o curso anterior'); END IF; END IF; CLOSE c_asigna; 66 CLOSE c_rama; CLOSE c_curso; END ControlAsignaturaMatricula; 37. Crear un trigger CaseClausus que controle que en un grupo no puede haber más de 23 alumnos matriculados en CASE. CREATE OR REPLACE TRIGGER CaseClausus BEFORE INSERT ON estudia FOR EACH ROW WHEN(new.codasigna=’CASE’) DECLARE CURSOR c_grupo IS SELECT codgrupo from alumno WHERE dni=:new.dni; v_grupo c_grupo%ROWTYPE; v_case number; BEGIN OPEN c_grupo; FETCH c_grupo INTO v_grupo; IF c_grupo%FOUND THEN SELECT count(*) INTO v_case FROM estudia,alumno WHERE codasigna='CASE' and alumno.codgrupo=v_grupo.codgrupo AND estudia.dni=alumno.dni; IF v_case>=23 THEN RAISE_APPLICATION_ERROR(-20001,'No pueden haber más de 23 alumnos matriculados en CASE'); END IF; END IF; CLOSE c_grupo; END CaseClausus; 38. Realizar el disparador AsignaturaClausus que controle que en cualquier asignatura, en un grupo no puede haber matriculados más de 23 alumnos. CREATE OR REPLACE TRIGGER AsignaturaClausus BEFORE INSERT ON estudia FOR EACH ROW DECLARE CURSOR c_grupo IS SELECT codgrupo FROM alumno WHERE dni=:new.dni; CURSOR c_asigna IS SELECT codasigna FROM asigna WHERE codasigna=:new.codasigna; v_grupo c_grupo%ROWTYPE; v_asigna c_asigna%ROWTYPE; v_case number; BEGIN OPEN c_grupo; FETCH c_grupo INTO v_grupo; 67 OPEN c_asigna; FETCH c_asigna INTO v_asigna; IF c_grupo%FOUND THEN IF c_asigna%FOUND THEN SELECT count(*) INTO v_case FROM estudia,alumno WHERE codasigna=v_asigna.codasigna and alumno.codgrupo=v_grupo.codgrupo AND estudia.dni=alumno.dni; IF v_case>=23 THEN RAISE_APPLICATION_ERROR(-20001,'No pueden haber más de 23 alumnos matriculados en '||v_asigna.codasigna||’ en el mismo grupo); END IF; END IF; END IF; CLOSE c_grupo; CLOSE c_asigna; END AsignaturaClausus; 39. En la BD_PERSONAL implementar las siguientes restricciones semánticas que no han podido ser implementadas mediante constraints: - Un departamento no puede depender de si mismo. - Si un departamento A depende de un departamento B el departamento B no puede depender del A. - Un empleado puede ser jefe en propiedad de un único departamento en cuyo caso sería el departamento al que está asignado y puede ser en funciones de varios departamentos - Los salarios, comisiones y número de hijos no pueden tener valores negativos. CREATE OR REPLACE TRIGGER ControlDependenciaDepto BEFORE INSERT ON depto FOR EACH ROW DECLARE CURSOR c_depto IS SELECT depde FROM depto WHERE codde=:new.depde; v_depto c_depto%ROWTYPE; BEGIN OPEN c_depto; FETCH c_depto INTO v_depto; IF c_depto%FOUND THEN IF :new.codde=:new.depde THEN RAISE_APPLICA TION_ERROR(-20001,’Un depender de si mismo’); END IF; IF :new.codde=v_depto.depde THEN RAISE_APPLICATION_ERROR(-20002,’No dependencia entre departamentos’); END IF; END IF; departamento no puede ciclos haber puede de 68 CLOSE c_depto; END ControlDependenciaDepartamentos; CREATE OR REPLACE TRIGGER ControlJefesDepto BEFORE INSERT ON DEPTO FOR EACH ROW WHEN (new.tidir=’P’) DECLARE CURSOR c_emple IS SELECT codde FROM emple WHERE codem=:new.codjefe; v_codde_empleado emple.codde%TYPE; BEGIN OPEN c_emple; FETCH c_emple INTO v_codde_empleado; IF c_emple%FOUND THEN IF v_codde_empleado<>:new.codde THEN RAISE_APPLICATION_ERROR(-20002,’Un empleado no puede ser jefe en propiedad de un departamento al que no está asignado’); END IF; END IF; CLOSE c_emple; END ControlJefesDepto; 40. En el procedimiento AñadirAlumno crear una excepción e_DemasiadosAlumnos que se generecuando en un grupo se intenta matricular a más de 30 alumnos. CREATE OR REPLACE PROCEDURE AñadirAlumno(p_dni alumno.dni%TYPE, p_nomalumno alumno.nomalumno%type, p_codrama rama.codrama%type, p_codgrupo grupo.codgrupo%type) IS v_cuantos number; e_DemasiadosAlumnos EXCEPTION; BEGIN SELECT count(*) INTO v_cuantos FROM alumnos WHERE codgrupo=p_codgrupo; IF v_cuantos>=30 THEN RAISE e_DemasiadosAlumnos; ELSE INSERT INTO alumno VALUES(p_dni, p_nomalumno, p_codrama, p_codgrupo); END IF; END; 69