PERIOD_DIFF
La función PERIOD_DIFF calcula la diferencia en meses entre dos valores de período. Al igual que PERIOD_ADD, trabaja con valores numéricos en formato YYMM o YYYYMM, no con valores de tipo DATE. Devuelve un número entero que representa cuántos meses separan ambos períodos, siendo positivo cuando el primer período es posterior al segundo y negativo en caso contrario.
Esta función es fundamental en escenarios donde necesitas medir duraciones en meses completos, como calcular la antigüedad de un empleado, la duración de una suscripción o el tiempo transcurrido entre dos eventos medidos en períodos contables.
Sintaxis
PERIOD_DIFF(P1, P2)Ambos argumentos son valores de período en formato YYMM o YYYYMM. La función calcula P1 - P2 y devuelve la diferencia en meses como un número entero con signo. No importa si usas formato de dos o cuatro dígitos para el año, pero por claridad es recomendable usar siempre YYYYMM.
Comportamiento básico
La forma más directa de usar PERIOD_DIFF es calcular los meses entre dos períodos conocidos.
SELECT
PERIOD_DIFF(202602, 202508) AS meses_diferencia;| meses_diferencia |
|---|
| 6 |
Desde agosto de 2025 hasta febrero de 2026 hay exactamente 6 meses. Si inviertes el orden de los argumentos, el resultado es negativo.
SELECT
PERIOD_DIFF(202508, 202602) AS diferencia_inversa;| diferencia_inversa |
|---|
| -6 |
La función también maneja correctamente diferencias que cruzan varios años.
SELECT
PERIOD_DIFF(202602, 202002) AS seis_anios,
PERIOD_DIFF(202612, 202601) AS mismo_anio,
PERIOD_DIFF(202602, 202602) AS mismo_periodo;| seis_anios | mismo_anio | mismo_periodo |
|---|---|---|
| 72 | 11 | 0 |
Seis años equivalen a 72 meses, de enero a diciembre del mismo año hay 11 meses, y dos períodos iguales tienen diferencia cero.
Caso práctico: antigüedad de empleados
Calcular la antigüedad de cada empleado en meses y años es uno de los usos más comunes de PERIOD_DIFF. El período actual se obtiene formateando la fecha actual con DATE_FORMAT.
SELECT
e.nombre,
e.departamento,
e.periodo_ingreso,
PERIOD_DIFF(
DATE_FORMAT(CURDATE(), '%Y%m'),
e.periodo_ingreso
) AS meses_antiguedad,
FLOOR(
PERIOD_DIFF(DATE_FORMAT(CURDATE(), '%Y%m'), e.periodo_ingreso) / 12
) AS anios_completos,
MOD(
PERIOD_DIFF(DATE_FORMAT(CURDATE(), '%Y%m'), e.periodo_ingreso), 12
) AS meses_restantes
FROM empleados e
ORDER BY meses_antiguedad DESC
LIMIT 5;| nombre | departamento | periodo_ingreso | meses_antiguedad | anios_completos | meses_restantes |
|---|---|---|---|---|---|
| Ana Martínez | Ingeniería | 201503 | 131 | 10 | 11 |
| Carlos López | Ventas | 201709 | 101 | 8 | 5 |
| Laura Gómez | Marketing | 201901 | 85 | 7 | 1 |
| Pedro Sánchez | Finanzas | 202006 | 68 | 5 | 8 |
| María Ruiz | RRHH | 202201 | 49 | 4 | 1 |
Al combinar PERIOD_DIFF con FLOOR y MOD, obtienes la antigüedad expresada en años y meses, que es más legible que solo el total de meses. Ana Martínez lleva 10 años y 11 meses en la empresa, siendo la empleada con mayor antigüedad.
Caso práctico: duración de suscripciones
Un servicio de streaming necesita analizar cuánto tiempo permanecen suscritos sus clientes para medir la retención.
SELECT
u.nombre,
u.plan,
u.periodo_alta,
COALESCE(u.periodo_baja, DATE_FORMAT(CURDATE(), '%Y%m')) AS periodo_referencia,
PERIOD_DIFF(
COALESCE(u.periodo_baja, DATE_FORMAT(CURDATE(), '%Y%m')),
u.periodo_alta
) AS meses_suscrito,
CASE
WHEN u.periodo_baja IS NULL THEN 'Activo'
ELSE 'Cancelado'
END AS estado
FROM usuarios_suscripcion u
ORDER BY meses_suscrito DESC
LIMIT 6;| nombre | plan | periodo_alta | periodo_referencia | meses_suscrito | estado |
|---|---|---|---|---|---|
| María García | Premium | 202201 | 202602 | 49 | Activo |
| Carlos López | Básico | 202306 | 202602 | 32 | Activo |
| Laura Sánchez | Premium | 202401 | 202512 | 23 | Cancelado |
| Pedro Fernández | Familiar | 202406 | 202602 | 20 | Activo |
| Ana Martínez | Básico | 202501 | 202509 | 8 | Cancelado |
| Juan Rodríguez | Premium | 202510 | 202602 | 4 | Activo |
La función COALESCE es clave aquí: para usuarios activos que no tienen periodo_baja, se usa el período actual como referencia. Esto permite calcular uniformemente la duración tanto de suscripciones activas como canceladas.
Caso práctico: clasificar clientes por permanencia
A partir de la duración en meses, puedes segmentar a los clientes en categorías útiles para marketing y retención.
SELECT
CASE
WHEN PERIOD_DIFF(DATE_FORMAT(CURDATE(), '%Y%m'), periodo_alta) < 3
THEN 'Nuevo (< 3 meses)'
WHEN PERIOD_DIFF(DATE_FORMAT(CURDATE(), '%Y%m'), periodo_alta) < 12
THEN 'Regular (3-11 meses)'
WHEN PERIOD_DIFF(DATE_FORMAT(CURDATE(), '%Y%m'), periodo_alta) < 24
THEN 'Fiel (1-2 años)'
ELSE 'Veterano (2+ años)'
END AS segmento,
COUNT(*) AS total_clientes,
ROUND(AVG(gasto_mensual), 2) AS gasto_promedio
FROM clientes
WHERE activo = 1
GROUP BY segmento
ORDER BY gasto_promedio DESC;| segmento | total_clientes | gasto_promedio |
|---|---|---|
| Veterano (2+ años) | 342 | 89.50 |
| Fiel (1-2 años) | 518 | 62.30 |
| Regular (3-11 meses) | 891 | 45.75 |
| Nuevo (< 3 meses) | 1204 | 32.10 |
Este análisis revela que los clientes veteranos gastan significativamente más que los nuevos, información valiosa para justificar inversiones en programas de retención.
Diferencia con TIMESTAMPDIFF
Mientras que PERIOD_DIFF trabaja con períodos numéricos y solo calcula diferencias en meses, TIMESTAMPDIFF trabaja con valores DATE o DATETIME y puede calcular diferencias en diversas unidades.
-- PERIOD_DIFF: trabaja con períodos numéricos
SELECT PERIOD_DIFF(202602, 202508) AS con_period_diff;
-- TIMESTAMPDIFF: trabaja con fechas reales
SELECT TIMESTAMPDIFF(MONTH, '2025-08-14', '2026-02-14') AS con_timestampdiff;| con_period_diff |
|---|
| 6 |
| con_timestampdiff |
|---|
| 6 |
Ambas devuelven 6 en este caso, pero hay una diferencia sutil. PERIOD_DIFF calcula la diferencia entre meses sin considerar los días, mientras que TIMESTAMPDIFF con MONTH sí considera el día exacto. Por ejemplo, TIMESTAMPDIFF(MONTH, '2025-08-20', '2026-02-14') devolvería 5, no 6, porque no se ha completado el sexto mes. PERIOD_DIFF(202602, 202508) siempre devuelve 6 independientemente de los días.
Manejo de NULL
PERIOD_DIFF devuelve NULL si cualquiera de sus argumentos es NULL.
SELECT
PERIOD_DIFF(NULL, 202602) AS primer_nulo,
PERIOD_DIFF(202602, NULL) AS segundo_nulo,
PERIOD_DIFF(NULL, NULL) AS ambos_nulos;| primer_nulo | segundo_nulo | ambos_nulos |
|---|---|---|
| NULL | NULL | NULL |
En consultas con columnas que pueden ser nulas, usa COALESCE para proporcionar un valor de reemplazo coherente.
SELECT
nombre,
COALESCE(
PERIOD_DIFF(DATE_FORMAT(CURDATE(), '%Y%m'), periodo_inicio),
0
) AS meses_activo
FROM proyectos;| nombre | meses_activo |
|---|---|
| Rediseño web | 8 |
| App móvil | 14 |
| Proyecto pendiente | 0 |
Combinación con otras funciones
PERIOD_DIFF se combina naturalmente con PERIOD_ADD para realizar cálculos completos. También puedes convertir fechas a períodos usando DATE_FORMAT o EXTRACT.
SELECT
e.nombre,
e.fecha_contratacion,
PERIOD_DIFF(
EXTRACT(YEAR_MONTH FROM CURDATE()),
EXTRACT(YEAR_MONTH FROM e.fecha_contratacion)
) AS meses_desde_contratacion
FROM empleados e
ORDER BY meses_desde_contratacion DESC
LIMIT 3;| nombre | fecha_contratacion | meses_desde_contratacion |
|---|---|---|
| Ana Martínez | 2015-03-16 | 131 |
| Carlos López | 2017-09-01 | 101 |
| Laura Gómez | 2019-01-15 | 85 |
La función EXTRACT(YEAR_MONTH FROM ...) devuelve el año y mes en formato YYYYMM, que es exactamente lo que PERIOD_DIFF espera. Esta combinación permite usar PERIOD_DIFF con columnas DATE sin necesidad de DATE_FORMAT.
En el siguiente artículo veremos TIME_TO_SEC para convertir valores de tiempo a segundos.
Escrito por Eduardo Lázaro
