Una función en MySQL es un bloque de código SQL que recibe cero o más parámetros de entrada, realiza operaciones y devuelve un único valor. Se puede invocar dentro de expresiones SQL, cláusulas SELECT, WHERE, HAVING y otras, de la misma manera que se usan las funciones nativas del motor.
Las funciones encapsulan lógica reutilizable: en lugar de repetir el mismo cálculo en múltiples consultas, se define una vez y se invoca cuando sea necesario.
Operan sobre una sola fila a la vez y devuelven un único valor. Son el objeto de estudio principal de este apunte.
Ejemplo de UDF nativa: UPPER('hola') → devuelve 'HOLA'.
Operan sobre un conjunto de filas y devuelven un valor resumen. No pueden definirse como UDF en MySQL; son nativas del motor.
| Característica | Funciones Escalares | Funciones Agregadas |
|---|---|---|
| Operación | Sobre una sola fila | Sobre un conjunto de filas |
| Resultado | Un valor por fila procesada | Un único valor que resume el conjunto |
| Uso típico | Cálculos, transformaciones, validaciones | Resúmenes, estadísticas, reportes |
| Ejemplos | ABS, ROUND, UPPER, NOW, IF | SUM, AVG, COUNT, MAX, MIN |
| Aplicación típica | SELECT UPPER(nombre) FROM tabla | SELECT SUM(precio) FROM tabla |
| ¿Definibles por el usuario? | Sí (UDF) | No en MySQL estándar |
CREATE FUNCTION nombre_funcion(param1 tipo, param2 tipo, ...)
RETURNS tipo_dato_retorno
[DETERMINISTIC | NOT DETERMINISTIC]
[NO SQL | READS SQL DATA | MODIFIES SQL DATA]
BEGIN
-- declaración de variables locales (opcional)
DECLARE variable tipo DEFAULT valor;
-- lógica de la función
-- ...
RETURN expresion_o_variable;
END;
Las características de una UDF se declaran en dos dimensiones independientes que responden a preguntas distintas:
Ambas se pueden (y deben) combinar cuando corresponda. Por ejemplo: NOT DETERMINISTIC READS SQL DATA.
Dado que el cuerpo de la función contiene punto y coma (;), es necesario cambiar el delimitador antes de crear la función para que el cliente MySQL no interprete cada ; interno como el fin del comando:
DELIMITER //
CREATE FUNCTION nombre_funcion(...)
RETURNS tipo
DETERMINISTIC
BEGIN
DECLARE resultado tipo;
-- lógica
RETURN resultado;
END //
DELIMITER ;
La siguiente función calcula el precio con descuento del 10%:
DELIMITER //
CREATE FUNCTION calcular_descuento(precio DECIMAL(7,2))
RETURNS DECIMAL(7,2)
DETERMINISTIC
BEGIN
DECLARE descuento DECIMAL(7,2);
SET descuento = precio * 0.10;
RETURN precio - descuento;
END //
DELIMITER ;
-- Invocación:
SELECT calcular_descuento(1500.00); -- Resultado: 1350.00
SELECT nombre, precio, calcular_descuento(precio) AS precio_final
FROM productos;
Cuando se declara una UDF, MySQL exige información sobre dos aspectos de su comportamiento. Esta información no es solo formal: el motor la usa para optimizar la ejecución y garantizar la coherencia en entornos con replicación.
Esta dimensión responde a la pregunta: si llamo a la función dos veces con los mismos parámetros, ¿obtengo siempre el mismo valor?
Se declara DETERMINISTIC cuando, dados los mismos valores de entrada, la función siempre produce el mismo resultado, independientemente del momento en que se ejecute, del estado de las tablas o de variables del sistema. El resultado depende exclusivamente de los parámetros recibidos.
Ejemplos de funciones determinísticas:
CREATE FUNCTION celsius_a_fahrenheit(grados DECIMAL(5,2))
RETURNS DECIMAL(5,2)
DETERMINISTIC -- mismos grados → mismo resultado, siempre
NO SQL -- no toca ninguna tabla
BEGIN
RETURN (grados * 9/5) + 32;
END;
Se declara NOT DETERMINISTIC cuando la función puede devolver resultados distintos con los mismos parámetros, porque depende de algo externo que puede cambiar: el reloj del sistema, un valor aleatorio, o el contenido actual de una tabla.
Ejemplos de funciones no determinísticas:
CREATE FUNCTION obtener_timestamp_actual()
RETURNS DATETIME
NOT DETERMINISTIC -- cada llamada puede devolver un valor diferente
NO SQL
BEGIN
RETURN NOW();
END;
Esta dimensión responde a: ¿la función accede a tablas? ¿las lee? ¿las modifica? Hay tres opciones:
La función no contiene ninguna sentencia SQL de ningún tipo. No lee ni escribe tablas, ni siquiera ejecuta un SELECT. Opera únicamente con los parámetros recibidos y expresiones aritméticas o de cadena.
Se combina naturalmente con DETERMINISTIC, ya que si no hay acceso a datos externos, el resultado solo puede depender de los parámetros.
CREATE FUNCTION calcular_iva(precio DECIMAL(10,2), tasa DECIMAL(4,2))
RETURNS DECIMAL(10,2)
DETERMINISTIC
NO SQL -- pura aritmética, sin acceso a tablas
BEGIN
RETURN precio * tasa;
END;
La función contiene sentencias SELECT que leen datos de tablas, pero no los modifica. No ejecuta INSERT, UPDATE ni DELETE.
Este es el caso típico de funciones que consultan información de la base de datos para realizar un cálculo o buscar un valor. Como el contenido de las tablas puede cambiar entre llamadas, suelen combinarse con NOT DETERMINISTIC, aunque si los datos consultados son estáticos (tablas de configuración que no cambian), podría argumentarse que son determinísticas.
CREATE FUNCTION obtener_precio_lista(p_producto_id INT)
RETURNS DECIMAL(10,2)
NOT DETERMINISTIC -- el precio en tabla puede cambiar
READS SQL DATA -- hace un SELECT sobre la tabla productos
BEGIN
DECLARE precio DECIMAL(10,2);
SELECT precio_venta INTO precio
FROM productos
WHERE producto_id = p_producto_id;
RETURN IFNULL(precio, 0.00);
END;
Indica que la función contiene sentencias que modifican datos (INSERT, UPDATE, DELETE, DROP, etc.). En la práctica, MySQL no permite ejecutar DML dentro de funciones que sean llamadas desde un SELECT u otras expresiones SQL, por lo que declarar MODIFIES SQL DATA en una UDF generará un error en tiempo de ejecución si la función efectivamente intenta modificar datos.
Esta característica existe en el estándar SQL y tiene uso real en procedimientos almacenados, pero en funciones UDF su aplicación práctica en MySQL es muy limitada. Se incluye aquí por completitud.
| Combinación | ¿Cuándo usarla? | Ejemplo típico |
|---|---|---|
| DETERMINISTIC NO SQL | Función de cálculo puro, sin acceso a tablas | Conversión de unidades, cálculo de descuento fijo, formateo de cadenas |
| NOT DETERMINISTIC READS SQL DATA | Función que consulta una tabla cuyo contenido puede variar | Obtener stock, precio, nombre de un registro por ID |
| NOT DETERMINISTIC NO SQL | Función sin tablas pero con resultado variable (reloj, azar) | Función que devuelve NOW() o RAND() |
| DETERMINISTIC READS SQL DATA | Función que lee una tabla que se sabe estática (tablas de configuración) | Obtener un parámetro del sistema que nunca cambia |
La no determinismo viene de funciones nativas del motor cuyo resultado varía en cada ejecución, no de los parámetros de entrada. La siguiente función genera un código de seguimiento combinando un prefijo recibido como parámetro con la fecha y hora actuales:
DELIMITER //
CREATE FUNCTION generar_codigo_seguimiento(prefijo VARCHAR(10))
RETURNS VARCHAR(30)
NOT DETERMINISTIC -- NOW() cambia en cada llamada
NO SQL
BEGIN
-- Aunque el parámetro sea el mismo, el resultado varía
-- porque NOW() devuelve el instante actual de ejecución.
-- Ejemplo: 'ORD-20240915-143022', 'ORD-20240915-143055'
RETURN CONCAT(prefijo, '-', DATE_FORMAT(NOW(), '%Y%m%d-%H%i%s'));
END //
DELIMITER ;
-- Dos llamadas con el mismo parámetro producen resultados distintos:
SELECT generar_codigo_seguimiento('ORD'); -- ORD-20240915-143022
SELECT generar_codigo_seguimiento('ORD'); -- ORD-20240915-143055
Otro ejemplo con RAND(): función que asigna un número de turno aleatorio dentro de un rango:
DELIMITER //
CREATE FUNCTION asignar_turno(min_turno INT, max_turno INT)
RETURNS INT
NOT DETERMINISTIC -- RAND() produce un valor distinto en cada llamada
NO SQL
BEGIN
-- FLOOR(RAND() * (max - min + 1)) + min genera un entero en [min, max]
RETURN FLOOR(RAND() * (max_turno - min_turno + 1)) + min_turno;
END //
DELIMITER ;
SELECT asignar_turno(1, 100); -- puede devolver 47
SELECT asignar_turno(1, 100); -- puede devolver 83 (mismo parámetro, distinto resultado)
Una función puede ser no determinística sin usar ninguna función variable del motor. El no determinismo surge de la lógica de negocio: el resultado depende del estado actual de una tabla, que puede cambiar entre llamadas.
La siguiente función devuelve la categoría de un cliente según la cantidad de compras que tiene registradas en ese momento:
DELIMITER //
CREATE FUNCTION categorizar_cliente(p_cliente_id INT)
RETURNS VARCHAR(20)
NOT DETERMINISTIC -- el resultado cambia si el cliente hace nuevas compras
READS SQL DATA -- consulta la tabla ventas
BEGIN
DECLARE total_compras INT;
DECLARE categoria VARCHAR(20);
SELECT COUNT(*) INTO total_compras
FROM ventas
WHERE cliente_id = p_cliente_id;
IF total_compras = 0 THEN
SET categoria = 'Nuevo';
ELSEIF total_compras < 5 THEN
SET categoria = 'Ocasional';
ELSEIF total_compras < 20 THEN
SET categoria = 'Frecuente';
ELSE
SET categoria = 'VIP';
END IF;
RETURN categoria;
END //
DELIMITER ;
-- Hoy, con 3 compras registradas:
SELECT categorizar_cliente(42); -- devuelve 'Ocasional'
-- Mañana, si el cliente hizo 2 compras más (total: 5):
SELECT categorizar_cliente(42); -- devuelve 'Frecuente'
-- Mismo parámetro (42), resultado diferente → NOT DETERMINISTIC
Las funciones pueden utilizar variables locales (DECLARE), estructuras condicionales (IF / ELSEIF / ELSE) y expresiones CASE.
DELIMITER //
CREATE FUNCTION clasificar_precio(precio DECIMAL(10,2))
RETURNS VARCHAR(20)
DETERMINISTIC
BEGIN
DECLARE categoria VARCHAR(20);
IF precio < 500 THEN
SET categoria = 'Económico';
ELSEIF precio < 2000 THEN
SET categoria = 'Intermedio';
ELSE
SET categoria = 'Premium';
END IF;
RETURN categoria;
END //
DELIMITER ;
-- Uso:
SELECT nombre, precio, clasificar_precio(precio) AS categoria
FROM productos;
DELIMITER //
CREATE FUNCTION estado_stock(cantidad INT)
RETURNS VARCHAR(20)
DETERMINISTIC
BEGIN
RETURN CASE
WHEN cantidad = 0 THEN 'Sin stock'
WHEN cantidad < 10 THEN 'Stock bajo'
WHEN cantidad < 50 THEN 'Stock normal'
ELSE 'Stock alto'
END;
END //
DELIMITER ;
El siguiente ejemplo calcula el precio final de una venta aplicando descuento e impuesto:
DELIMITER //
-- Nota: esta función lee de una tabla, por lo que se declara READS SQL DATA
CREATE FUNCTION calcular_precio_final(
p_venta_id INT,
p_descuento DECIMAL(5,4),
p_impuesto DECIMAL(5,4)
)
RETURNS DECIMAL(10,2)
READS SQL DATA
BEGIN
DECLARE subtotal DECIMAL(10,2);
SELECT precio * cantidad INTO subtotal
FROM ventas
WHERE venta_id = p_venta_id;
RETURN subtotal * (1 - p_descuento) * (1 + p_impuesto);
END //
DELIMITER ;
-- Invocación directa:
SELECT calcular_precio_final(10, 0.10, 0.21); -- 10% descuento, 21% IVA
Una UDF puede ser llamada desde dentro de un procedimiento almacenado para delegar cálculos puntuales:
DELIMITER //
CREATE PROCEDURE actualizar_precio_final(
p_venta_id INT,
p_descuento DECIMAL(5,4),
p_impuesto DECIMAL(5,4)
)
BEGIN
UPDATE ventas
SET precio_final = calcular_precio_final(p_venta_id, p_descuento, p_impuesto)
WHERE venta_id = p_venta_id;
END //
DELIMITER ;
-- Llamada al procedimiento:
CALL actualizar_precio_final(10, 0.10, 0.21);
-- Ver todas las funciones de la base de datos actual:
SHOW FUNCTION STATUS WHERE Db = 'nombre_base_datos';
-- Ver el código fuente de una función:
SHOW CREATE FUNCTION nombre_funcion;
MySQL no tiene ALTER FUNCTION para modificar el cuerpo. Se debe eliminar y recrear:
DROP FUNCTION IF EXISTS nombre_funcion;
-- Luego CREATE FUNCTION con la nueva lógica...
DROP FUNCTION nombre_funcion;
-- Con verificación previa:
DROP FUNCTION IF EXISTS nombre_funcion;
Además de las UDF escalares, MySQL incluye otras categorías de funciones que conviene conocer:
| Tipo | Ejemplos | Uso |
|---|---|---|
| Ventana / Analíticas | ROW_NUMBER(), RANK(), DENSE_RANK(), SUM() OVER(), LAG(), LEAD() | Cálculos sobre particiones de filas sin colapsar el resultado |
| JSON | JSON_EXTRACT(), JSON_ARRAYAGG(), JSON_OBJECT() | Manejo de datos semiestructurados JSON |
| Información del sistema | USER(), DATABASE(), VERSION() | Metadatos del entorno actual |
| Control de flujo | IF(), CASE, COALESCE(), IFNULL() | Lógica condicional dentro de consultas |
| Cifrado / Seguridad | SHA2(), MD5(), AES_ENCRYPT() | Hash y encriptación de datos sensibles |
| Espaciales | ST_DISTANCE(), ST_CONTAINS() | Datos geográficos y geométricos |
| Característica | Funciones (UDF) | Procedimientos Almacenados (SP) |
|---|---|---|
| Propósito principal | Calcular y devolver un valor | Realizar múltiples operaciones (DML, transacciones) |
| Uso en expresiones SQL | Sí (SELECT, WHERE, HAVING, ORDER BY) | No (se invoca con CALL) |
| Retorno de valor | Sí, un único valor escalar | No directamente; usa parámetros OUT o result sets |
| Puede hacer DML (INSERT/UPDATE/DELETE) | No | Sí |
| Puede usar transacciones | No | Sí |
| Puede tener handlers de error | Técnicamente sí, pero se desaconseja totalmente en la práctica (no maneja transacciones, dificulta la depuración). En un curso académico se enseña a no usarlos en UDF. | Sí |
| Sintaxis de creación | CREATE FUNCTION ... RETURNS | CREATE PROCEDURE |
| Objeto | Relación con UDF | ¿Qué puede hacer? | ¿Qué NO puede hacer? |
|---|---|---|---|
| UDF | Autónoma; invocable desde SP, triggers, vistas, CTE | Devolver un valor, usarse en SELECT/WHERE/HAVING, transformaciones | usar handlers de error de forma segura (aunque MySQL los permite, se desaconsejan porque no pueden realizar rollback ni controlar transacciones; para eso existe el procedimiento almacenado) |
| Procedimientos (SP) | Pueden invocar UDF para cálculos puntuales | Ejecutar tareas complejas, DML, transacciones, cursores | No se invocan desde SELECT; no devuelven valor como función |
| Triggers | Pueden llamar UDF para lógica de cálculo | Responder automáticamente a eventos DML | No se invocan directamente; dependen de eventos de tabla |
| Vistas | Pueden incluir llamadas a UDF en su definición | Ofrecer abstracción y simplificar el acceso a datos | No aceptan parámetros; no realizan DML directamente |
| CTE | Una UDF puede ser invocada dentro de una CTE | Crear subconsultas temporales reutilizables, jerarquías | Solo existen durante la ejecución de la consulta; no persisten |