UD09 · Prácticas
RA5 Desarrolla procedimientos almacenados evaluando y utilizando las sentencias del lenguaje incorporado en el sistema gestor de bases de datos. 10 CE
- RA5.a Se han identificado las diversas formas de automatizar tareas.
- RA5.b Se han reconocido los métodos de ejecución de guiones.
- RA5.c Se han identificado las herramientas disponibles para editar guiones.
- RA5.d Se han definido y utilizado guiones para automatizar tareas.
- RA5.e Se ha hecho uso de las funciones proporcionadas por el sistema gestor.
- RA5.f Se han definido procedimientos y funciones de usuario.
- RA5.g Se han utilizado estructuras de control de flujo.
- RA5.h Se han definido eventos y disparadores.
- RA5.i Se han utilizado cursores.
- RA5.j Se han utilizado excepciones.
RA4 Modifica la información almacenada en la base de datos utilizando asistentes, herramientas gráficas y el lenguaje de manipulación de datos. 2 CE
- RA4.d Se han diseñado guiones de sentencias para llevar a cabo tareas complejas.
- RA4.h Se han adoptado medidas para mantener la integridad y consistencia de la información.
RA6 Diseña modelos relacionales normalizados interpretando diagramas entidad/relación. 1 CE
- RA6.h Se han analizado y documentado las restricciones que no pueden plasmarse en el diseño lógico.
| Práctica | Tipo | Nivel | CE principales |
|---|---|---|---|
| 9.1 Primeros bloques: el expediente de un alumno | Guiada | ●○○ | RA5.b, RA5.c, RA5.e, RA5.g |
| 9.2 Biblioteca de funciones de EduGest | Guiada | ●●○ | RA5.e, RA5.f |
| 9.3 Procedimiento de matrícula con excepciones | Guiada | ●●○ | RA5.f, RA5.g, RA5.j |
| 9.4 Cursores: boletines e informes | Autónoma | ●●○ | RA5.g, RA5.i |
| 9.5 Disparadores de integridad y auditoría | Guiada | ●●● | RA5.h, RA6.h, RA4.h |
| 9.6 Reto: la tabla mutante | Reto | ●●● | RA5.h, RA5.i |
| 9.7 Tareas programadas | Guiada | ●●○ | RA5.a, RA5.d, RA5.h |
| 9.8 Reto: el paquete de secretaría | Reto | ●●● | RA5.d, RA5.f, RA5.j, RA4.d |
| Proyecto EduGest · UD09 | Proyecto | ●●● | RA5 completo, RA6.h |
- Trabaja como
EDUGESTsobre los datos originales (scripts 01 y 02).- Activa la salida en cada sesión:
SET SERVEROUTPUT ON(en SQL Developer, también Ver → Salida de DBMS y el botón + para la conexión).- Guarda cada subprograma en su propio fichero
.sqlterminado en/. Así podrás recompilarlo y versionarlo.
Práctica 9.1 · Primeros bloques: el expediente de un alumno
Objetivo
Escribir y ejecutar bloques anónimos con variables, consultas SELECT INTO, funciones del sistema gestor y estructuras de control.
Desarrollo
Bloque mínimo y variables del sistema:
SET SERVEROUTPUT ON BEGIN DBMS_OUTPUT.PUT_LINE('Usuario: ' || USER); DBMS_OUTPUT.PUT_LINE('Fecha: ' || TO_CHAR(SYSDATE, 'DD/MM/YYYY HH24:MI')); DBMS_OUTPUT.PUT_LINE('Equipo cliente: ' || SYS_CONTEXT('USERENV', 'HOST')); END; /Datos de un alumno con
%ROWTYPEy una variable de sustitución:DECLARE r_alumno alumno%ROWTYPE; v_edad NUMBER; BEGIN SELECT * INTO r_alumno FROM alumno WHERE id_alumno = &id_alumno; v_edad := TRUNC(MONTHS_BETWEEN(DATE '2026-10-06', r_alumno.fecha_nacimiento) / 12); DBMS_OUTPUT.PUT_LINE('EXPEDIENTE ' || r_alumno.nia); DBMS_OUTPUT.PUT_LINE(RPAD('-', 40, '-')); DBMS_OUTPUT.PUT_LINE('Alumno: ' || r_alumno.apellidos || ', ' || r_alumno.nombre); DBMS_OUTPUT.PUT_LINE('Grupo: ' || NVL(r_alumno.cod_grupo, 'sin grupo')); DBMS_OUTPUT.PUT_LINE('Edad: ' || v_edad || ' años'); END; /SQLcl preguntará
Enter value for id_alumno:. Escribe 20.Añade estadísticas con
SELECT INTOde funciones de agregado y una condición:-- (dentro del bloque anterior; declara v_media NUMBER y v_horas NUMBER) SELECT ROUND(AVG(nota_final), 2) INTO v_media FROM matricula WHERE id_alumno = r_alumno.id_alumno; SELECT NVL(SUM(f.horas), 0) INTO v_horas FROM falta_asistencia f JOIN matricula m ON m.id_matricula = f.id_matricula WHERE m.id_alumno = r_alumno.id_alumno; DBMS_OUTPUT.PUT_LINE('Media: ' || NVL(TO_CHAR(v_media), 'sin notas')); DBMS_OUTPUT.PUT_LINE('Faltas: ' || v_horas || ' horas'); IF v_horas >= 10 THEN DBMS_OUTPUT.PUT_LINE('AVISO: riesgo de pérdida de evaluación continua'); ELSIF v_horas > 0 THEN DBMS_OUTPUT.PUT_LINE('Tiene faltas registradas'); ELSE DBMS_OUTPUT.PUT_LINE('Sin faltas'); END IF;Ejecuta el guion desde SQLcl con
@p9_1.sqly compara con la ejecución en SQL Developer.
Comprobación
Para el alumno 20 la salida debe ser:
EXPEDIENTE 10450740
----------------------------------------
Alumno: Sala Brotons, Mateo
Grupo: 2DAW
Edad: 23 años
Media: 7.13
Faltas: 5 horas
Tiene faltas registradas- Con el alumno 30 (sin grupo y sin matrículas) el bloque muestra «sin grupo», «sin notas» y «Sin faltas».
- Con el alumno 999 se produce
ORA-01403: no data found. Añade una secciónEXCEPTIONque muestre «No existe el alumno 999».
Errores habituales
| Síntoma | Causa |
|---|---|
| El bloque termina sin mostrar nada | Falta SET SERVEROUTPUT ON o el panel de salida de DBMS no está activo |
| SQLcl se queda esperando con un número de línea | Falta la / final |
PLS-00103: Encountered the symbol ... | Error de sintaxis: suele ser un ; olvidado o un END IF sin cerrar |
Práctica 9.2 · Biblioteca de funciones de EduGest
Objetivo
Crear funciones de usuario reutilizables y usarlas tanto en PL/SQL como en consultas SQL.
Desarrollo
Crea estas funciones (las dos primeras están en la teoría):
| Función | Parámetros | Devuelve |
|---|---|---|
fn_calificacion | nota | 'NC', 'Insuficiente'… 'Sobresaliente' |
fn_edad | fecha de nacimiento, fecha de referencia (por defecto SYSDATE) | Años cumplidos |
fn_media_alumno | id del alumno, curso académico (por defecto '2025-26') | Nota media con 2 decimales, o NULL si no tiene notas |
fn_horas_falta | id del alumno, solo no justificadas ('S'/'N', por defecto 'N') | Total de horas de falta (0 si no tiene) |
fn_nombre_completo | id del alumno | 'Apellidos, Nombre' o NULL si no existe (sin lanzar error) |
🔒 Solución de fn_media_alumno y fn_nombre_completo
Comprobación
Ejecuta esta consulta. El resultado debe coincidir:
SELECT id_alumno, fn_nombre_completo(id_alumno) AS alumno,
fn_media_alumno(id_alumno) AS media,
fn_calificacion(fn_media_alumno(id_alumno)) AS calificacion,
fn_horas_falta(id_alumno, 'N') + fn_horas_falta(id_alumno, 'S') AS horas_falta
FROM alumno
WHERE cod_grupo = '2DAW'
ORDER BY id_alumno;| ID_ALUMNO | ALUMNO | MEDIA | CALIFICACION | HORAS_FALTA |
|---|---|---|---|---|
| 20 | Sala Brotons, Mateo | 7.13 | Notable | 5 |
| 21 | Carbonell Soriano, Elena | 5.44 | Suficiente | 1 |
| 22 | Planelles Marco, Sofía | 5.56 | Suficiente | 4 |
| 23 | Amorós Guillem, Sara | 5.42 | Suficiente | 3 |
| 24 | Verdú Castelló, Nicolás | 6 | Bien | 0 |
-
SELECT fn_nombre_completo(999) FROM dual;devuelveNULLsin error. -
SELECT fn_media_alumno(30) FROM dual;devuelveNULL(alumno sin matrículas). -
SELECT object_name, status FROM user_objects WHERE object_type = 'FUNCTION';muestra las cinco funcionesVALID.
Ampliación
¿Qué pasa si intentas usar en un SELECT una función que hace un INSERT? Pruébalo y anota el error (ORA-14551). ¿Por qué Oracle lo impide?
Práctica 9.3 · Procedimiento de matrícula con excepciones
Objetivo
Implementar un procedimiento almacenado que valida reglas de negocio y comunica los errores de forma controlada.
Desarrollo
Crea
pr_matriculartal como aparece en la teoría.Ejecuta la batería de pruebas. Cada prueba indica el resultado esperado:
# Llamada Resultado esperado P1 pr_matricular(1, 2, '2026-27')Correcto. Convocatoria 2 P2 La misma llamada otra vez ORA-20013: ya está matriculadoP3 pr_matricular(1, 12, '2026-27')ORA-20010: el módulo es de DAWP4 pr_matricular(30, 2, '2026-27')ORA-20012: alumno sin grupoP5 pr_matricular(999, 2, '2026-27')ORA-20012P6 pr_matricular(1, 999, '2026-27')ORA-20012P7 pr_matricular(1, 3, '2026/27')ORA-02290(CK_MATRICULA_CURSO): lo detecta la restricción, no el procedimientoP8 Matricular al alumno 1 en el módulo 4 en los cursos '2026-27','2027-28','2028-29'y'2029-30'Las tres primeras crean las convocatorias 2, 3 y 4; la cuarta (convocatoria 5) da ORA-20011Comprueba el estado tras las pruebas:
SELECT id_modulo, curso_academico, convocatoria FROM matricula WHERE id_alumno = 1 AND curso_academico <> '2025-26' ORDER BY id_modulo, curso_academico; ROLLBACK;
Comprobación
- Las ocho pruebas dan el resultado esperado.
- Tras las pruebas y antes del
ROLLBACKhay 4 matrículas nuevas del alumno 1: módulo 2 (convocatoria 2) y módulo 4 (convocatorias 2, 3 y 4). - Puedes explicar por qué el procedimiento no hace
COMMIT.
¿Por qué P8 falla en la cuarta llamada?
El alumno 1 ya tenía una matrícula del módulo 4 en 2025-26 (convocatoria 1). El procedimiento calcula la convocatoria como MAX(convocatoria) + 1, así que las llamadas para 2026-27, 2027-28 y 2028-29 crean las convocatorias 2, 3 y 4. La quinta convocatoria no está permitida.
Ampliación
Añade un parámetro p_forzar BOOLEAN DEFAULT FALSE que permita saltarse la regla del ciclo (matrícula autorizada por jefatura). ¿Se puede llamar a este procedimiento desde SQL*Plus con un BOOLEAN? ¿Y desde Oracle 23ai?
Práctica 9.4 · Cursores: boletines e informes
Enunciado
1. Boletín de notas. Crea pr_boletin(p_id_alumno) que muestre el boletín de un alumno usando un cursor explícito sobre sus matrículas. Para el alumno 20 la salida debe ser:
BOLETÍN DE NOTAS · Curso 2025-26
Alumno: Sala Brotons, Mateo (2DAW)
-------------------------------------------------------------
0612 Desarrollo web en entorno cliente 6.50 Bien
0613 Desarrollo web en entorno servidor 5.25 Suficiente
0614 Despliegue de aplicaciones web 7.50 Notable
0615 Diseño de interfaces web 9.25 Sobresaliente
-------------------------------------------------------------
Módulos: 4 · Aprobados: 4 · Media: 7.13Pistas: RPAD(nombre, 45) alinea columnas; TO_CHAR(nota, '90.00') formatea la nota; usa fn_calificacion y fn_media_alumno de la práctica 9.2; cuenta los aprobados dentro del bucle.
2. Boletines de un grupo. Crea pr_boletines_grupo(p_cod_grupo) que recorra el alumnado del grupo con un cursor FOR y llame a pr_boletin para cada uno.
3. Alerta de faltas. Crea pr_alerta_faltas(p_umbral NUMBER DEFAULT 4) que, con un cursor con parámetros, muestre el alumnado cuyas horas de falta no justificadas sean iguales o superiores al umbral, y que termine indicando cuántos alumnos ha encontrado.
Comprobación
EXEC pr_alerta_faltas(5);encuentra 2 alumnos: Martina Alemany Vidal (6) y Valeria Quiles Marco (5).EXEC pr_alerta_faltas;(umbral por defecto, 4) encuentra 5 alumnos.EXEC pr_boletines_grupo('2ASIR');no da error y muestra que el grupo no tiene alumnado.El cursor explícito se cierra también si se produce un error (sección
EXCEPTIONque comprueba%ISOPEN).
Práctica 9.5 · Disparadores de integridad y auditoría
Objetivo
Implementar con disparadores la auditoría de cambios y las restricciones de EduGest que no pudieron declararse en el modelo lógico (catálogo de la UD03).
Desarrollo
Auditoría de notas. Crea la tabla
AUDITORIA_NOTAy el disparadortrg_auditoria_notade la teoría. Amplíalo para registrar también la IP de la sesión (SYS_CONTEXT('USERENV', 'IP_ADDRESS')) y el tipo de operación ('U'en cambios de nota; añade el eventoDELETEy registra'D').R1 · El jefe pertenece al departamento (los dos lados de la regla):
trg_jefe_departamentosobreDEPARTAMENTO(teoría, apartado 11.3).trg_profesor_cambio_dptosobrePROFESOR: impide cambiar de departamento a un profesor que es jefe de su departamento actual.
CREATE OR REPLACE TRIGGER trg_profesor_cambio_dpto BEFORE UPDATE OF id_departamento ON profesor FOR EACH ROW WHEN (NEW.id_departamento <> OLD.id_departamento) DECLARE v_n NUMBER; BEGIN SELECT COUNT(*) INTO v_n FROM departamento WHERE id_jefe = :OLD.id_profesor AND id_departamento = :OLD.id_departamento; IF v_n > 0 THEN RAISE_APPLICATION_ERROR(-20021, 'El profesor ' || :OLD.id_profesor || ' es jefe de su departamento: asigna antes otra jefatura'); END IF; END; /R3 · No hay faltas anteriores a la matrícula. Escribe
trg_falta_fecha(BEFORE INSERT OR UPDATE OF fecha ON falta_asistencia) que consulte lafecha_matriculay rechace la falta con-20022si es anterior.Normalización de datos. Crea
trg_alumno_normaliza(teoría, apartado 11.4).Ejecuta la matriz de pruebas y anota el resultado:
# Sentencia Resultado esperado T1 UPDATE matricula SET nota_final = 6 WHERE id_matricula = 10002;1 fila; 1 registro en AUDITORIA_NOTA(4.75 → 6)T2 UPDATE matricula SET nota_final = 6 WHERE id_matricula = 10002;(otra vez)1 fila; ningún registro nuevo (no cambia el valor) T3 UPDATE departamento SET id_jefe = 111 WHERE id_departamento = 1;ORA-20020T4 UPDATE departamento SET id_jefe = 102 WHERE id_departamento = 1;Correcto T5 UPDATE profesor SET id_departamento = 2 WHERE id_profesor = 102;ORA-20021(tras T4, 102 es jefe)T6 UPDATE profesor SET id_departamento = 2 WHERE id_profesor = 108;Correcto (108 no es jefe) T7 INSERT INTO falta_asistencia (id_matricula, fecha, horas) VALUES (10001, DATE '2025-08-01', 2);ORA-20022T8 INSERT INTO alumno (nia, nombre, apellidos, fecha_nacimiento, email) VALUES ('10459999', ' marina ', 'lópez ortega', DATE '2007-01-01', ' Marina@Mail.COM ');Se guarda «Marina», «López Ortega», «marina@mail.com» T9 UPDATE matricula SET nota_final = nota_final WHERE id_alumno = 2;5 filas; ningún registro de auditoría Termina con
ROLLBACKy comprueba que la auditoría también se ha deshecho. ¿Por qué? ¿Cómo conseguirías que el registro de auditoría se conserve aunque la transacción se deshaga? (Pista:PRAGMA AUTONOMOUS_TRANSACTION.)
Comprobación
- La matriz completa coincide con lo esperado.
-
SELECT trigger_name, status FROM user_triggers;muestra todos los disparadoresENABLED. - Cada disparador tiene un comentario que cita la restricción del catálogo (R1, R3…) que implementa.
Si al cargar de nuevo los datos con el script 02 aparecen errores
ORA-200xx, es que tus disparadores rechazan algún dato de ejemplo o el orden de carga. Desactívalos antes de una carga masiva (ALTER TABLE ... DISABLE ALL TRIGGERS) y actívalos después… comprobando luego los datos con una consulta.
Práctica 9.6 · Reto: la tabla mutante
Enunciado
Hay que implementar la restricción R4: «un profesor no puede superar 20 horas lectivas semanales en un curso académico».
- Escribe primero un disparador de fila
AFTER INSERT OR UPDATE ON imparte FOR EACH ROWque sume las horas del profesor enIMPARTE. EjecutaUPDATE imparte SET horas_semanales = horas_semanales + 1 WHERE id_profesor = 102;y anota el error. - Explica por qué se produce
ORA-04091. - Sustitúyelo por un disparador compuesto que funcione para cualquier curso académico: guarda en una colección las parejas (profesor, curso) modificadas y compruébalas en
AFTER STATEMENT. - Pruebas:
UPDATE imparte SET horas_semanales = horas_semanales + 1 WHERE id_profesor = 102;→ correcto (Javier pasa de 16 a 20 horas).- Repite la sentencia →
ORA-20030(24 horas: la sentencia suma 1 hora a cada una de sus 4 asignaciones). INSERTde una asignación de 6 horas a Marta (101, 15 horas) en el curso 2026-27 → correcto (es otro curso).
- ¿Qué pasaría con dos sesiones que, a la vez, añaden horas al mismo profesor sin confirmar? ¿Garantiza el disparador la regla en ese caso? Relaciónalo con la UD08 y propón una solución.
Pista para el apartado 5
Cada sesión ve solo sus propios cambios sin confirmar (consistencia de lectura). Las dos pueden sumar 19 horas y confirmar, dejando 22. Una solución es serializar las modificaciones de cada profesor bloqueando su fila en PROFESOR con SELECT ... FOR UPDATE dentro del disparador, antes de sumar.
Práctica 9.7 · Tareas programadas
Objetivo
Automatizar tareas periódicas en el servidor y comprobar su ejecución.
Desarrollo
Crea la tabla
RESUMEN_GRUPOy el procedimientopr_recalcular_resumende la teoría.Crea una tabla
LOG_TAREA(momento TIMESTAMP, tarea VARCHAR2(50), mensaje VARCHAR2(200))y modifica el procedimiento para que escriba una fila al terminar, con el número de grupos procesados.Crea la tarea
JOB_RESUMEN_PRUEBAque se ejecute cada minuto (FREQ=MINUTELY; INTERVAL=1).Espera tres minutos y comprueba:
SELECT * FROM log_tarea ORDER BY momento; SELECT log_date, status FROM user_scheduler_job_run_details WHERE job_name = 'JOB_RESUMEN_PRUEBA' ORDER BY log_date;Provoca un error (por ejemplo, renombra la tabla
RESUMEN_GRUPO) y observa el estadoFAILEDy el código de error en el historial. Restaura la tabla.Cambia el calendario a «de lunes a viernes a las 7:30» con
DBMS_SCHEDULER.SET_ATTRIBUTEy desactiva la tarea.
Comprobación
-
LOG_TAREAtiene una fila por minuto mientras la tarea está activa. - El historial muestra al menos una ejecución
SUCCEEDEDy unaFAILED. - El calendario final es
FREQ=WEEKLY; BYDAY=MON,TUE,WED,THU,FRI; BYHOUR=7; BYMINUTE=30.
Las tareas que no hagas desaparecer siguen ejecutándose en tu contenedor. Al terminar:
EXEC DBMS_SCHEDULER.DROP_JOB('JOB_RESUMEN_PRUEBA');
Práctica 9.8 · Reto: el paquete de secretaría
Enunciado
Agrupa la lógica de secretaría en un paquete pkg_secretaria:
CREATE OR REPLACE PACKAGE pkg_secretaria AS
c_curso_actual CONSTANT matricula.curso_academico%TYPE := '2026-27';
PROCEDURE matricular (p_id_alumno NUMBER, p_id_modulo NUMBER);
PROCEDURE matricular_curso_completo (p_id_alumno NUMBER);
PROCEDURE promocionar (p_curso_origen VARCHAR2, p_curso_destino VARCHAR2,
p_promocionados OUT NUMBER, p_pendientes OUT NUMBER);
FUNCTION media (p_id_alumno NUMBER, p_curso VARCHAR2 DEFAULT NULL) RETURN NUMBER;
END pkg_secretaria;
/matricularreutiliza la lógica de la práctica 9.3.matricular_curso_completomatricula al alumno en todos los módulos del curso de su grupo llamando amatricular. Si alguna matrícula falla, no debe quedar ninguna (usa unSAVEPOINT).promocionarimplementa el guion de promoción de la práctica 8.7 como procedimiento, devuelve los recuentos en parámetrosOUTy lanza un error propio si ya existen matrículas del curso destino.- Escribe un bloque de prueba que ejecute
promocionar('2025-26', '2026-27', ...), muestre los recuentos y hagaROLLBACK.
Comprobación
La promoción devuelve 12 alumnos promocionados y 18 matrículas de pendientes, igual que el guion de la UD08.
Una segunda llamada lanza el error propio.
El cuerpo del paquete no tiene
COMMIT.
Proyecto EduGest · UD09: lógica en el servidor
Enunciado
- Catálogo de restricciones cerrado. Implementa con disparadores o procedimientos todas las restricciones de tu catálogo (UD03-UD05) que queden pendientes. Para cada una, una prueba que demuestre que funciona.
- Auditoría. Audita los cambios de nota y las bajas de alumnado. La auditoría debe conservarse aunque la transacción se deshaga.
- API de secretaría. Paquete
pkg_secretaria(práctica 9.8) y concesión deEXECUTEsobre él al rol de secretaría. Retira a ese rol los privilegiosINSERTdirectos sobreMATRICULA: ahora solo podrá matricular a través del procedimiento. Explica qué ventaja de seguridad tiene. - Tarea nocturna que recalcula el resumen por grupo y registra su ejecución.
- Documentación: tabla con cada objeto PL/SQL, su finalidad, sus parámetros y los errores propios que puede lanzar (código y mensaje).
Comprobación
Todos los objetos están
VALIDenUSER_OBJECTS.El usuario de secretaría puede matricular con
EXEC edugest.pkg_secretaria.matricular(...)pero no con unINSERTdirecto.Cada restricción del catálogo tiene su prueba.