SQL

Tutorial SQL: Manejo de errores y excepciones

Aprende a capturar y manejar errores y excepciones en SQL con MySQL y PostgreSQL para crear aplicaciones robustas y fiables.

Aprende SQL y certifícate

Captura errores

En SQL, la captura de errores es un aspecto fundamental para desarrollar aplicaciones robustas que puedan manejar situaciones inesperadas sin interrumpir su funcionamiento. Cuando ejecutamos consultas o procedimientos almacenados, diversos problemas pueden surgir: desde violaciones de restricciones hasta errores de sintaxis o problemas de conexión.

Estructura básica de captura de errores

Tanto MySQL como PostgreSQL ofrecen mecanismos para detectar y gestionar errores durante la ejecución de código SQL. La estructura básica sigue un patrón similar al de otros lenguajes de programación, aunque con sintaxis específica para cada sistema de gestión de bases de datos.

En MySQL, la estructura para capturar errores dentro de procedimientos almacenados utiliza el bloque DECLARE ... HANDLER:

DELIMITER //

CREATE PROCEDURE ejemplo_captura_error()
BEGIN
    -- Declaración de variables para manejar errores
    DECLARE exit_flag BOOLEAN DEFAULT FALSE;
    
    -- Declarar manejador de errores
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
    BEGIN
        SET exit_flag = TRUE;
        SELECT 'Ha ocurrido un error' AS mensaje;
    END;
    
    -- Código que podría generar un error
    INSERT INTO clientes (nombre, email) VALUES ('Ana López', 'ana@ejemplo.com');
    
    -- Verificar si ocurrió un error
    IF NOT exit_flag THEN
        SELECT 'Operación completada con éxito' AS mensaje;
    END IF;
END //

DELIMITER ;

En PostgreSQL, la estructura es diferente y utiliza bloques BEGIN ... EXCEPTION:

CREATE OR REPLACE FUNCTION ejemplo_captura_error()
RETURNS TEXT AS $$
BEGIN
    -- Código que podría generar un error
    INSERT INTO clientes (nombre, email) VALUES ('Ana López', 'ana@ejemplo.com');
    
    RETURN 'Operación completada con éxito';
EXCEPTION
    WHEN OTHERS THEN
        RETURN 'Ha ocurrido un error: ' || SQLERRM;
END;
$$ LANGUAGE plpgsql;

Tipos de errores en SQL

Para capturar errores de manera efectiva, es importante entender los diferentes tipos que pueden ocurrir:

  • Errores de sintaxis: Ocurren cuando la estructura de la consulta SQL no es correcta.
  • Errores de restricción: Suceden al violar reglas como claves únicas o restricciones de integridad referencial.
  • Errores de datos: Aparecen al intentar insertar datos incompatibles con el tipo de columna.
  • Errores de conexión: Ocurren cuando hay problemas para conectar con la base de datos.
  • Errores de tiempo de ejecución: Surgen durante la ejecución de procedimientos o funciones.

Captura de errores específicos

En lugar de capturar todos los errores de manera genérica, es más eficiente manejar tipos específicos de errores con respuestas adaptadas a cada situación.

En MySQL, podemos capturar códigos de error específicos:

DELIMITER //

CREATE PROCEDURE insertar_cliente(IN p_nombre VARCHAR(100), IN p_email VARCHAR(100))
BEGIN
    -- Manejador para error de duplicado (código 1062)
    DECLARE CONTINUE HANDLER FOR 1062
    BEGIN
        SELECT 'El cliente ya existe en la base de datos' AS mensaje;
    END;
    
    -- Manejador para error de columna no puede ser nula (código 1048)
    DECLARE CONTINUE HANDLER FOR 1048
    BEGIN
        SELECT 'Los datos del cliente no pueden estar vacíos' AS mensaje;
    END;
    
    -- Intentar insertar el cliente
    INSERT INTO clientes (nombre, email) VALUES (p_nombre, p_email);
    
    -- Si no hubo error, mostrar mensaje de éxito
    SELECT 'Cliente registrado correctamente' AS mensaje;
END //

DELIMITER ;

En PostgreSQL, podemos capturar excepciones específicas por su nombre:

CREATE OR REPLACE FUNCTION insertar_cliente(p_nombre VARCHAR, p_email VARCHAR)
RETURNS TEXT AS $$
BEGIN
    -- Intentar insertar el cliente
    INSERT INTO clientes (nombre, email) VALUES (p_nombre, p_email);
    
    RETURN 'Cliente registrado correctamente';
EXCEPTION
    WHEN unique_violation THEN
        RETURN 'El cliente ya existe en la base de datos';
    WHEN not_null_violation THEN
        RETURN 'Los datos del cliente no pueden estar vacíos';
    WHEN OTHERS THEN
        RETURN 'Error desconocido: ' || SQLERRM;
END;
$$ LANGUAGE plpgsql;

Obtención de información del error

Para un diagnóstico efectivo, es útil obtener información detallada sobre el error que ha ocurrido.

En MySQL, podemos usar variables como @error_code y @error_message:

DELIMITER //

CREATE PROCEDURE capturar_info_error()
BEGIN
    DECLARE error_code VARCHAR(5);
    DECLARE error_message VARCHAR(200);
    
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
    BEGIN
        GET DIAGNOSTICS CONDITION 1
            error_code = MYSQL_ERRNO,
            error_message = MESSAGE_TEXT;
        
        SELECT CONCAT('Error ', error_code, ': ', error_message) AS info_error;
    END;
    
    -- Código que podría generar un error
    DELETE FROM tabla_inexistente WHERE id = 1;
END //

DELIMITER ;

En PostgreSQL, podemos acceder a variables especiales dentro del bloque EXCEPTION:

CREATE OR REPLACE FUNCTION capturar_info_error()
RETURNS TEXT AS $$
BEGIN
    -- Código que podría generar un error
    DELETE FROM tabla_inexistente WHERE id = 1;
    
    RETURN 'Operación completada con éxito';
EXCEPTION
    WHEN OTHERS THEN
        RETURN 'Error ' || SQLSTATE || ': ' || SQLERRM;
END;
$$ LANGUAGE plpgsql;

Anidación de bloques de captura

Para manejar errores en diferentes niveles de ejecución, podemos anidar bloques de captura de errores.

En MySQL:

DELIMITER //

CREATE PROCEDURE proceso_complejo()
BEGIN
    DECLARE exit_handler_outer BOOLEAN DEFAULT FALSE;
    
    -- Manejador de errores externo
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
    BEGIN
        SET exit_handler_outer = TRUE;
        SELECT 'Error en el proceso principal' AS mensaje;
    END;
    
    -- Primer bloque de operaciones
    BEGIN
        DECLARE exit_handler_inner BOOLEAN DEFAULT FALSE;
        
        -- Manejador de errores interno
        DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
        BEGIN
            SET exit_handler_inner = TRUE;
            SELECT 'Error en el subproceso' AS mensaje;
        END;
        
        -- Operación que podría fallar
        UPDATE productos SET stock = stock - 1 WHERE id = 1;
        
        IF exit_handler_inner THEN
            SELECT 'Continuando después del error en subproceso' AS estado;
        END IF;
    END;
    
    -- Segunda operación
    IF NOT exit_handler_outer THEN
        INSERT INTO registro_operaciones (descripcion) VALUES ('Actualización completada');
    END IF;
END //

DELIMITER ;

En PostgreSQL:

CREATE OR REPLACE FUNCTION proceso_complejo()
RETURNS TEXT AS $$
DECLARE
    resultado TEXT;
BEGIN
    -- Bloque externo
    BEGIN
        -- Bloque interno
        BEGIN
            -- Operación que podría fallar
            UPDATE productos SET stock = stock - 1 WHERE id = 1;
            resultado := 'Actualización de stock completada';
        EXCEPTION
            WHEN OTHERS THEN
                resultado := 'Error en subproceso: ' || SQLERRM;
        END;
        
        -- Segunda operación
        INSERT INTO registro_operaciones (descripcion) VALUES ('Operación registrada');
        resultado := resultado || ' | Registro completado';
        
        RETURN resultado;
    EXCEPTION
        WHEN OTHERS THEN
            RETURN 'Error en proceso principal: ' || SQLERRM;
    END;
END;
$$ LANGUAGE plpgsql;

Captura de errores en transacciones

La captura de errores es especialmente importante en transacciones, donde necesitamos asegurar que todas las operaciones se completen correctamente o ninguna se aplique.

En MySQL:

DELIMITER //

CREATE PROCEDURE transferencia_segura(
    IN cuenta_origen INT,
    IN cuenta_destino INT,
    IN monto DECIMAL(10,2)
)
BEGIN
    DECLARE exit_flag BOOLEAN DEFAULT FALSE;
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
    BEGIN
        SET exit_flag = TRUE;
        ROLLBACK;
        SELECT 'Error en la transferencia, operación cancelada' AS resultado;
    END;
    
    START TRANSACTION;
    
    -- Retirar de cuenta origen
    UPDATE cuentas SET saldo = saldo - monto WHERE id = cuenta_origen;
    
    -- Depositar en cuenta destino
    UPDATE cuentas SET saldo = saldo + monto WHERE id = cuenta_destino;
    
    IF NOT exit_flag THEN
        COMMIT;
        SELECT 'Transferencia completada con éxito' AS resultado;
    END IF;
END //

DELIMITER ;

En PostgreSQL:

CREATE OR REPLACE FUNCTION transferencia_segura(
    cuenta_origen INT,
    cuenta_destino INT,
    monto DECIMAL
)
RETURNS TEXT AS $$
BEGIN
    -- Iniciar transacción
    BEGIN
        -- Retirar de cuenta origen
        UPDATE cuentas SET saldo = saldo - monto WHERE id = cuenta_origen;
        
        -- Depositar en cuenta destino
        UPDATE cuentas SET saldo = saldo + monto WHERE id = cuenta_destino;
        
        RETURN 'Transferencia completada con éxito';
    EXCEPTION
        WHEN OTHERS THEN
            RAISE NOTICE 'Error en la transferencia: %', SQLERRM;
            RETURN 'Error en la transferencia, operación cancelada';
    END;
END;
$$ LANGUAGE plpgsql;

La captura de errores en SQL es una práctica esencial que permite crear aplicaciones más robustas y confiables, capaces de manejar situaciones inesperadas de manera elegante y sin interrumpir el funcionamiento general del sistema.

Manejo excepciones

El manejo de excepciones en SQL representa un nivel superior de control de errores que permite responder de manera estructurada a situaciones anómalas durante la ejecución de procedimientos almacenados y funciones. A diferencia de la simple captura de errores, el manejo de excepciones proporciona mecanismos más sofisticados para identificar, clasificar y responder a diferentes tipos de problemas.

Excepciones en PostgreSQL

PostgreSQL ofrece un sistema de manejo de excepciones robusto inspirado en lenguajes como Ada y PL/SQL. Este sistema permite definir bloques de código con secciones específicas para el manejo de diferentes tipos de excepciones.

La estructura básica para manejar excepciones en PostgreSQL es:

BEGIN
    -- Código principal
EXCEPTION
    WHEN condición_excepción1 THEN
        -- Acciones para manejar excepción1
    WHEN condición_excepción2 THEN
        -- Acciones para manejar excepción2
    WHEN OTHERS THEN
        -- Acciones para cualquier otra excepción
END;

PostgreSQL proporciona un conjunto de excepciones predefinidas que podemos utilizar para manejar situaciones específicas:

CREATE OR REPLACE FUNCTION procesar_pedido(id_producto INT, cantidad INT)
RETURNS TEXT AS $$
BEGIN
    -- Verificar stock disponible
    UPDATE inventario 
    SET stock = stock - cantidad 
    WHERE producto_id = id_producto 
    AND stock >= cantidad;
    
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Stock insuficiente o producto no encontrado';
    END IF;
    
    -- Registrar pedido
    INSERT INTO pedidos (producto_id, cantidad, fecha)
    VALUES (id_producto, cantidad, CURRENT_DATE);
    
    RETURN 'Pedido procesado correctamente';
EXCEPTION
    WHEN check_violation THEN
        RETURN 'Error: La cantidad solicitada excede los límites permitidos';
    WHEN foreign_key_violation THEN
        RETURN 'Error: El producto especificado no existe';
    WHEN OTHERS THEN
        RETURN 'Error inesperado: ' || SQLERRM;
END;
$$ LANGUAGE plpgsql;

Excepciones personalizadas en PostgreSQL

PostgreSQL permite crear excepciones personalizadas utilizando la sentencia RAISE, lo que facilita el manejo de situaciones específicas de negocio:

CREATE OR REPLACE FUNCTION verificar_edad(fecha_nacimiento DATE)
RETURNS BOOLEAN AS $$
DECLARE
    edad INT;
BEGIN
    edad := EXTRACT(YEAR FROM AGE(CURRENT_DATE, fecha_nacimiento));
    
    IF edad < 18 THEN
        RAISE EXCEPTION 'edad_insuficiente'
            USING HINT = 'El usuario debe ser mayor de edad',
                  DETAIL = 'La edad calculada es ' || edad || ' años';
    END IF;
    
    RETURN TRUE;
EXCEPTION
    WHEN SQLSTATE '45000' THEN  -- Código para excepciones definidas por el usuario
        RAISE NOTICE 'Verificación fallida: %', SQLERRM;
        RETURN FALSE;
END;
$$ LANGUAGE plpgsql;

Manejo de excepciones en MySQL

MySQL implementa el manejo de excepciones a través de handlers, que son bloques de código que se ejecutan cuando se detecta un error específico. Aunque la sintaxis es diferente a PostgreSQL, el concepto es similar.

La estructura básica en MySQL es:

DELIMITER //

CREATE PROCEDURE ejemplo_manejo_excepciones()
BEGIN
    -- Declaración de handlers
    DECLARE CONTINUE HANDLER FOR 1062 
        SELECT 'Error: Entrada duplicada' AS mensaje;
    
    DECLARE EXIT HANDLER FOR 1146 
        SELECT 'Error: Tabla no existe' AS mensaje;
    
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
        SELECT 'Error SQL genérico' AS mensaje;
    
    -- Código principal que podría generar excepciones
    INSERT INTO usuarios (username, email) VALUES ('usuario1', 'correo@ejemplo.com');
    
    SELECT * FROM tabla_inexistente;
    
    SELECT 'Esta línea solo se ejecuta si no se activó un EXIT handler' AS estado;
END //

DELIMITER ;

MySQL ofrece tres tipos de handlers para manejar excepciones:

  • CONTINUE: Después de ejecutar el handler, la ejecución continúa con la siguiente instrucción.
  • EXIT: Después de ejecutar el handler, se termina el bloque actual.
  • UNDO: Similar a EXIT pero con rollback (disponible en algunas versiones).

Manejo de múltiples condiciones

En MySQL podemos agrupar múltiples condiciones en un solo handler, lo que resulta útil cuando queremos dar la misma respuesta a diferentes tipos de errores:

DELIMITER //

CREATE PROCEDURE procesar_datos()
BEGIN
    -- Handler para errores relacionados con restricciones
    DECLARE CONTINUE HANDLER FOR 1062, 1048, 1452
    BEGIN
        SELECT 'Error de integridad de datos' AS mensaje;
        -- Registrar el error en una tabla de log
        INSERT INTO error_log (codigo, mensaje, fecha)
        VALUES (MYSQL_ERRNO(), CONCAT('Error en procesar_datos: ', MYSQL_ERRNO()), NOW());
    END;
    
    -- Código principal
    INSERT INTO clientes (id, nombre, email) VALUES (1, 'Juan Pérez', 'juan@ejemplo.com');
END //

DELIMITER ;

Propagación de excepciones

En sistemas complejos, a veces es necesario propagar excepciones a niveles superiores para su manejo centralizado. Tanto PostgreSQL como MySQL permiten esta funcionalidad:

En PostgreSQL:

CREATE OR REPLACE FUNCTION subproceso()
RETURNS VOID AS $$
BEGIN
    -- Código que podría generar una excepción
    PERFORM 1/0;  -- División por cero
EXCEPTION
    WHEN division_by_zero THEN
        -- Propagar la excepción con información adicional
        RAISE EXCEPTION 'Error en subproceso: división por cero detectada'
            USING ERRCODE = SQLSTATE;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION proceso_principal()
RETURNS TEXT AS $$
BEGIN
    -- Llamar al subproceso
    PERFORM subproceso();
    RETURN 'Proceso completado';
EXCEPTION
    WHEN OTHERS THEN
        RETURN 'Error capturado en proceso principal: ' || SQLERRM;
END;
$$ LANGUAGE plpgsql;

En MySQL:

DELIMITER //

CREATE PROCEDURE subproceso()
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        -- Registrar el error
        INSERT INTO error_log (mensaje) VALUES ('Error en subproceso');
        -- Propagar el error
        RESIGNAL;
    END;
    
    -- Código que podría generar un error
    INSERT INTO tabla_inexistente VALUES (1);
END //

CREATE PROCEDURE proceso_principal()
BEGIN
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
    BEGIN
        SELECT 'Error capturado en proceso principal' AS mensaje;
    END;
    
    CALL subproceso();
    
    SELECT 'Proceso completado' AS estado;
END //

DELIMITER ;

Manejo de excepciones con variables de estado

Una técnica avanzada consiste en utilizar variables de estado para controlar el flujo después de una excepción:

DELIMITER //

CREATE PROCEDURE procesar_pedido(IN id_cliente INT, IN id_producto INT, IN cantidad INT)
BEGIN
    DECLARE error_ocurrido BOOLEAN DEFAULT FALSE;
    DECLARE stock_actual INT;
    
    -- Handler para cualquier error SQL
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
    BEGIN
        SET error_ocurrido = TRUE;
        -- Registrar el error
        INSERT INTO log_errores (codigo, mensaje, fecha)
        VALUES (MYSQL_ERRNO(), MYSQL_ERROR(), NOW());
    END;
    
    -- Verificar stock
    SELECT stock INTO stock_actual FROM inventario WHERE producto_id = id_producto;
    
    IF stock_actual < cantidad THEN
        SIGNAL SQLSTATE '45000' 
        SET MESSAGE_TEXT = 'Stock insuficiente';
    END IF;
    
    -- Si no hay error, procesar el pedido
    IF NOT error_ocurrido THEN
        START TRANSACTION;
        
        -- Actualizar inventario
        UPDATE inventario SET stock = stock - cantidad WHERE producto_id = id_producto;
        
        -- Crear pedido
        INSERT INTO pedidos (cliente_id, producto_id, cantidad, fecha)
        VALUES (id_cliente, id_producto, cantidad, NOW());
        
        IF error_ocurrido THEN
            ROLLBACK;
            SELECT 'Pedido cancelado debido a errores' AS resultado;
        ELSE
            COMMIT;
            SELECT 'Pedido procesado correctamente' AS resultado;
        END IF;
    ELSE
        SELECT 'No se pudo procesar el pedido' AS resultado;
    END IF;
END //

DELIMITER ;

Integración con el sistema de errores del cliente

El manejo de excepciones en SQL debe integrarse adecuadamente con el sistema de errores de la aplicación cliente. Esto implica devolver códigos de error y mensajes que la aplicación pueda interpretar:

CREATE OR REPLACE FUNCTION autenticar_usuario(
    p_username VARCHAR,
    p_password VARCHAR
)
RETURNS JSON AS $$
DECLARE
    usuario_id INT;
    resultado JSON;
BEGIN
    -- Buscar usuario
    SELECT id INTO usuario_id
    FROM usuarios
    WHERE username = p_username AND password_hash = crypt(p_password, password_hash);
    
    IF FOUND THEN
        resultado := json_build_object(
            'success', TRUE,
            'user_id', usuario_id,
            'message', 'Autenticación exitosa'
        );
    ELSE
        resultado := json_build_object(
            'success', FALSE,
            'error_code', 'AUTH_FAILED',
            'message', 'Credenciales inválidas'
        );
    END IF;
    
    RETURN resultado;
EXCEPTION
    WHEN OTHERS THEN
        RETURN json_build_object(
            'success', FALSE,
            'error_code', 'SYSTEM_ERROR',
            'message', 'Error interno del sistema',
            'details', SQLERRM
        );
END;
$$ LANGUAGE plpgsql;

El manejo efectivo de excepciones en SQL es una habilidad fundamental para desarrollar sistemas de bases de datos robustos y confiables. Al implementar estrategias adecuadas de manejo de excepciones, podemos crear aplicaciones que respondan elegantemente a situaciones inesperadas, mejorando la experiencia del usuario y facilitando el mantenimiento del sistema.

Mensajes personalizados

La personalización de mensajes de error en SQL es una técnica avanzada que permite mejorar significativamente la experiencia del usuario y facilitar la depuración de aplicaciones. En lugar de mostrar mensajes genéricos o códigos de error crípticos, podemos crear respuestas claras y específicas que comuniquen exactamente qué salió mal y cómo solucionarlo.

Creación de mensajes personalizados en MySQL

En MySQL, podemos generar mensajes personalizados utilizando la sentencia SIGNAL, que nos permite definir explícitamente el estado SQL y el texto del mensaje:

DELIMITER //

CREATE PROCEDURE verificar_saldo(IN cuenta_id INT, IN monto DECIMAL(10,2))
BEGIN
    DECLARE saldo_actual DECIMAL(10,2);
    
    -- Obtener saldo actual
    SELECT saldo INTO saldo_actual 
    FROM cuentas 
    WHERE id = cuenta_id;
    
    -- Verificar si la cuenta existe
    IF saldo_actual IS NULL THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'La cuenta especificada no existe';
    END IF;
    
    -- Verificar si hay saldo suficiente
    IF saldo_actual < monto THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = CONCAT(
            'Saldo insuficiente. Saldo actual: ', 
            saldo_actual, 
            ', Monto solicitado: ', 
            monto
        );
    END IF;
    
    SELECT 'Fondos disponibles' AS mensaje;
END //

DELIMITER ;

En este ejemplo, utilizamos SIGNAL para generar mensajes informativos que indican exactamente cuál es el problema: si la cuenta no existe o si el saldo es insuficiente. En el segundo caso, incluso incluimos los valores específicos para facilitar la comprensión del error.

Personalización avanzada con SIGNAL

La sentencia SIGNAL en MySQL permite incluir información adicional mediante varias cláusulas:

DELIMITER //

CREATE PROCEDURE registrar_usuario(
    IN p_username VARCHAR(50),
    IN p_email VARCHAR(100),
    IN p_edad INT
)
BEGIN
    -- Validar edad
    IF p_edad < 18 THEN
        SIGNAL SQLSTATE '45000'
        SET 
            MESSAGE_TEXT = 'El usuario debe ser mayor de edad',
            MYSQL_ERRNO = 30001,
            COLUMN_NAME = 'edad';
    END IF;
    
    -- Validar formato de email
    IF p_email NOT LIKE '%@%.%' THEN
        SIGNAL SQLSTATE '45000'
        SET 
            MESSAGE_TEXT = 'Formato de email inválido',
            MYSQL_ERRNO = 30002,
            COLUMN_NAME = 'email';
    END IF;
    
    -- Insertar usuario si todas las validaciones pasan
    INSERT INTO usuarios (username, email, edad)
    VALUES (p_username, p_email, p_edad);
    
    SELECT CONCAT('Usuario ', p_username, ' registrado correctamente') AS mensaje;
END //

DELIMITER ;

En este procedimiento, utilizamos SIGNAL con atributos adicionales:

  • MESSAGE_TEXT: El mensaje descriptivo del error
  • MYSQL_ERRNO: Un código de error personalizado
  • COLUMN_NAME: El nombre de la columna relacionada con el error

Estos atributos adicionales permiten a las aplicaciones cliente procesar el error de manera más efectiva y presentar mensajes más útiles al usuario final.

Mensajes personalizados en PostgreSQL

PostgreSQL ofrece la función RAISE para generar mensajes personalizados con diferentes niveles de severidad:

CREATE OR REPLACE FUNCTION validar_producto(
    p_nombre VARCHAR,
    p_precio DECIMAL,
    p_stock INT
)
RETURNS VOID AS $$
BEGIN
    -- Validar nombre
    IF length(p_nombre) < 3 THEN
        RAISE EXCEPTION 'El nombre del producto debe tener al menos 3 caracteres'
            USING HINT = 'Ingrese un nombre más descriptivo';
    END IF;
    
    -- Validar precio
    IF p_precio <= 0 THEN
        RAISE EXCEPTION 'El precio debe ser mayor que cero'
            USING DETAIL = 'Valor ingresado: %', p_precio;
    END IF;
    
    -- Validar stock
    IF p_stock < 0 THEN
        RAISE EXCEPTION 'El stock no puede ser negativo'
            USING ERRCODE = 'check_violation';
    END IF;
    
    -- Si todo está bien, mostrar mensaje informativo
    RAISE NOTICE 'Producto validado correctamente: %', p_nombre;
END;
$$ LANGUAGE plpgsql;

PostgreSQL permite diferentes niveles de severidad para los mensajes:

  • RAISE DEBUG: Mensajes de depuración (solo visibles si el nivel de depuración está activado)
  • RAISE LOG: Mensajes informativos que van al registro del servidor
  • RAISE NOTICE: Mensajes informativos visibles para el cliente
  • RAISE WARNING: Advertencias que no detienen la ejecución
  • RAISE EXCEPTION: Errores que interrumpen la ejecución

Personalización con parámetros dinámicos

Una técnica útil es incluir valores dinámicos en los mensajes para proporcionar contexto específico:

CREATE OR REPLACE FUNCTION transferir_fondos(
    origen_id INT,
    destino_id INT,
    monto DECIMAL
)
RETURNS TEXT AS $$
DECLARE
    saldo_origen DECIMAL;
    nombre_origen VARCHAR;
    nombre_destino VARCHAR;
BEGIN
    -- Obtener información de las cuentas
    SELECT saldo, nombre INTO saldo_origen, nombre_origen
    FROM cuentas WHERE id = origen_id;
    
    SELECT nombre INTO nombre_destino
    FROM cuentas WHERE id = destino_id;
    
    -- Validar que las cuentas existan
    IF nombre_origen IS NULL THEN
        RAISE EXCEPTION 'La cuenta de origen (ID: %) no existe', origen_id;
    END IF;
    
    IF nombre_destino IS NULL THEN
        RAISE EXCEPTION 'La cuenta de destino (ID: %) no existe', destino_id;
    END IF;
    
    -- Validar saldo suficiente
    IF saldo_origen < monto THEN
        RAISE EXCEPTION 'Saldo insuficiente en la cuenta de %: %.2f (requerido: %.2f)',
            nombre_origen, saldo_origen, monto;
    END IF;
    
    -- Realizar la transferencia
    UPDATE cuentas SET saldo = saldo - monto WHERE id = origen_id;
    UPDATE cuentas SET saldo = saldo + monto WHERE id = destino_id;
    
    RETURN format('Transferencia exitosa: %.2f de %s a %s', 
                 monto, nombre_origen, nombre_destino);
END;
$$ LANGUAGE plpgsql;

En este ejemplo, los mensajes incluyen información contextual como IDs, nombres y cantidades, lo que hace que sean mucho más útiles para diagnosticar problemas.

Mensajes personalizados con códigos de error específicos

Para sistemas más complejos, es útil definir un catálogo de códigos de error personalizados que permitan a las aplicaciones cliente identificar y manejar situaciones específicas:

DELIMITER //

CREATE PROCEDURE procesar_pedido(
    IN cliente_id INT,
    IN producto_id INT,
    IN cantidad INT
)
BEGIN
    DECLARE stock_disponible INT;
    DECLARE limite_credito DECIMAL(10,2);
    DECLARE total_pedido DECIMAL(10,2);
    
    -- Verificar existencia del cliente
    IF NOT EXISTS (SELECT 1 FROM clientes WHERE id = cliente_id) THEN
        SIGNAL SQLSTATE '45000'
        SET 
            MESSAGE_TEXT = 'Cliente no encontrado',
            MYSQL_ERRNO = 40001;
    END IF;
    
    -- Verificar existencia del producto
    IF NOT EXISTS (SELECT 1 FROM productos WHERE id = producto_id) THEN
        SIGNAL SQLSTATE '45000'
        SET 
            MESSAGE_TEXT = 'Producto no encontrado',
            MYSQL_ERRNO = 40002;
    END IF;
    
    -- Verificar stock
    SELECT stock INTO stock_disponible FROM productos WHERE id = producto_id;
    
    IF stock_disponible < cantidad THEN
        SIGNAL SQLSTATE '45000'
        SET 
            MESSAGE_TEXT = CONCAT('Stock insuficiente. Disponible: ', stock_disponible),
            MYSQL_ERRNO = 40003;
    END IF;
    
    -- Calcular total del pedido
    SELECT precio * cantidad INTO total_pedido FROM productos WHERE id = producto_id;
    
    -- Verificar límite de crédito
    SELECT credito_disponible INTO limite_credito FROM clientes WHERE id = cliente_id;
    
    IF limite_credito < total_pedido THEN
        SIGNAL SQLSTATE '45000'
        SET 
            MESSAGE_TEXT = CONCAT(
                'Límite de crédito excedido. Disponible: ', 
                limite_credito, 
                ', Requerido: ', 
                total_pedido
            ),
            MYSQL_ERRNO = 40004;
    END IF;
    
    -- Procesar pedido
    INSERT INTO pedidos (cliente_id, producto_id, cantidad, total)
    VALUES (cliente_id, producto_id, cantidad, total_pedido);
    
    -- Actualizar stock
    UPDATE productos SET stock = stock - cantidad WHERE id = producto_id;
    
    -- Actualizar crédito
    UPDATE clientes SET credito_disponible = credito_disponible - total_pedido 
    WHERE id = cliente_id;
    
    SELECT 'Pedido procesado correctamente' AS mensaje;
END //

DELIMITER ;

En este procedimiento, cada tipo de error tiene un código específico (40001, 40002, etc.), lo que permite a la aplicación cliente identificar exactamente qué tipo de problema ocurrió y responder adecuadamente.

Mensajes personalizados en diferentes idiomas

Para aplicaciones multilingües, podemos implementar un sistema de mensajes en diferentes idiomas:

CREATE TABLE mensajes_error (
    codigo VARCHAR(10),
    idioma VARCHAR(5),
    mensaje TEXT,
    PRIMARY KEY (codigo, idioma)
);

INSERT INTO mensajes_error VALUES
('ERR001', 'es', 'El cliente no existe'),
('ERR001', 'en', 'Customer does not exist'),
('ERR002', 'es', 'Saldo insuficiente'),
('ERR002', 'en', 'Insufficient balance');

DELIMITER //

CREATE PROCEDURE obtener_mensaje(
    IN p_codigo VARCHAR(10),
    IN p_idioma VARCHAR(5)
)
BEGIN
    DECLARE v_mensaje TEXT;
    
    -- Intentar obtener mensaje en el idioma solicitado
    SELECT mensaje INTO v_mensaje
    FROM mensajes_error
    WHERE codigo = p_codigo AND idioma = p_idioma;
    
    -- Si no existe, usar inglés como idioma por defecto
    IF v_mensaje IS NULL THEN
        SELECT mensaje INTO v_mensaje
        FROM mensajes_error
        WHERE codigo = p_codigo AND idioma = 'en';
    END IF;
    
    -- Si aún no existe, usar un mensaje genérico
    IF v_mensaje IS NULL THEN
        SET v_mensaje = CONCAT('Error desconocido: ', p_codigo);
    END IF;
    
    SELECT v_mensaje AS mensaje;
END //

DELIMITER ;

Esta técnica permite centralizar la gestión de mensajes de error y facilita la internacionalización de la aplicación.

Integración con el sistema de logging

Para facilitar la depuración y auditoría, es recomendable integrar los mensajes personalizados con un sistema de registro:

CREATE TABLE log_errores (
    id INT AUTO_INCREMENT PRIMARY KEY,
    fecha TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    usuario VARCHAR(50),
    codigo_error VARCHAR(10),
    mensaje TEXT,
    detalles JSON
);

DELIMITER //

CREATE PROCEDURE registrar_error(
    IN p_codigo VARCHAR(10),
    IN p_mensaje TEXT,
    IN p_detalles JSON
)
BEGIN
    INSERT INTO log_errores (usuario, codigo_error, mensaje, detalles)
    VALUES (CURRENT_USER(), p_codigo, p_mensaje, p_detalles);
END //

CREATE PROCEDURE validar_operacion(
    IN operacion_tipo VARCHAR(50),
    IN operacion_datos JSON
)
BEGIN
    DECLARE v_error_mensaje TEXT;
    DECLARE v_error_codigo VARCHAR(10);
    
    -- Validar operación
    CASE operacion_tipo
        WHEN 'TRANSFERENCIA' THEN
            IF JSON_EXTRACT(operacion_datos, '$.monto') <= 0 THEN
                SET v_error_codigo = 'ERR101';
                SET v_error_mensaje = 'El monto de transferencia debe ser positivo';
                
                -- Registrar error
                CALL registrar_error(
                    v_error_codigo,
                    v_error_mensaje,
                    operacion_datos
                );
                
                SIGNAL SQLSTATE '45000'
                SET MESSAGE_TEXT = v_error_mensaje,
                    MYSQL_ERRNO = 50000;
            END IF;
        WHEN 'RETIRO' THEN
            -- Otras validaciones...
        ELSE
            SET v_error_codigo = 'ERR999';
            SET v_error_mensaje = CONCAT('Tipo de operación desconocido: ', operacion_tipo);
            
            CALL registrar_error(
                v_error_codigo,
                v_error_mensaje,
                operacion_datos
            );
            
            SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = v_error_mensaje,
                MYSQL_ERRNO = 50999;
    END CASE;
    
    -- Si llegamos aquí, la operación es válida
    SELECT 'Operación validada correctamente' AS mensaje;
END //

DELIMITER ;

Este enfoque permite mantener un registro detallado de todos los errores que ocurren en el sistema, lo que facilita la identificación de patrones y la resolución de problemas recurrentes.

La implementación de mensajes personalizados en SQL es una práctica que mejora significativamente la calidad del software, facilitando tanto el desarrollo como el mantenimiento de aplicaciones basadas en bases de datos. Al proporcionar información clara y específica sobre los errores, ayudamos a los usuarios y desarrolladores a entender y resolver problemas rápidamente.

Aprende SQL online

Otras lecciones de SQL

Accede a todas las lecciones de SQL y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Sql

Introducción Y Entorno

Introducción A Ddl

Introducción Y Entorno

Instalación De Mysql

Introducción Y Entorno

Instalación De Postgresql

Introducción Y Entorno

Tipos De Datos

Introducción Y Entorno

Bases De Datos Y Tablas

Introducción Y Entorno

Sistemas De Gestión De Bases De Datos

Introducción Y Entorno

Tipos De Bases De Datos

Introducción Y Entorno

Definición De Ddl Y Dml

Introducción Y Entorno

Creación Con Create

Sintaxis Dml Crud

Consultas Básicas De Selección: Select Y Where

Sintaxis Dml Crud

Inserción De Datos: Insert Into

Sintaxis Dml Crud

Actualización De Datos: Update

Sintaxis Dml Crud

Eliminación De Datos: Delete

Sintaxis Dml Crud

Introducción A Dml

Sintaxis Dml Crud

Consultar Datos: Select

Sintaxis Dml Crud

Clasificación De Resultados Con Order By

Filtros Y Clasificación

Filtrado De Valores Únicos Con Distinct

Filtros Y Clasificación

Paginación Con Limit Y Offset

Filtros Y Clasificación

Modificar Con Alter

Sintaxis Ddl

Renombrar Con Rename

Sintaxis Ddl

Borrar Con Drop

Sintaxis Ddl

Uso De Funciones Agregadas: Count, Sum, Avg, Max, Min

Funciones Y Agrupación

Agrupación De Resultados Con Group By

Funciones Y Agrupación

Filtrado De Grupos De Resultados Con Having

Funciones Y Agrupación

Funciones Numéricas Y Matemáticas

Funciones Y Agrupación

Funciones De Fecha Y Hora

Funciones Y Agrupación

Funciones De Texto

Funciones Y Agrupación

Many To One

Asociaciones Entre Tablas

One To Many

Asociaciones Entre Tablas

One To One

Asociaciones Entre Tablas

Many To Many

Asociaciones Entre Tablas

Relaciones Entre Tablas

Joins Y Subqueries

Uso De Inner Join

Joins Y Subqueries

Creación Y Uso De Subqueries

Joins Y Subqueries

Left Join Y Right Join

Joins Y Subqueries

Full Join

Joins Y Subqueries

Cross Join Y Self Join

Joins Y Subqueries

Optimización De Consultas

Sintaxis Avanzada

Uso De Índices Y Particiones

Sintaxis Avanzada

Uso De Vistas

Sintaxis Avanzada

Triggers Y Eventos

Sintaxis Avanzada

Particiones

Sintaxis Avanzada

Restricciones E Integridad

Sintaxis Avanzada

Transacciones

Sintaxis Avanzada

Vistas Materializadas

Sintaxis Avanzada

Rollback

Sintaxis Avanzada

Vistas Con Create View

Sintaxis Avanzada

Principios Acid

Sintaxis Avanzada

Manejo De Errores Y Excepciones

Sintaxis Avanzada

Funciones Ventana

Sintaxis Avanzada

Índices

Sintaxis Avanzada

Expresiones De Tabla Comunes (Cte) Con With

Sintaxis Avanzada

Creación Y Uso De Funciones

Programación En Sql

Creación Y Uso De Procedimientos Almacenados

Programación En Sql

Variables Y Control De Flujo

Programación En Sql

Creación Y Manejo De Usuarios Y Roles

Seguridad Y Administración

Asignación Y Gestión De Permisos

Seguridad Y Administración

Copias De Seguridad Y Restauración De Bases De Datos

Seguridad Y Administración

Accede GRATIS a SQL y certifícate

Ejercicios de programación de SQL

Evalúa tus conocimientos de esta lección Manejo de errores y excepciones con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Tipos de datos

Test

Inserción de datos: INSERT INTO

Test

Filtrado de grupos de resultados con HAVING

Test

Uso de índices y particiones

Test

Renombrar tablas y bases de datos: RENAME

Test

Uso de vistas

Test

Uso de INNER JOIN, LEFT JOIN, RIGHT JOIN, FULL JOIN

Test

Agrupación de resultados con GROUP BY

Test

Creación y uso de subqueries

Test

Sentencias INSERT

Código

Copias de seguridad y restauración de bases de datos

Test

Uso de INNER JOIN, LEFT JOIN, RIGHT JOIN, FULL JOIN

Código

Instalación de MySQL

Test

Relaciones entre tablas

Código

Eliminación de datos: DELETE

Test

Creación de bases de datos y tablas: CREATE DATABASE, CREATE TABLE

Test

Creación y uso de funciones

Test

Creación de tablas e inserción de datos con SQL

Proyecto

Uso de funciones agregadas: COUNT, SUM, AVG, MAX, MIN

Test

Optimización de consultas

Test

Introducción a SQL

Test

Triggers y eventos

Test

Clasificación de resultados con ORDER BY

Test

Alterar la estructura de tablas existentes: ALTER TABLE

Test

Eliminación de datos: DELETE

Código

Instalación de PostgreSQL

Test

Creación y uso de procedimientos almacenados

Test

Consultas básicas de selección: SELECT y WHERE

Test

Vaciar tablas y bases de datos: DROP

Test

Actualización de datos: UPDATE

Test

Creación y manejo de usuarios y roles

Test

Consultas básicas de selección SELECT y WHERE

Código

Creación de bases de datos y tablas

Código

Bases de datos y tablas

Test

Actualización de datos: UPDATE

Código

Relaciones entre tablas

Test

Filtrado de valores únicos con DISTINCT

Test

Asignación y gestión de permisos

Test

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la importancia de la captura de errores en SQL para mantener la robustez de las aplicaciones.
  • Aprender la sintaxis y estructura para manejar errores y excepciones en MySQL y PostgreSQL.
  • Diferenciar entre captura genérica y específica de errores, y cómo implementar ambas.
  • Implementar mensajes de error personalizados para mejorar la comunicación con el usuario y facilitar la depuración.
  • Integrar el manejo de errores con transacciones y sistemas de logging para asegurar la integridad y trazabilidad.