Sentencia IF

La sentencia IF permite ejecutar diferentes bloques de código según una condición. Es la estructura de control de flujo más básica dentro de un procedimiento almacenado.

Hasta ahora, los procedimientos que hemos visto ejecutan sus sentencias en orden secuencial: primero esta consulta, luego esta otra, después el SELECT final. Pero la verdadera potencia de los procedimientos aparece cuando puedes tomar decisiones: si el stock es suficiente, procesa el pedido; si no, devuelve un mensaje de error. Si el cliente es VIP, aplica un descuento mayor. La sentencia IF es la herramienta que permite este tipo de lógica condicional.

Sintaxis

MySQL ofrece tres formas de la sentencia IF, desde la más simple hasta la más completa:

-- IF simple
IF condicion THEN
    sentencias;
END IF;
 
-- IF...ELSE
IF condicion THEN
    sentencias;
ELSE
    sentencias;
END IF;
 
-- IF...ELSEIF...ELSE
IF condicion1 THEN
    sentencias;
ELSEIF condicion2 THEN
    sentencias;
ELSEIF condicion3 THEN
    sentencias;
ELSE
    sentencias;
END IF;

Es importante no confundir la sentencia IF del procedimiento (que ocupa varias líneas y termina con END IF) con la función IF() de MySQL (que se usa en SELECTs como IF(condicion, valor_si, valor_no)). Son mecanismos diferentes: la sentencia IF controla el flujo de ejecución del procedimiento, mientras que la función IF() es una expresión que devuelve un valor.

IF simple

La forma más básica ejecuta un bloque de código solo si la condición es verdadera. Si la condición es falsa, no hace nada y continúa con la siguiente sentencia después del END IF:

DELIMITER //
 
CREATE PROCEDURE verificar_stock(IN prod_id INT)
BEGIN
    DECLARE v_stock INT;
    DECLARE v_nombre VARCHAR(100);
 
    SELECT nombre, stock INTO v_nombre, v_stock
    FROM productos WHERE id = prod_id;
 
    IF v_stock = 0 THEN
        SELECT v_nombre AS producto, 'SIN STOCK' AS estado;
    END IF;
 
    IF v_stock > 0 THEN
        SELECT v_nombre AS producto, 'Disponible' AS estado, v_stock AS unidades;
    END IF;
END //
 
DELIMITER ;
CALL verificar_stock(1);
productoestadounidades
iPhone 15 ProDisponible45

Aunque este ejemplo funciona, usar dos IF separados no es la mejor opción: si el stock fuera NULL (producto no encontrado), ninguno de los dos IF se ejecutaría y el procedimiento no devolvería nada. Además, ambas condiciones se evalúan siempre, aunque sean mutuamente excluyentes. Para estos casos es mejor usar IF...ELSE.

IF...ELSE

Cuando necesitas exactamente dos caminos (esto o lo otro), IF...ELSE garantiza que siempre se ejecute uno de los dos bloques:

DELIMITER //
 
CREATE PROCEDURE clasificar_precio(IN prod_id INT)
BEGIN
    DECLARE v_nombre VARCHAR(100);
    DECLARE v_precio DECIMAL(10,2);
 
    SELECT nombre, precio INTO v_nombre, v_precio
    FROM productos WHERE id = prod_id;
 
    IF v_precio > 500 THEN
        SELECT v_nombre AS producto, v_precio AS precio, 'Premium' AS categoria;
    ELSE
        SELECT v_nombre AS producto, v_precio AS precio, 'Estándar' AS categoria;
    END IF;
END //
 
DELIMITER ;
CALL clasificar_precio(1);
productopreciocategoria
iPhone 15 Pro1299.99Premium
CALL clasificar_precio(22);
productopreciocategoria
Camiseta algodón básica19.99Estándar

Con IF...ELSE, siempre hay un resultado. Si el precio es mayor que 500, se devuelve "Premium"; en cualquier otro caso (incluido NULL), se devuelve "Estándar". Esto hace que el procedimiento sea más predecible y fácil de razonar.

IF...ELSEIF...ELSE

Cuando necesitas clasificar un valor en más de dos categorías, puedes encadenar múltiples condiciones con ELSEIF. MySQL evalúa las condiciones de arriba a abajo y ejecuta el bloque de la primera que sea verdadera. Si ninguna es verdadera, ejecuta el bloque ELSE:

DELIMITER //
 
CREATE PROCEDURE nivel_stock(IN prod_id INT)
BEGIN
    DECLARE v_nombre VARCHAR(100);
    DECLARE v_stock INT;
    DECLARE v_nivel VARCHAR(20);
 
    SELECT nombre, stock INTO v_nombre, v_stock
    FROM productos WHERE id = prod_id;
 
    IF v_stock = 0 THEN
        SET v_nivel = 'Agotado';
    ELSEIF v_stock < 20 THEN
        SET v_nivel = 'Bajo';
    ELSEIF v_stock < 50 THEN
        SET v_nivel = 'Normal';
    ELSEIF v_stock < 100 THEN
        SET v_nivel = 'Alto';
    ELSE
        SET v_nivel = 'Sobrestock';
    END IF;
 
    SELECT v_nombre AS producto, v_stock AS stock, v_nivel AS nivel;
END //
 
DELIMITER ;
CALL nivel_stock(7);
productostocknivel
ASUS ROG Zephyrus12Bajo
CALL nivel_stock(4);
productostocknivel
Xiaomi 1480Alto

El orden de las condiciones importa. Como MySQL ejecuta la primera que sea verdadera, las condiciones deben ir de la más restrictiva a la menos restrictiva. Si pusieras v_stock < 100 antes que v_stock < 20, todos los productos con stock menor a 100 entrarían en esa rama y nunca llegarían a "Bajo". Es el mismo principio que en cualquier cadena de if/else if en otros lenguajes.

IF anidados

Puedes anidar sentencias IF dentro de otras sentencias IF para modelar lógica más compleja. Cada IF interior necesita su propio END IF:

DELIMITER //
 
CREATE PROCEDURE evaluar_producto(IN prod_id INT)
BEGIN
    DECLARE v_nombre VARCHAR(100);
    DECLARE v_precio DECIMAL(10,2);
    DECLARE v_stock INT;
    DECLARE v_mensaje VARCHAR(200);
 
    SELECT nombre, precio, stock INTO v_nombre, v_precio, v_stock
    FROM productos WHERE id = prod_id;
 
    IF v_stock > 0 THEN
        IF v_precio > 1000 THEN
            SET v_mensaje = 'Producto premium disponible';
        ELSE
            SET v_mensaje = 'Producto disponible';
        END IF;
    ELSE
        IF v_precio > 1000 THEN
            SET v_mensaje = 'Producto premium AGOTADO - Reponer urgente';
        ELSE
            SET v_mensaje = 'Producto agotado';
        END IF;
    END IF;
 
    SELECT v_nombre AS producto, v_precio AS precio, v_stock AS stock, v_mensaje AS mensaje;
END //
 
DELIMITER ;

Los IF anidados son poderosos pero pueden volverse difíciles de leer si se anidan demasiados niveles (tres o más niveles de anidamiento suelen ser señal de que el código necesita reestructurarse). Cuando la lógica se vuelve compleja, considera extraer parte de ella a otro procedimiento o usar CASE para las clasificaciones de valores.

Ejemplo práctico: validar pedido

Este ejemplo muestra un patrón habitual en producción: una serie de validaciones secuenciales donde cada una puede abortar la operación. Las condiciones se evalúan en orden, y la primera que falle determina el mensaje de error:

DELIMITER //
 
CREATE PROCEDURE validar_pedido(
    IN p_cliente_id INT,
    IN p_producto_id INT,
    IN p_cantidad INT,
    OUT p_valido BOOLEAN,
    OUT p_mensaje VARCHAR(200)
)
BEGIN
    DECLARE v_cliente_existe INT;
    DECLARE v_stock INT;
    DECLARE v_activo BOOLEAN;
 
    SET p_valido = FALSE;
 
    -- Verificar cliente
    SELECT COUNT(*) INTO v_cliente_existe
    FROM clientes WHERE id = p_cliente_id;
 
    IF v_cliente_existe = 0 THEN
        SET p_mensaje = 'Cliente no encontrado';
    ELSE
        -- Verificar producto
        SELECT stock, activo INTO v_stock, v_activo
        FROM productos WHERE id = p_producto_id;
 
        IF v_activo = FALSE THEN
            SET p_mensaje = 'Producto no activo';
        ELSEIF v_stock = 0 THEN
            SET p_mensaje = 'Producto sin stock';
        ELSEIF v_stock < p_cantidad THEN
            SET p_mensaje = CONCAT('Stock insuficiente. Disponible: ', v_stock);
        ELSE
            SET p_valido = TRUE;
            SET p_mensaje = 'Pedido válido';
        END IF;
    END IF;
END //
 
DELIMITER ;
CALL validar_pedido(1, 1, 2, @valido, @msg);
SELECT @valido AS valido, @msg AS mensaje;
validomensaje
1Pedido válido

Este patrón de "validar primero, ejecutar después" es uno de los usos más comunes de IF en procedimientos almacenados. Cada validación que falla establece el mensaje de error apropiado y evita que se ejecuten las validaciones siguientes (gracias a la estructura ELSE). Esto produce mensajes de error claros y específicos para cada caso de fallo.

Buenas prácticas

Siempre incluye un ELSE final. Aunque no sea obligatorio, tener un ELSE garantiza que tu procedimiento produzca un resultado en todos los casos. Un procedimiento que a veces no devuelve nada es difícil de depurar y propenso a errores en el código que lo llama.

Evalúa primero los casos más comunes. Si sabes que el 90% de los productos tendrán stock disponible, pon esa condición primero. Aunque la diferencia de rendimiento es insignificante, la legibilidad mejora cuando el caso más probable está arriba.

Cuidado con NULL en las condiciones. Si una variable puede ser NULL (por ejemplo, porque SELECT INTO no encontró filas), cualquier comparación con NULL devuelve NULL (que se trata como FALSE). Usa IS NULL para verificar explícitamente: IF v_precio IS NULL THEN ... END IF.

Limpieza

DROP PROCEDURE IF EXISTS verificar_stock;
DROP PROCEDURE IF EXISTS clasificar_precio;
DROP PROCEDURE IF EXISTS nivel_stock;
DROP PROCEDURE IF EXISTS evaluar_producto;
DROP PROCEDURE IF EXISTS validar_pedido;

En el siguiente artículo veremos la sentencia CASE, una alternativa más limpia al IF para múltiples condiciones.

Escrito por Eduardo Lázaro