Skip to content

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

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.

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/SQL
EXCEPTION
-- Manejo de excepciones
WHEN excepcion1 THEN
-- Acciones para manejar excepcion1
WHEN OTHERS THEN
-- Acciones para manejar otras excepciones
END nombre_procedimiento;
/
  • 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
CREATE OR REPLACE PROCEDURE actualizar_salario(
p_empleado_id IN NUMBER,
p_porcentaje IN NUMBER
)
IS
BEGIN
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;
/

Hay varias formas de ejecutar un procedimiento almacenado:

-- Método 1: Usando EXECUTE o EXEC
EXECUTE actualizar_salario(101, 10);
EXEC actualizar_salario(101, 10);
-- Método 2: En un bloque PL/SQL anónimo
BEGIN
actualizar_salario(101, 10);
END;
/
-- Método 3: Con parámetros OUT
DECLARE
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
-- Ver el código fuente de un procedimiento
SELECT text FROM user_source WHERE name = 'ACTUALIZAR_SALARIO' ORDER BY line;
-- Ver información sobre un procedimiento
SELECT * FROM user_procedures WHERE object_name = 'ACTUALIZAR_SALARIO';
-- Eliminar un procedimiento
DROP PROCEDURE actualizar_salario;
-- Otorgar permisos de ejecución
GRANT EXECUTE ON actualizar_salario TO usuario;

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.

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;
/
  • 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
CREATE OR REPLACE FUNCTION calcular_salario_anual(
p_empleado_id IN NUMBER
) RETURN NUMBER
IS
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;
/

Las funciones pueden ser utilizadas en varios contextos:

-- En una consulta SELECT
SELECT
empleado_id,
nombre,
salario,
calcular_salario_anual(empleado_id) AS salario_anual
FROM empleados;
-- En una cláusula WHERE
SELECT * FROM empleados
WHERE calcular_edad(fecha_nacimiento) > 30;
-- En un bloque PL/SQL
DECLARE
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 tabla
SELECT * FROM TABLE(obtener_empleados_departamento(10));

Diferencias entre procedimientos y funciones

Section titled “Diferencias entre procedimientos y funciones”
CaracterísticaProcedimientoFunción
Valor de retornoNo devuelve valorSiempre devuelve un valor
Uso en consultas SQLNo se puede usar en consultasSe puede usar en SELECT, WHERE, etc.
ParámetrosIN, OUT, IN OUTPrincipalmente IN (aunque puede tener OUT e IN OUT)
Propósito principalRealizar operacionesCalcular y devolver valores
TransaccionesPuede contener COMMIT/ROLLBACKNo debe contener COMMIT/ROLLBACK
-- Ver el código fuente de una función
SELECT text FROM user_source WHERE name = 'CALCULAR_SALARIO_ANUAL' ORDER BY line;
-- Ver información sobre una función
SELECT * FROM user_procedures WHERE object_name = 'CALCULAR_SALARIO_ANUAL' AND object_type = 'FUNCTION';
-- Eliminar una función
DROP FUNCTION calcular_salario_anual;
-- Otorgar permisos de ejecución
GRANT EXECUTE ON calcular_salario_anual TO usuario;

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.

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.

  • 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

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]];
  • 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
-- Vista que muestra empleados con sus departamentos
CREATE OR REPLACE VIEW vista_empleados_departamento AS
SELECT
e.empleado_id,
e.nombre,
e.apellido,
e.salario,
d.nombre_departamento
FROM
empleados e
JOIN departamentos d ON e.departamento_id = d.departamento_id;

Ventajas de reutilización y seguridad con vistas

Section titled “Ventajas de reutilización y seguridad con vistas”
  • 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
  • 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

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 materializada
CREATE MATERIALIZED VIEW resumen_ventas_mensual
BUILD IMMEDIATE
REFRESH COMPLETE ON DEMAND
AS
SELECT
TO_CHAR(fecha, 'YYYY-MM') AS mes,
producto_id,
SUM(cantidad) AS total_unidades,
SUM(monto) AS total_ventas
FROM
ventas
GROUP BY
TO_CHAR(fecha, 'YYYY-MM'), producto_id;
-- Actualizar una vista materializada
EXECUTE DBMS_MVIEW.REFRESH('resumen_ventas_mensual');

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 activas
SELECT * FROM v$session WHERE status = 'ACTIVE';
-- Consultar estadísticas de rendimiento
SELECT * FROM v$sysstat WHERE name LIKE '%physical read%';
-- Ver la definición de una vista
SELECT text FROM user_views WHERE view_name = 'VISTA_EMPLEADOS_DEPARTAMENTO';
-- Ver información sobre una vista
SELECT * FROM user_views WHERE view_name = 'VISTA_EMPLEADOS_DEPARTAMENTO';
-- Eliminar una vista
DROP VIEW vista_empleados_departamento;
-- Otorgar permisos sobre una vista
GRANT SELECT ON vista_empleados_departamento TO usuario;

La combinación efectiva de procedimientos, funciones y vistas permite crear soluciones modulares y mantenibles. A continuación, se presentan algunos ejemplos prácticos.

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 pedidos
CREATE OR REPLACE VIEW vista_pedidos_completa AS
SELECT
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_pedido
FROM
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_id
GROUP BY
p.pedido_id, p.fecha_pedido, p.estado, c.cliente_id, c.nombre, c.email, e.empleado_id, e.nombre;
-- Función para calcular el total de un pedido
CREATE OR REPLACE FUNCTION calcular_total_pedido(
p_pedido_id IN NUMBER
) RETURN NUMBER
IS
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 inventario
CREATE OR REPLACE FUNCTION verificar_disponibilidad(
p_producto_id IN NUMBER,
p_cantidad IN NUMBER
) RETURN BOOLEAN
IS
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 pedido
CREATE OR REPLACE PROCEDURE crear_pedido(
p_cliente_id IN NUMBER,
p_empleado_id IN NUMBER,
p_pedido_id OUT NUMBER
)
IS
BEGIN
-- 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 pedido
CREATE 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;
/
-- Ejemplo de uso combinado
DECLARE
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;
/
  • 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

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.

🐝