DECLARE CONDITION
DECLARE CONDITION asigna un nombre descriptivo a un código de error de MySQL o a un código SQLSTATE. Por sí sola no maneja ningún error ni cambia el comportamiento del procedimiento: lo único que hace es crear un alias legible que luego puedes usar en los handlers y en las sentencias SIGNAL. Su valor es, por tanto, de claridad y mantenimiento.
La razón de ser de esta sentencia es que los códigos de error de MySQL son números crípticos. Cuando alguien lee DECLARE CONTINUE HANDLER FOR 1062, tiene que recordar o buscar qué significa 1062. En cambio, si ese código se ha bautizado antes como registro_duplicado, el handler se lee casi como una frase en lenguaje natural. En procedimientos largos con muchas validaciones, esta diferencia entre números y nombres marca la frontera entre un código mantenible y uno indescifrable. En este artículo veremos cómo declarar condiciones, cómo usarlas con códigos numéricos y con SQLSTATE, y una tabla de los errores más frecuentes que conviene nombrar.
Sintaxis
Una condición puede asociarse a un código de error numérico de MySQL o a un código SQLSTATE estándar. Ambas formas se escriben igual, cambiando solo la parte que sigue a FOR.
DECLARE nombre_condicion CONDITION FOR codigo_error;
DECLARE nombre_condicion CONDITION FOR SQLSTATE 'valor';Al igual que ocurre con los handlers, las condiciones deben declararse al principio del bloque, justo después de las variables y antes de los handlers que las utilicen. El orden importa: una condición tiene que existir antes de que un handler la nombre.
Sin DECLARE CONDITION
Para apreciar la diferencia, veamos primero cómo queda un handler que vigila un error usando directamente su número. El código funciona, pero el 1062 no dice nada por sí mismo a quien lo lee.
-- El número 1062 no es autoexplicativo
DECLARE CONTINUE HANDLER FOR 1062
SET v_error = 'Duplicado';Quien revise este fragmento meses después tendrá que recordar que 1062 corresponde a una entrada duplicada. Multiplica esto por diez o quince códigos distintos en un procedimiento real y el problema de legibilidad se vuelve evidente.
Con DECLARE CONDITION
La misma lógica, pero nombrando antes el código, se lee mucho mejor. Declaramos el alias una vez y a partir de ahí el handler habla de duplicado en lugar de 1062.
-- Mucho más legible
DECLARE duplicado CONDITION FOR 1062;
DECLARE CONTINUE HANDLER FOR duplicado
SET v_error = 'Duplicado';El comportamiento es idéntico al del ejemplo anterior; lo único que ha cambiado es que el código ahora se explica solo. Este es el beneficio esencial de DECLARE CONDITION.
Ejemplo completo
Veamos la técnica aplicada a un procedimiento real de inserción de clientes. Nombramos dos errores frecuentes (el de email duplicado y el de clave foránea inválida) y declaramos un handler para cada uno que asigna un mensaje comprensible. Si la inserción tiene éxito, la variable de resultado queda nula y se construye el mensaje de confirmación.
DELIMITER //
CREATE PROCEDURE insertar_cliente_seguro(
IN p_nombre VARCHAR(50),
IN p_apellidos VARCHAR(100),
IN p_email VARCHAR(100),
IN p_ciudad VARCHAR(50)
)
BEGIN
DECLARE registro_duplicado CONDITION FOR 1062;
DECLARE fk_invalida CONDITION FOR 1452;
DECLARE v_resultado VARCHAR(200);
DECLARE CONTINUE HANDLER FOR registro_duplicado
SET v_resultado = CONCAT('Error: el email ', p_email, ' ya está registrado');
DECLARE CONTINUE HANDLER FOR fk_invalida
SET v_resultado = 'Error: referencia a datos no existentes';
INSERT INTO clientes (nombre, apellidos, email, ciudad, telefono)
VALUES (p_nombre, p_apellidos, p_email, p_ciudad, '600000000');
IF v_resultado IS NULL THEN
SET v_resultado = CONCAT('Cliente ', p_nombre, ' insertado con ID ', LAST_INSERT_ID());
END IF;
SELECT v_resultado AS resultado;
END //
DELIMITER ;Fíjate en lo legibles que quedan las declaraciones de handler: FOR registro_duplicado y FOR fk_invalida se entienden sin consultar ninguna tabla de códigos. Al llamar al procedimiento con un email que ya existe, salta el handler de duplicado:
CALL insertar_cliente_seguro('María', 'García', 'maria.garcia@email.com', 'Madrid');| resultado |
|---|
| Error: el email maria.garcia@email.com ya está registrado |
Condiciones con SQLSTATE
Las condiciones no se limitan a códigos numéricos de MySQL. También puedes nombrar códigos SQLSTATE, que son más portables por pertenecer al estándar SQL. En el siguiente procedimiento bautizamos dos SQLSTATE habituales y declaramos sendos handlers que se apoyan en esos nombres.
DELIMITER //
CREATE PROCEDURE ejemplo_sqlstate()
BEGIN
-- Condiciones con nombre descriptivo
DECLARE restriccion_violada CONDITION FOR SQLSTATE '23000';
DECLARE tabla_no_existe CONDITION FOR SQLSTATE '42S02';
DECLARE v_error VARCHAR(200);
DECLARE CONTINUE HANDLER FOR restriccion_violada
SET v_error = 'Violación de restricción de integridad';
DECLARE CONTINUE HANDLER FOR tabla_no_existe
SET v_error = 'La tabla referenciada no existe';
-- Lógica...
SELECT COALESCE(v_error, 'Sin errores') AS estado;
END //
DELIMITER ;Una ventaja de nombrar SQLSTATE en lugar de códigos numéricos es que un mismo SQLSTATE suele agrupar varios errores relacionados. Por ejemplo, '23000' cubre tanto las claves foráneas como los índices únicos, así que un único nombre y un único handler abarcan toda la familia de violaciones de integridad.
Códigos de error comunes
Para que te sirva de referencia al nombrar condiciones, esta tabla recoge los errores que más aparecen en procedimientos almacenados, con un nombre sugerido para cada uno. El error 1062 ocurre cuando se viola un índice único, y el 1452 cuando se viola una clave foránea al insertar.
| Código MySQL | SQLSTATE | Nombre sugerido | Descripción |
|---|---|---|---|
| 1062 | 23000 | registro_duplicado | Entrada duplicada en índice único |
| 1452 | 23000 | fk_invalida | Violación de clave foránea al insertar |
| 1451 | 23000 | fk_referenciada | Violación de FK al eliminar padre |
| 1048 | 23000 | campo_nulo | Columna NOT NULL recibió NULL |
| 1146 | 42S02 | tabla_no_existe | La tabla no existe |
| 1054 | 42S22 | columna_no_existe | Columna desconocida |
| 1264 | 22003 | valor_fuera_rango | Valor fuera de rango para el tipo |
| 1406 | 22001 | dato_muy_largo | Dato demasiado largo para la columna |
Adoptar nombres consistentes como estos en todo tu código facilita que cualquiera que lo lea entienda de inmediato qué error está vigilando cada handler.
Ejemplo práctico
Para cerrar, un procedimiento de transferencia de stock entre dos productos que combina condiciones nombradas con validaciones manuales. Las condiciones documentan los errores que podrían surgir, mientras que la lógica comprueba explícitamente el stock disponible antes de mover unidades.
DELIMITER //
CREATE PROCEDURE transferir_stock(
IN p_origen INT,
IN p_destino INT,
IN p_cantidad INT
)
BEGIN
DECLARE producto_no_encontrado CONDITION FOR SQLSTATE '02000';
DECLARE stock_insuficiente CONDITION FOR SQLSTATE '45000';
DECLARE v_stock_origen INT;
DECLARE v_error VARCHAR(200);
DECLARE v_exito BOOLEAN DEFAULT FALSE;
DECLARE CONTINUE HANDLER FOR producto_no_encontrado
SET v_error = 'Producto no encontrado';
DECLARE CONTINUE HANDLER FOR stock_insuficiente
SET v_error = 'Stock insuficiente para la transferencia';
-- Verificar origen
SELECT stock INTO v_stock_origen FROM productos WHERE id = p_origen;
IF v_stock_origen IS NULL THEN
SET v_error = 'Producto origen no encontrado';
ELSEIF v_stock_origen < p_cantidad THEN
SET v_error = CONCAT('Stock insuficiente. Disponible: ', v_stock_origen);
ELSE
UPDATE productos SET stock = stock - p_cantidad WHERE id = p_origen;
UPDATE productos SET stock = stock + p_cantidad WHERE id = p_destino;
SET v_exito = TRUE;
END IF;
IF v_exito THEN
SELECT 'Transferencia completada' AS resultado, p_cantidad AS unidades;
ELSE
SELECT v_error AS resultado;
END IF;
END //
DELIMITER ;Este procedimiento ilustra que las condiciones nombradas y las comprobaciones manuales no se excluyen, sino que se complementan: las primeras documentan y manejan los errores que MySQL pueda lanzar, y las segundas anticipan situaciones de negocio antes de que se conviertan en errores.
Errores comunes
El descuido más típico es declarar las condiciones en un orden incorrecto dentro del bloque. Como toda declaración de un procedimiento, deben ir al principio: primero variables, después condiciones y luego handlers. Si nombras una condición después de un handler que la usa, MySQL no la reconocerá.
También es frecuente confundir el papel de DECLARE CONDITION. Por sí sola no captura nada ni reacciona ante el error; solo crea el alias. Para que algo ocurra cuando se produzca esa condición, necesitas además un DECLARE HANDLER que la utilice. Olvidar este segundo paso hace que el código parezca manejar el error cuando en realidad no hace nada con él.
Cuándo usar DECLARE CONDITION
Recurre a DECLARE CONDITION siempre que un procedimiento maneje varios códigos de error o que un mismo código aparezca repetido en distintos sitios. En esos casos, los nombres descriptivos mejoran notablemente la legibilidad y reducen el riesgo de erratas al teclear los números. En procedimientos muy cortos con un único error trivial, en cambio, puede que no aporte gran cosa y el código directo resulte igual de claro.
En el siguiente artículo veremos SIGNAL, que aprovecha precisamente estas condiciones nombradas para lanzar errores personalizados desde tus procedimientos.
Limpieza
Para terminar, eliminamos los procedimientos de ejemplo.
DROP PROCEDURE IF EXISTS insertar_cliente_seguro;
DROP PROCEDURE IF EXISTS ejemplo_sqlstate;
DROP PROCEDURE IF EXISTS transferir_stock;Escrito por Eduardo Lázaro
