Variables
Los procedimientos almacenados pueden usar variables locales declaradas con DECLARE y variables de sesión con el prefijo @. Las variables locales solo existen dentro del bloque BEGIN...END donde se declaran.
Entender cómo funcionan las variables es fundamental para escribir procedimientos que hagan algo más que ejecutar consultas simples. Las variables te permiten almacenar valores intermedios, construir lógica condicional, acumular resultados y controlar el flujo de ejecución. Son el pegamento que une las diferentes sentencias SQL dentro de un procedimiento en una secuencia lógica coherente.
Variables locales con DECLARE
Las variables locales se crean con la sentencia DECLARE y existen únicamente durante la ejecución del procedimiento. Cuando el procedimiento termina, las variables desaparecen sin dejar rastro. No afectan a la sesión del usuario ni a otros procedimientos.
DELIMITER //
CREATE PROCEDURE ejemplo_variables()
BEGIN
-- Declarar variables (siempre al inicio del bloque BEGIN)
DECLARE nombre VARCHAR(100);
DECLARE precio DECIMAL(10,2) DEFAULT 0.00;
DECLARE cantidad INT DEFAULT 1;
DECLARE activo BOOLEAN DEFAULT TRUE;
-- Asignar valores con SET
SET nombre = 'Producto de prueba';
SET precio = 99.99;
SELECT nombre, precio, cantidad, activo;
END //
DELIMITER ;CALL ejemplo_variables();| nombre | precio | cantidad | activo |
|---|---|---|---|
| Producto de prueba | 99.99 | 1 | 1 |
Cada variable necesita un tipo de dato MySQL (VARCHAR, INT, DECIMAL, BOOLEAN, DATE, etc.) y opcionalmente un valor por defecto con DEFAULT. Si no especificas DEFAULT, la variable se inicializa como NULL, lo que puede causar problemas si la usas en operaciones aritméticas o concatenaciones sin verificar primero su valor.
Reglas de DECLARE
La posición de las declaraciones dentro del bloque BEGIN...END es estricta. MySQL exige que todas las sentencias DECLARE vayan al principio del bloque, antes de cualquier sentencia ejecutable (SET, SELECT, IF, etc.). Si intentas declarar una variable después de una sentencia ejecutable, obtendrás un error de sintaxis.
DELIMITER //
CREATE PROCEDURE ejemplo_declare()
BEGIN
-- Múltiples variables del mismo tipo
DECLARE x, y, z INT DEFAULT 0;
SET x = 10;
SET y = 20;
SET z = x + y;
SELECT x, y, z;
END //
DELIMITER ;CALL ejemplo_declare();| x | y | z |
|---|---|---|
| 10 | 20 | 30 |
Puedes declarar múltiples variables del mismo tipo en una sola línea separándolas por comas, como DECLARE x, y, z INT DEFAULT 0. Todas compartirán el mismo tipo y valor por defecto. Esto es útil para declarar contadores o flags relacionados, pero si las variables tienen propósitos diferentes, es más legible declararlas por separado con nombres descriptivos.
Hay tres reglas clave que conviene recordar:
DECLAREdebe ir al inicio del bloque BEGIN...END, antes de cualquier otra sentencia.- Puedes declarar múltiples variables del mismo tipo en una línea.
- El valor por defecto es NULL si no se especifica DEFAULT.
Asignar valores con SET
SET es la forma más directa de asignar un valor a una variable. Puedes asignar literales, resultados de expresiones o valores de otras variables:
SET nombre = 'Producto de prueba';
SET precio = 99.99;
SET total = precio * cantidad;
SET activo = NOT activo;También puedes asignar múltiples variables en un solo SET separándolas por comas: SET x = 10, y = 20, z = 30. Esto es más conciso pero puede ser menos legible si las asignaciones son complejas.
Asignar valores con SELECT INTO
SELECT INTO permite asignar el resultado de una consulta a variables. Es la forma estándar de cargar datos de las tablas en variables del procedimiento:
DELIMITER //
CREATE PROCEDURE info_producto(IN prod_id INT)
BEGIN
DECLARE v_nombre VARCHAR(100);
DECLARE v_precio DECIMAL(10,2);
DECLARE v_stock INT;
SELECT nombre, precio, stock
INTO v_nombre, v_precio, v_stock
FROM productos
WHERE id = prod_id;
SELECT v_nombre AS producto,
v_precio AS precio,
v_stock AS stock,
v_precio * v_stock AS valor_inventario;
END //
DELIMITER ;CALL info_producto(1);| producto | precio | stock | valor_inventario |
|---|---|---|---|
| iPhone 15 Pro | 1299.99 | 45 | 58499.55 |
Hay un requisito importante: la consulta del SELECT INTO debe devolver exactamente una fila. Si devuelve cero filas, las variables quedarán como NULL (o se activará un handler NOT FOUND si lo tienes configurado). Si devuelve más de una fila, MySQL genera un error. Por eso es habitual usar SELECT INTO con consultas que filtran por clave primaria (WHERE id = ...) o con funciones de agregación (COUNT, MAX, SUM) que siempre devuelven una sola fila.
Variables de sesión
Las variables de sesión son un mecanismo completamente diferente a las variables locales. Se crean con el prefijo @ y persisten durante toda la conexión del usuario con MySQL. No necesitan declaración previa y se pueden usar tanto dentro como fuera de los procedimientos:
-- Crear y asignar
SET @contador = 0;
SET @nombre = 'MySQL';
-- Usar en consultas
SELECT @contador, @nombre;| @contador | @nombre |
|---|---|
| 0 | MySQL |
-- Asignar desde una consulta
SELECT @max_precio := MAX(precio) FROM productos;
SELECT @max_precio;| @max_precio |
|---|
| 1899.99 |
Las variables de sesión son convenientes porque no requieren declaración y sobreviven entre sentencias, pero tienen desventajas: no tienen tipo de dato definido (MySQL lo infiere del valor asignado), persisten hasta que la conexión se cierra (lo que puede causar bugs si olvidas reinicializarlas), y no se pueden usar en cláusulas WHERE de forma fiable en todas las versiones de MySQL.
Dentro de los procedimientos, la recomendación es usar variables locales (DECLARE) para la lógica interna y reservar las variables de sesión para comunicarse con el código externo (parámetros OUT, resultados para uso posterior).
Variables locales vs variables de sesión
| Característica | Variable local | Variable de sesión |
|---|---|---|
| Prefijo | Sin prefijo | @ |
| Declaración | Requiere DECLARE | No requiere |
| Tipo de dato | Definido explícitamente | Inferido del valor |
| Ámbito | Bloque BEGIN...END | Toda la sesión |
| Persistencia | Solo durante la ejecución | Hasta cerrar conexión |
| Uso fuera del procedimiento | No | Sí |
En la práctica, esta distinción importa porque determina cuándo se limpia cada variable. Las variables locales se destruyen automáticamente al terminar el procedimiento, por lo que no hay riesgo de que valores residuales afecten a futuras ejecuciones. Las variables de sesión, en cambio, mantienen su último valor, lo que puede provocar resultados inesperados si un procedimiento asume que una variable de sesión tiene un valor determinado.
Ámbito de variables
El ámbito de una variable local está determinado por el bloque BEGIN...END donde se declara. Un bloque interior puede acceder a las variables del bloque exterior, pero no al revés:
DELIMITER //
CREATE PROCEDURE ejemplo_ambito()
BEGIN
DECLARE x INT DEFAULT 10;
BEGIN
-- Este bloque interno puede acceder a x
DECLARE y INT DEFAULT 20;
SELECT x + y AS suma_interna;
END;
-- x sigue visible, pero y no existe aquí
SELECT x AS valor_x;
-- SELECT y; -- Error: Unknown column 'y'
END //
DELIMITER ;Este comportamiento es idéntico al ámbito de variables en lenguajes como JavaScript (con let), Java o C. Las variables del bloque exterior son "heredadas" por los bloques interiores, pero las variables internas no se filtran hacia fuera.
Los bloques anidados son especialmente útiles cuando trabajas con múltiples cursores, ya que cada bloque puede tener su propio handler NOT FOUND sin que interfiera con los cursores de otros bloques.
Ejemplo práctico: calcular descuento
Este ejemplo combina variables locales, SELECT INTO y condicionales para implementar una lógica de descuentos escalonados. Demuestra cómo las variables sirven de puente entre las consultas a la base de datos y la lógica de negocio:
DELIMITER //
CREATE PROCEDURE calcular_descuento(IN prod_id INT)
BEGIN
DECLARE v_nombre VARCHAR(100);
DECLARE v_precio DECIMAL(10,2);
DECLARE v_descuento DECIMAL(5,2) DEFAULT 0;
DECLARE v_precio_final DECIMAL(10,2);
SELECT nombre, precio INTO v_nombre, v_precio
FROM productos WHERE id = prod_id;
-- Calcular descuento según precio
IF v_precio > 1000 THEN
SET v_descuento = 15.00;
ELSEIF v_precio > 500 THEN
SET v_descuento = 10.00;
ELSEIF v_precio > 100 THEN
SET v_descuento = 5.00;
END IF;
SET v_precio_final = v_precio - (v_precio * v_descuento / 100);
SELECT v_nombre AS producto,
v_precio AS precio_original,
v_descuento AS descuento_pct,
v_precio_final AS precio_final;
END //
DELIMITER ;CALL calcular_descuento(1);| producto | precio_original | descuento_pct | precio_final |
|---|---|---|---|
| iPhone 15 Pro | 1299.99 | 15.00 | 1104.99 |
CALL calcular_descuento(22);| producto | precio_original | descuento_pct | precio_final |
|---|---|---|---|
| Camiseta algodón básica | 19.99 | 0.00 | 19.99 |
El patrón es claro: primero se cargan los datos de la tabla en variables locales con SELECT INTO, luego se aplica la lógica de negocio manipulando esas variables con SET e IF, y finalmente se devuelve el resultado con un SELECT que referencia las variables calculadas.
Limpieza
DROP PROCEDURE IF EXISTS ejemplo_variables;
DROP PROCEDURE IF EXISTS ejemplo_declare;
DROP PROCEDURE IF EXISTS info_producto;
DROP PROCEDURE IF EXISTS ejemplo_ambito;
DROP PROCEDURE IF EXISTS calcular_descuento;En el siguiente artículo veremos la sentencia IF para control de flujo dentro de procedimientos.
Escrito por Eduardo Lázaro
