Desarrollo de aplicaciones informáticas en lenguajes de cuarta generación con herramientas CASE

Anuncio
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
Descargar