SIGNAL
SIGNAL es la sentencia que MySQL ofrece para lanzar un error o una advertencia de forma deliberada desde dentro de un procedimiento almacenado, una función, un trigger o un evento. Es el equivalente directo a throw en Java, raise en Python o throw new Error() en JavaScript: interrumpe la ejecución normal y devuelve a quien llamó al código un mensaje de error que tú mismo defines.
Su utilidad principal es trasladar las reglas de negocio al propio motor de la base de datos. En lugar de confiar en que cada aplicación que se conecta valide los datos antes de insertarlos, puedes hacer que sea la base de datos quien rechace de forma centralizada una edad negativa, un precio igual a cero o una referencia a una categoría que no existe. Así, sin importar si los datos llegan desde una aplicación PHP, un script de Python o la consola de MySQL, las mismas validaciones se aplican siempre. En este artículo veremos cómo se construye un SIGNAL, qué información puedes adjuntar al error, cómo distinguir entre errores y advertencias, y cómo se relaciona con la captura de errores mediante handlers.
Sintaxis
La forma básica de SIGNAL consta de dos partes: la palabra clave SIGNAL SQLSTATE seguida del código de estado que identifica el error, y una cláusula SET opcional donde defines las propiedades del error, como el texto del mensaje o un código numérico propio.
SIGNAL SQLSTATE 'valor'
SET MESSAGE_TEXT = 'mensaje de error',
MYSQL_ERRNO = codigo;El 'valor' es siempre una cadena de cinco caracteres que representa un código SQLSTATE. Este código es un estándar SQL que clasifica el tipo de condición que se ha producido. La cláusula SET admite varias propiedades separadas por comas, todas opcionales, aunque en la práctica MESSAGE_TEXT casi siempre está presente porque es lo que verá la persona o aplicación que reciba el error.
SQLSTATE para errores personalizados
No puedes elegir cualquier código SQLSTATE: muchos están reservados por el estándar para condiciones concretas (por ejemplo, los que empiezan por '23' se refieren a violaciones de integridad). Para los errores que tú defines, MySQL reserva el valor genérico '45000', que significa "excepción definida por el usuario, sin más subclase". Es el código que usarás en la inmensa mayoría de los casos.
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Error personalizado';Cuando este SIGNAL se ejecuta, MySQL devuelve el error genérico número 1644 acompañado del SQLSTATE '45000' y del mensaje que indiques. Es la base sobre la que se construyen todos los ejemplos siguientes.
Ejemplo básico
Veamos SIGNAL funcionando dentro de un procedimiento real. El siguiente procedimiento recibe una edad y la valida: si es negativa o exageradamente alta, lanza un error que detiene la ejecución; si es razonable, devuelve un mensaje de confirmación. Fíjate en cómo combinamos SIGNAL con una sentencia IF para decidir cuándo lanzar el error.
DELIMITER //
CREATE PROCEDURE verificar_edad(IN edad INT)
BEGIN
IF edad < 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'La edad no puede ser negativa';
ELSEIF edad > 150 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'La edad no parece válida';
END IF;
SELECT CONCAT('Edad válida: ', edad) AS resultado;
END //
DELIMITER ;Usamos DELIMITER para cambiar temporalmente el separador de sentencias a //, de modo que los puntos y coma internos del procedimiento no lo corten antes de tiempo. Una vez creado el procedimiento, podemos llamarlo con un valor válido para comprobar que el flujo normal funciona:
CALL verificar_edad(25);Como 25 no entra en ninguna de las condiciones de error, el procedimiento llega hasta el SELECT final y devuelve el mensaje de confirmación:
| resultado |
|---|
| Edad válida: 25 |
En cambio, si llamamos al procedimiento con un valor negativo, el primer IF se cumple y el SIGNAL se dispara. La ejecución se detiene en ese punto: el SELECT final nunca llega a ejecutarse y MySQL devuelve el error en su lugar.
-- CALL verificar_edad(-5);
-- Error 1644 (45000): La edad no puede ser negativaPropiedades de SIGNAL
El mensaje de texto no es lo único que puedes adjuntar a un error. La cláusula SET admite varias propiedades, llamadas formalmente "ítems de información de condición", que enriquecen el error con contexto adicional. Esto es especialmente útil cuando una aplicación captura el error y quiere reaccionar de forma distinta según el código numérico o saber a qué tabla y columna se refería el problema.
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Descripción del error',
MYSQL_ERRNO = 5001,
TABLE_NAME = 'productos',
COLUMN_NAME = 'precio';Cada propiedad cumple una función concreta. La más importante después del mensaje es MYSQL_ERRNO, un código numérico propio que permite a la aplicación identificar el tipo de error de forma programática sin tener que analizar el texto. La tabla siguiente resume las propiedades más habituales:
| Propiedad | Descripción |
|---|---|
| MESSAGE_TEXT | Mensaje descriptivo del error |
| MYSQL_ERRNO | Código de error numérico personalizado |
| TABLE_NAME | Nombre de tabla relacionada |
| COLUMN_NAME | Nombre de columna relacionada |
| SCHEMA_NAME | Nombre del esquema |
| CURSOR_NAME | Nombre del cursor |
Conviene reservar los valores de MYSQL_ERRNO por encima de un rango alto (por ejemplo, a partir de 5000) para que no choquen con los códigos de error internos de MySQL, que ocupan los valores bajos.
Validación de datos
El uso más extendido de SIGNAL es construir una capa de validación dentro de un procedimiento de inserción. La idea es comprobar cada regla de negocio una por una y, en cuanto alguna falla, lanzar un error con un mensaje y un código específicos. Solo si todas las validaciones se superan se ejecuta el INSERT. El siguiente procedimiento de creación de productos ilustra este patrón completo.
DELIMITER //
CREATE PROCEDURE crear_producto(
IN p_nombre VARCHAR(100),
IN p_precio DECIMAL(10,2),
IN p_stock INT,
IN p_categoria_id INT
)
BEGIN
-- Validaciones
IF p_nombre IS NULL OR TRIM(p_nombre) = '' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'El nombre del producto es obligatorio',
MYSQL_ERRNO = 5001;
END IF;
IF p_precio IS NULL OR p_precio <= 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'El precio debe ser mayor que cero',
MYSQL_ERRNO = 5002;
END IF;
IF p_stock IS NULL OR p_stock < 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'El stock no puede ser negativo',
MYSQL_ERRNO = 5003;
END IF;
-- Verificar que la categoría existe
IF NOT EXISTS (SELECT 1 FROM categorias WHERE id = p_categoria_id) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'La categoría especificada no existe',
MYSQL_ERRNO = 5004;
END IF;
-- Si todas las validaciones pasan, insertar
INSERT INTO productos (nombre, precio, stock, categoria_id, activo)
VALUES (p_nombre, p_precio, p_stock, p_categoria_id, TRUE);
SELECT LAST_INSERT_ID() AS nuevo_id, p_nombre AS producto;
END //
DELIMITER ;Cada validación tiene su propio MYSQL_ERRNO (5001 para el nombre, 5002 para el precio, etc.), de manera que la aplicación que llama al procedimiento puede saber exactamente qué regla se incumplió sin depender del idioma o la redacción del mensaje. Observa también la cuarta validación: usa una subconsulta con NOT EXISTS para comprobar que la categoría referenciada existe realmente, una integridad que conviene verificar antes de insertar. Al probar el procedimiento con datos inválidos, el error se dispara en la primera validación que falla:
-- CALL crear_producto('', 100, 10, 6);
-- Error 1644 (45000): El nombre del producto es obligatorio
-- CALL crear_producto('Test', -50, 10, 6);
-- Error 1644 (45000): El precio debe ser mayor que ceroSIGNAL como advertencia
No todo lo que señalas tiene que detener la ejecución. Si en lugar de un código '45000' usas un SQLSTATE que empiece por '01', MySQL trata la condición como una advertencia (warning) en lugar de un error. La diferencia es fundamental: una advertencia se registra y puede consultarse después, pero no interrumpe el procedimiento, que continúa ejecutándose con normalidad.
DELIMITER //
CREATE PROCEDURE verificar_stock_minimo(IN prod_id INT)
BEGIN
DECLARE v_nombre VARCHAR(100);
DECLARE v_stock INT;
SELECT nombre, stock INTO v_nombre, v_stock
FROM productos WHERE id = prod_id;
IF v_stock < 10 THEN
SIGNAL SQLSTATE '01000'
SET MESSAGE_TEXT = 'Stock bajo, considerar reposición';
END IF;
SELECT v_nombre AS producto, v_stock AS stock;
END //
DELIMITER ;En este procedimiento, aunque el stock sea inferior a diez unidades y se lance la advertencia, el SELECT final se ejecuta igualmente y devuelve los datos del producto. El cliente recibe la advertencia, que puede inspeccionar con SHOW WARNINGS, pero la operación se completa. Es el mecanismo idóneo para avisos no bloqueantes.
Capturar errores SIGNAL
Lanzar un error no siempre significa propagarlo hasta la aplicación. A veces quieres atraparlo dentro del propio procedimiento para reaccionar de forma controlada: registrar el problema, devolver un valor por defecto o continuar con otra rama de lógica. Para eso se usa un handler declarado con DECLARE HANDLER, que intercepta la condición indicada.
DELIMITER //
CREATE PROCEDURE ejemplo_capturar_signal()
BEGIN
DECLARE v_error VARCHAR(200);
DECLARE CONTINUE HANDLER FOR SQLSTATE '45000'
BEGIN
GET DIAGNOSTICS CONDITION 1
v_error = MESSAGE_TEXT;
END;
-- Esto lanza un SIGNAL
CALL verificar_edad(-10);
IF v_error IS NOT NULL THEN
SELECT CONCAT('Error capturado: ', v_error) AS resultado;
END IF;
END //
DELIMITER ;El handler declarado como CONTINUE para el SQLSTATE '45000' se activa en cuanto la llamada a verificar_edad(-10) lanza su SIGNAL. Dentro del handler, GET DIAGNOSTICS extrae el MESSAGE_TEXT del error que se acaba de producir y lo guarda en una variable, de modo que el procedimiento puede mostrarlo o registrarlo en lugar de abortar. Es la combinación habitual: un procedimiento lanza el error con SIGNAL y otro, más arriba, lo captura y decide qué hacer con él.
Condición nombrada con SIGNAL
Cuando reutilizas el mismo SQLSTATE en muchos sitios, repetir '45000' por todas partes resulta poco legible y propenso a erratas. La solución es darle un nombre con DECLARE ... CONDITION y luego referirte a ese nombre en el SIGNAL. El código gana en claridad porque el nombre describe la intención del error.
DELIMITER //
CREATE PROCEDURE ejemplo_condicion_signal()
BEGIN
DECLARE precio_invalido CONDITION FOR SQLSTATE '45000';
IF TRUE THEN -- Ejemplo simplificado
SIGNAL precio_invalido
SET MESSAGE_TEXT = 'El precio no es válido para esta operación';
END IF;
END //
DELIMITER ;Aquí precio_invalido es simplemente un alias del SQLSTATE '45000'. Lanzar SIGNAL precio_invalido es exactamente equivalente a lanzar SIGNAL SQLSTATE '45000', pero el nombre comunica de un vistazo qué tipo de problema representa, lo que facilita el mantenimiento de procedimientos largos con muchas validaciones.
Errores comunes
El fallo más frecuente al empezar con SIGNAL es olvidar que la sentencia solo puede usarse dentro de un contexto de rutina almacenada: procedimientos, funciones, triggers o eventos. Si intentas ejecutar un SIGNAL directamente en la consola de MySQL, obtendrás un error de sintaxis, porque fuera de una rutina no tiene sentido.
Otro error habitual es elegir mal el SQLSTATE. Si usas accidentalmente un código reservado por el estándar en lugar de '45000', MySQL puede rechazarlo o interpretarlo de una forma inesperada. Para errores propios, manténte siempre en '45000' salvo que tengas una razón muy concreta para usar otro. También es fácil confundir el prefijo de las advertencias: un SQLSTATE que empieza por '01' es una advertencia que no detiene la ejecución, mientras que '45000' sí la detiene; usar uno cuando querías el otro provoca comportamientos difíciles de depurar.
Por último, recuerda que MESSAGE_TEXT tiene un límite de longitud (128 caracteres en las versiones actuales de MySQL). Si construyes mensajes muy largos concatenando datos, el texto se truncará silenciosamente, así que conviene mantenerlos concisos y apoyarte en MYSQL_ERRNO para la lógica de la aplicación.
Cuándo usar SIGNAL
SIGNAL es la herramienta adecuada siempre que quieras hacer cumplir reglas de negocio que van más allá de lo que las restricciones de tabla pueden expresar. Una columna NOT NULL o una clave foránea cubren casos sencillos, pero validaciones como "el precio de un producto en oferta debe ser menor que su precio original" o "no se puede cancelar un pedido que ya está enviado" requieren lógica condicional que solo encaja dentro de un procedimiento, y ahí es donde SIGNAL brilla.
También es la opción correcta cuando quieres garantizar que la validación se aplica de manera uniforme independientemente de qué cliente acceda a la base de datos, o cuando necesitas devolver mensajes de error claros y códigos numéricos consistentes a la capa de aplicación. En el siguiente artículo veremos RESIGNAL, que permite relanzar o modificar un error ya existente desde dentro de un handler, completando así el ciclo de manejo de errores en MySQL.
Limpieza
Para dejar la base de datos como estaba, eliminamos todos los procedimientos creados en los ejemplos. Conviene usar IF EXISTS para que el comando no falle si alguno no llegó a crearse.
DROP PROCEDURE IF EXISTS verificar_edad;
DROP PROCEDURE IF EXISTS crear_producto;
DROP PROCEDURE IF EXISTS verificar_stock_minimo;
DROP PROCEDURE IF EXISTS ejemplo_capturar_signal;
DROP PROCEDURE IF EXISTS ejemplo_condicion_signal;Escrito por Eduardo Lázaro
