10. Procedimientos, Funciones y Vistas
Introducción a los objetos de programación en Oracle
Section titled “Introducción a los objetos de programación en Oracle”Oracle Database proporciona varios tipos de objetos de programación que permiten encapsular lógica de negocio dentro de la base de datos. Estos objetos son fundamentales para desarrollar aplicaciones robustas, modulares y seguras. Los tres tipos principales que veremos en este documento son:
- Procedimientos almacenados: Bloques de código PL/SQL que realizan una tarea específica
- Funciones almacenadas: Similares a los procedimientos, pero devuelven un valor
- Vistas: Consultas almacenadas que se comportan como tablas virtuales
Estos objetos ofrecen numerosas ventajas:
- Modularidad: Permiten dividir la lógica de aplicación en componentes reutilizables
- Seguridad: Controlan el acceso a los datos subyacentes
- Rendimiento: Reducen el tráfico de red y mejoran la velocidad de ejecución
- Mantenibilidad: Centralizan la lógica de negocio para facilitar su mantenimiento
Procedimientos almacenados
Section titled “Procedimientos almacenados”Los procedimientos almacenados son bloques de código PL/SQL que se almacenan en la base de datos y pueden ser invocados por nombre. Realizan una tarea específica y pueden recibir parámetros de entrada y salida.
Creación de procedimientos
Section titled “Creación de procedimientos”La sintaxis básica para crear un procedimiento es:
CREATE [OR REPLACE] PROCEDURE nombre_procedimiento [(parametro1 [IN | OUT | IN OUT] tipo1, parametro2 [IN | OUT | IN OUT] tipo2, ...)][AUTHID DEFINER | CURRENT_USER]IS | AS -- Declaración de variables locales variable1 tipo1; variable2 tipo2;BEGIN -- Cuerpo del procedimiento -- Instrucciones PL/SQLEXCEPTION -- Manejo de excepciones WHEN excepcion1 THEN -- Acciones para manejar excepcion1 WHEN OTHERS THEN -- Acciones para manejar otras excepcionesEND nombre_procedimiento;/Tipos de parámetros
Section titled “Tipos de parámetros”- IN: Parámetros de entrada (valor por defecto)
- OUT: Parámetros de salida
- IN OUT: Parámetros que sirven tanto de entrada como de salida
Ejemplos de procedimientos
Section titled “Ejemplos de procedimientos”CREATE OR REPLACE PROCEDURE actualizar_salario( p_empleado_id IN NUMBER, p_porcentaje IN NUMBER)ISBEGIN UPDATE empleados SET salario = salario * (1 + p_porcentaje/100) WHERE empleado_id = p_empleado_id;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Salario actualizado correctamente.');EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('Empleado no encontrado.'); WHEN OTHERS THEN ROLLBACK; DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);END actualizar_salario;/CREATE OR REPLACE PROCEDURE obtener_info_empleado( p_empleado_id IN NUMBER, p_nombre OUT VARCHAR2, p_salario OUT NUMBER, p_departamento OUT VARCHAR2)ISBEGIN SELECT e.nombre, e.salario, d.nombre_departamento INTO p_nombre, p_salario, p_departamento FROM empleados e JOIN departamentos d ON e.departamento_id = d.departamento_id WHERE e.empleado_id = p_empleado_id;EXCEPTION WHEN NO_DATA_FOUND THEN p_nombre := NULL; p_salario := NULL; p_departamento := NULL; DBMS_OUTPUT.PUT_LINE('Empleado no encontrado.'); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);END obtener_info_empleado;/CREATE OR REPLACE PROCEDURE calcular_impuesto( p_salario IN NUMBER, p_impuesto IN OUT NUMBER)ISBEGIN -- Calcula el impuesto basado en el salario IF p_salario < 10000 THEN p_impuesto := p_salario * 0.10; -- 10% ELSIF p_salario < 20000 THEN p_impuesto := p_salario * 0.15; -- 15% ELSE p_impuesto := p_salario * 0.20; -- 20% END IF;
-- Ajusta el impuesto si se proporcionó un valor inicial IF p_impuesto > 0 THEN p_impuesto := p_impuesto + 100; -- Tarifa fija adicional END IF;END calcular_impuesto;/Ejecución de procedimientos
Section titled “Ejecución de procedimientos”Hay varias formas de ejecutar un procedimiento almacenado:
-- Método 1: Usando EXECUTE o EXECEXECUTE actualizar_salario(101, 10);EXEC actualizar_salario(101, 10);
-- Método 2: En un bloque PL/SQL anónimoBEGIN actualizar_salario(101, 10);END;/
-- Método 3: Con parámetros OUTDECLARE v_nombre VARCHAR2(100); v_salario NUMBER; v_departamento VARCHAR2(100);BEGIN obtener_info_empleado(101, v_nombre, v_salario, v_departamento); DBMS_OUTPUT.PUT_LINE('Nombre: ' || v_nombre); DBMS_OUTPUT.PUT_LINE('Salario: ' || v_salario); DBMS_OUTPUT.PUT_LINE('Departamento: ' || v_departamento);END;/Ventajas de los procedimientos almacenados
Section titled “Ventajas de los procedimientos almacenados”- Encapsulación: Ocultan la complejidad y los detalles de implementación
- Reutilización: Pueden ser invocados desde múltiples aplicaciones
- Seguridad: Permiten controlar quién puede ejecutar determinadas operaciones
- Rendimiento: Se compilan y almacenan en la base de datos, mejorando la velocidad de ejecución
- Reducción del tráfico de red: Envían solo los parámetros y resultados, no todo el código SQL
Gestión de procedimientos
Section titled “Gestión de procedimientos”-- Ver el código fuente de un procedimientoSELECT text FROM user_source WHERE name = 'ACTUALIZAR_SALARIO' ORDER BY line;
-- Ver información sobre un procedimientoSELECT * FROM user_procedures WHERE object_name = 'ACTUALIZAR_SALARIO';
-- Eliminar un procedimientoDROP PROCEDURE actualizar_salario;
-- Otorgar permisos de ejecuciónGRANT EXECUTE ON actualizar_salario TO usuario;Funciones almacenadas
Section titled “Funciones almacenadas”Las funciones almacenadas son similares a los procedimientos, pero con una diferencia fundamental: siempre devuelven un valor. Son ideales para cálculos y operaciones que necesitan retornar resultados.
Creación de funciones
Section titled “Creación de funciones”La sintaxis básica para crear una función es:
CREATE [OR REPLACE] FUNCTION nombre_funcion [(parametro1 [IN | OUT | IN OUT] tipo1, parametro2 [IN | OUT | IN OUT] tipo2, ...)]RETURN tipo_retorno[AUTHID DEFINER | CURRENT_USER][DETERMINISTIC | PARALLEL_ENABLE | PIPELINED]IS | AS -- Declaración de variables locales variable1 tipo1; variable2 tipo2;BEGIN -- Cuerpo de la función -- Instrucciones PL/SQL
RETURN valor_retorno;EXCEPTION -- Manejo de excepciones WHEN excepcion1 THEN -- Acciones para manejar excepcion1 RETURN valor_por_defecto; WHEN OTHERS THEN -- Acciones para manejar otras excepciones RETURN valor_por_defecto;END nombre_funcion;/Opciones importantes
Section titled “Opciones importantes”- DETERMINISTIC: Indica que la función siempre devuelve el mismo resultado para los mismos parámetros de entrada
- PARALLEL_ENABLE: Permite que la función se ejecute en paralelo
- PIPELINED: Permite devolver resultados incrementalmente en funciones que retornan colecciones
Ejemplos de funciones
Section titled “Ejemplos de funciones”CREATE OR REPLACE FUNCTION calcular_salario_anual( p_empleado_id IN NUMBER) RETURN NUMBERIS v_salario_mensual NUMBER;BEGIN -- Obtener el salario mensual del empleado SELECT salario INTO v_salario_mensual FROM empleados WHERE empleado_id = p_empleado_id;
-- Calcular y devolver el salario anual (12 meses + 2 pagas extra) RETURN v_salario_mensual * 14;EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('Empleado no encontrado.'); RETURN 0; WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM); RETURN 0;END calcular_salario_anual;/CREATE OR REPLACE FUNCTION calcular_edad( p_fecha_nacimiento IN DATE) RETURN NUMBERDETERMINISTICISBEGIN RETURN TRUNC(MONTHS_BETWEEN(SYSDATE, p_fecha_nacimiento) / 12);END calcular_edad;/-- Primero definimos un tipo de tablaCREATE OR REPLACE TYPE empleado_info_type AS OBJECT ( id NUMBER, nombre VARCHAR2(100), departamento VARCHAR2(100));/
CREATE OR REPLACE TYPE empleado_info_table AS TABLE OF empleado_info_type;/
-- Luego creamos la función que devuelve este tipoCREATE OR REPLACE FUNCTION obtener_empleados_departamento( p_departamento_id IN NUMBER) RETURN empleado_info_tablePIPELINEDIS v_info empleado_info_type; CURSOR c_empleados IS SELECT e.empleado_id, e.nombre, d.nombre_departamento FROM empleados e JOIN departamentos d ON e.departamento_id = d.departamento_id WHERE e.departamento_id = p_departamento_id;BEGIN FOR emp IN c_empleados LOOP v_info := empleado_info_type(emp.empleado_id, emp.nombre, emp.nombre_departamento); PIPE ROW(v_info); END LOOP;
RETURN;END obtener_empleados_departamento;/Uso de funciones
Section titled “Uso de funciones”Las funciones pueden ser utilizadas en varios contextos:
-- En una consulta SELECTSELECT empleado_id, nombre, salario, calcular_salario_anual(empleado_id) AS salario_anualFROM empleados;
-- En una cláusula WHERESELECT * FROM empleadosWHERE calcular_edad(fecha_nacimiento) > 30;
-- En un bloque PL/SQLDECLARE v_salario_anual NUMBER;BEGIN v_salario_anual := calcular_salario_anual(101); DBMS_OUTPUT.PUT_LINE('Salario anual: ' || v_salario_anual);END;/
-- Usando una función que devuelve tablaSELECT * FROM TABLE(obtener_empleados_departamento(10));Diferencias entre procedimientos y funciones
Section titled “Diferencias entre procedimientos y funciones”| Característica | Procedimiento | Función |
|---|---|---|
| Valor de retorno | No devuelve valor | Siempre devuelve un valor |
| Uso en consultas SQL | No se puede usar en consultas | Se puede usar en SELECT, WHERE, etc. |
| Parámetros | IN, OUT, IN OUT | Principalmente IN (aunque puede tener OUT e IN OUT) |
| Propósito principal | Realizar operaciones | Calcular y devolver valores |
| Transacciones | Puede contener COMMIT/ROLLBACK | No debe contener COMMIT/ROLLBACK |
Gestión de funciones
Section titled “Gestión de funciones”-- Ver el código fuente de una funciónSELECT text FROM user_source WHERE name = 'CALCULAR_SALARIO_ANUAL' ORDER BY line;
-- Ver información sobre una funciónSELECT * FROM user_procedures WHERE object_name = 'CALCULAR_SALARIO_ANUAL' AND object_type = 'FUNCTION';
-- Eliminar una funciónDROP FUNCTION calcular_salario_anual;
-- Otorgar permisos de ejecuciónGRANT EXECUTE ON calcular_salario_anual TO usuario;Vistas
Section titled “Vistas”Las vistas son consultas almacenadas que se comportan como tablas virtuales. Proporcionan una capa de abstracción sobre las tablas base, permitiendo simplificar consultas complejas y controlar el acceso a los datos.
Concepto y aplicación de vistas
Section titled “Concepto y aplicación de vistas”Una vista es esencialmente una consulta SELECT almacenada que se puede referenciar como si fuera una tabla. Sin embargo, a diferencia de las tablas, las vistas no almacenan datos físicamente, sino que obtienen los datos de las tablas base cuando se consultan.
Principales usos de las vistas
Section titled “Principales usos de las vistas”- Simplificar consultas complejas: Encapsulan joins y operaciones complejas
- Restringir acceso a datos: Muestran solo columnas y filas específicas
- Proporcionar independencia de datos: Ocultan la estructura subyacente de las tablas
- Presentar datos calculados: Incluyen columnas derivadas o agregadas
- Ofrecer compatibilidad con versiones anteriores: Mantienen interfaces estables cuando cambia la estructura subyacente
Creación de vistas
Section titled “Creación de vistas”La sintaxis básica para crear una vista es:
CREATE [OR REPLACE] [FORCE | NOFORCE] VIEW nombre_vista [(alias_columna1, alias_columna2, ...)]AS consulta_select[WITH CHECK OPTION [CONSTRAINT nombre_restriccion]][WITH READ ONLY [CONSTRAINT nombre_restriccion]];Opciones importantes
Section titled “Opciones importantes”- OR REPLACE: Reemplaza la vista si ya existe
- FORCE: Crea la vista incluso si las tablas base no existen
- NOFORCE: Solo crea la vista si las tablas base existen (valor por defecto)
- WITH CHECK OPTION: Garantiza que las operaciones DML a través de la vista cumplan con la condición de la vista
- WITH READ ONLY: Impide operaciones DML a través de la vista
Ejemplos de vistas
Section titled “Ejemplos de vistas”-- Vista que muestra empleados con sus departamentosCREATE OR REPLACE VIEW vista_empleados_departamento ASSELECT e.empleado_id, e.nombre, e.apellido, e.salario, d.nombre_departamentoFROM empleados e JOIN departamentos d ON e.departamento_id = d.departamento_id;-- Vista con columnas calculadasCREATE OR REPLACE VIEW vista_resumen_ventas ASSELECT vendedor_id, COUNT(*) AS total_ventas, SUM(monto) AS monto_total, AVG(monto) AS monto_promedio, MIN(fecha) AS primera_venta, MAX(fecha) AS ultima_ventaFROM ventasGROUP BY vendedor_id;-- Vista con CHECK OPTIONCREATE OR REPLACE VIEW empleados_it ASSELECT empleado_id, nombre, apellido, email, telefono, fecha_contratacion, salario, departamento_idFROM empleadosWHERE departamento_id = 60 -- Departamento de ITWITH CHECK OPTION CONSTRAINT chk_empleados_it;
-- Vista de solo lecturaCREATE OR REPLACE VIEW informacion_confidencial ASSELECT e.empleado_id, e.nombre, e.apellido, e.salario, e.comision_pct, d.nombre_departamentoFROM empleados e JOIN departamentos d ON e.departamento_id = d.departamento_idWHERE e.salario > 10000WITH READ ONLY;Ventajas de reutilización y seguridad con vistas
Section titled “Ventajas de reutilización y seguridad con vistas”Ventajas de reutilización
Section titled “Ventajas de reutilización”- Consistencia: Garantizan que todos los usuarios y aplicaciones accedan a los datos de la misma manera
- Mantenibilidad: Centralizan la lógica de consulta, facilitando cambios futuros
- Simplificación: Reducen la complejidad de las consultas para los usuarios finales
- Rendimiento: Pueden mejorar el rendimiento cuando el optimizador de consultas las materializa
Ventajas de seguridad
Section titled “Ventajas de seguridad”- Control de acceso granular: Permiten mostrar solo columnas y filas específicas
- Ocultamiento de datos sensibles: Pueden excluir columnas con información confidencial
- Segregación de deberes: Facilitan la implementación de políticas de seguridad
- Auditoría: Pueden incluir información de auditoría como quién y cuándo modificó los datos
Tipos especiales de vistas
Section titled “Tipos especiales de vistas”Vistas materializadas
Section titled “Vistas materializadas”A diferencia de las vistas regulares, las vistas materializadas almacenan físicamente los resultados de la consulta, mejorando el rendimiento para consultas complejas o que acceden a grandes volúmenes de datos.
-- Crear una vista materializadaCREATE MATERIALIZED VIEW resumen_ventas_mensualBUILD IMMEDIATEREFRESH COMPLETE ON DEMANDASSELECT TO_CHAR(fecha, 'YYYY-MM') AS mes, producto_id, SUM(cantidad) AS total_unidades, SUM(monto) AS total_ventasFROM ventasGROUP BY TO_CHAR(fecha, 'YYYY-MM'), producto_id;
-- Actualizar una vista materializadaEXECUTE DBMS_MVIEW.REFRESH('resumen_ventas_mensual');Vistas dinámicas
Section titled “Vistas dinámicas”Las vistas dinámicas (prefijadas con V$) proporcionan información sobre el estado de la base de datos, como sesiones activas, estadísticas de rendimiento, etc.
-- Consultar sesiones activasSELECT * FROM v$session WHERE status = 'ACTIVE';
-- Consultar estadísticas de rendimientoSELECT * FROM v$sysstat WHERE name LIKE '%physical read%';Gestión de vistas
Section titled “Gestión de vistas”-- Ver la definición de una vistaSELECT text FROM user_views WHERE view_name = 'VISTA_EMPLEADOS_DEPARTAMENTO';
-- Ver información sobre una vistaSELECT * FROM user_views WHERE view_name = 'VISTA_EMPLEADOS_DEPARTAMENTO';
-- Eliminar una vistaDROP VIEW vista_empleados_departamento;
-- Otorgar permisos sobre una vistaGRANT SELECT ON vista_empleados_departamento TO usuario;Ejemplos prácticos de modularización
Section titled “Ejemplos prácticos de modularización”La combinación efectiva de procedimientos, funciones y vistas permite crear soluciones modulares y mantenibles. A continuación, se presentan algunos ejemplos prácticos.
Sistema de gestión de pedidos
Section titled “Sistema de gestión de pedidos”Este ejemplo muestra cómo modularizar un sistema de gestión de pedidos utilizando procedimientos, funciones y vistas.
1. Crear vistas para simplificar consultas
Section titled “1. Crear vistas para simplificar consultas”-- Vista para información completa de pedidosCREATE OR REPLACE VIEW vista_pedidos_completa ASSELECT p.pedido_id, p.fecha_pedido, p.estado, c.cliente_id, c.nombre AS nombre_cliente, c.email AS email_cliente, e.empleado_id, e.nombre AS nombre_empleado, SUM(d.cantidad * d.precio_unitario) AS total_pedidoFROM pedidos p JOIN clientes c ON p.cliente_id = c.cliente_id JOIN empleados e ON p.empleado_id = e.empleado_id JOIN detalles_pedido d ON p.pedido_id = d.pedido_idGROUP BY p.pedido_id, p.fecha_pedido, p.estado, c.cliente_id, c.nombre, c.email, e.empleado_id, e.nombre;2. Crear funciones para cálculos comunes
Section titled “2. Crear funciones para cálculos comunes”-- Función para calcular el total de un pedidoCREATE OR REPLACE FUNCTION calcular_total_pedido( p_pedido_id IN NUMBER) RETURN NUMBERIS v_total NUMBER;BEGIN SELECT SUM(cantidad * precio_unitario) INTO v_total FROM detalles_pedido WHERE pedido_id = p_pedido_id;
RETURN NVL(v_total, 0);END calcular_total_pedido;/
-- Función para verificar disponibilidad de inventarioCREATE OR REPLACE FUNCTION verificar_disponibilidad( p_producto_id IN NUMBER, p_cantidad IN NUMBER) RETURN BOOLEANIS v_stock NUMBER;BEGIN SELECT stock_disponible INTO v_stock FROM inventario WHERE producto_id = p_producto_id;
RETURN (v_stock >= p_cantidad);EXCEPTION WHEN NO_DATA_FOUND THEN RETURN FALSE;END verificar_disponibilidad;/3. Crear procedimientos para operaciones de negocio
Section titled “3. Crear procedimientos para operaciones de negocio”-- Procedimiento para crear un nuevo pedidoCREATE OR REPLACE PROCEDURE crear_pedido( p_cliente_id IN NUMBER, p_empleado_id IN NUMBER, p_pedido_id OUT NUMBER)ISBEGIN -- Generar nuevo ID de pedido SELECT seq_pedido.NEXTVAL INTO p_pedido_id FROM dual;
-- Insertar el pedido INSERT INTO pedidos (pedido_id, cliente_id, empleado_id, fecha_pedido, estado) VALUES (p_pedido_id, p_cliente_id, p_empleado_id, SYSDATE, 'PENDIENTE');
COMMIT;EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE;END crear_pedido;/
-- Procedimiento para añadir un artículo al pedidoCREATE OR REPLACE PROCEDURE agregar_articulo_pedido( p_pedido_id IN NUMBER, p_producto_id IN NUMBER, p_cantidad IN NUMBER, p_precio_unitario IN NUMBER, p_resultado OUT VARCHAR2)IS v_disponible BOOLEAN;BEGIN -- Verificar disponibilidad v_disponible := verificar_disponibilidad(p_producto_id, p_cantidad);
IF v_disponible THEN -- Añadir el artículo al pedido INSERT INTO detalles_pedido (pedido_id, producto_id, cantidad, precio_unitario) VALUES (p_pedido_id, p_producto_id, p_cantidad, p_precio_unitario);
-- Actualizar inventario UPDATE inventario SET stock_disponible = stock_disponible - p_cantidad WHERE producto_id = p_producto_id;
p_resultado := 'OK'; COMMIT; ELSE p_resultado := 'STOCK_INSUFICIENTE'; END IF;EXCEPTION WHEN OTHERS THEN ROLLBACK; p_resultado := 'ERROR: ' || SQLERRM;END agregar_articulo_pedido;/4. Utilizar los objetos en conjunto
Section titled “4. Utilizar los objetos en conjunto”-- Ejemplo de uso combinadoDECLARE v_pedido_id NUMBER; v_resultado VARCHAR2(200);BEGIN -- Crear un nuevo pedido crear_pedido(101, 201, v_pedido_id);
-- Añadir artículos al pedido agregar_articulo_pedido(v_pedido_id, 301, 5, 10.99, v_resultado); DBMS_OUTPUT.PUT_LINE('Resultado: ' || v_resultado);
agregar_articulo_pedido(v_pedido_id, 302, 2, 25.50, v_resultado); DBMS_OUTPUT.PUT_LINE('Resultado: ' || v_resultado);
-- Mostrar el total del pedido DBMS_OUTPUT.PUT_LINE('Total del pedido: ' || calcular_total_pedido(v_pedido_id));END;/Ventajas de la modularización
Section titled “Ventajas de la modularización”- Separación de responsabilidades: Cada objeto tiene una función específica y bien definida
- Reutilización de código: Las funciones y procedimientos pueden ser utilizados en múltiples contextos
- Mantenibilidad: Los cambios en la lógica de negocio solo necesitan hacerse en un lugar
- Seguridad: El acceso a los datos puede controlarse a nivel de objeto
- Rendimiento: La lógica se ejecuta en el servidor de base de datos, reduciendo el tráfico de red
Conclusión
Section titled “Conclusión”Los procedimientos, funciones y vistas son componentes fundamentales para desarrollar aplicaciones Oracle robustas, mantenibles y seguras. Estos objetos permiten encapsular la lógica de negocio, reutilizar código y controlar el acceso a los datos.
- Procedimientos almacenados: Ideales para operaciones que modifican datos y realizan múltiples acciones como parte de una transacción.
- Funciones almacenadas: Perfectas para cálculos y operaciones que necesitan devolver un valor y pueden ser utilizadas en consultas SQL.
- Vistas: Excelentes para simplificar consultas complejas, controlar el acceso a los datos y proporcionar una capa de abstracción sobre las tablas base.
La combinación efectiva de estos objetos permite crear soluciones modulares que son fáciles de mantener, escalar y asegurar. Al centralizar la lógica de negocio en la base de datos, se garantiza la consistencia y se reduce la duplicación de código en las aplicaciones cliente.