SQL en la práctica
5 min de lectura·

Diferencia entre fechas en SQL: DATEDIFF y sus equivalentes

Contar cuántos días hay entre dos fechas se puede hacer en cualquier base de datos SQL, pero cada SGBD lo hace a su manera. MySQL y SQL Server tienen la función DATEDIFF, pero con argumentos distintos; PostgreSQL ni siquiera la tiene: allí las fechas simplemente se restan. El panorama general:

SGBDDiferencia en díasParticularidad
MySQLDATEDIFF(fin, inicio)Solo cuenta días; la fecha final va como primer argumento
SQL ServerDATEDIFF(day, inicio, fin)La unidad de medida va como primer argumento
PostgreSQLfin::date - inicio::dateNo existe DATEDIFF; las fechas se restan

Vamos a ver cada SGBD y después resolveremos problemas reales: desde «cuántos días tardó el cliente en decidirse a hacer su primer pedido» hasta la edad a partir de la fecha de nacimiento.

DATEDIFF en MySQL

La versión de MySQL recibe dos fechas y devuelve la diferencia en días. La fecha más reciente va como primer argumento:

MySQL 8.4
SELECT DATEDIFF('2026-06-12', '2026-06-01') AS days;
days
11

Si intercambias los argumentos, el resultado sale negativo, y esto es una fuente habitual de bugs en los informes:

MySQL 8.4
SELECT DATEDIFF('2026-06-01', '2026-06-12') AS days;
-- -11

El DATEDIFF de MySQL no sabe contar horas, meses ni años. Para eso existe TIMESTAMPDIFF, cuyo orden de argumentos es el contrario: primero el inicio y luego el fin:

MySQL 8.4
SELECT TIMESTAMPDIFF(HOUR, '2026-06-11 10:00:00', '2026-06-12 18:30:00') AS hours;
-- 32: TIMESTAMPDIFF solo devuelve horas completas (32.5 se trunca hacia abajo)

SELECT TIMESTAMPDIFF(MONTH, '2026-01-15', '2026-06-12') AS months;
-- 4: meses completos entre las fechas

¿Qué devuelve en MySQL la consulta SELECT DATEDIFF('2026-06-01', '2026-06-12')?

DATEDIFF en SQL Server

En SQL Server la función tiene tres argumentos: la unidad de medida, el inicio y el fin. Fíjate en que las fechas van en orden inverso respecto a MySQL:

MySQL 8.4
SELECT DATEDIFF(day, '2026-06-01', '2026-06-12') AS days;
-- 11

SELECT DATEDIFF(hour, '2026-06-11 10:00:00', '2026-06-12 18:30:00') AS hours;
-- 32

SQL Server tiene una particularidad traicionera: no cuenta periodos completos, sino el número de límites de la unidad que se cruzan. La diferencia «en años» entre el 31 de diciembre y el 1 de enero es de un año:

MySQL 8.4
SELECT DATEDIFF(year, '2025-12-31', '2026-01-01') AS years;
-- 1, aunque solo ha pasado un día

Por eso, en SQL Server no se puede usar DATEDIFF(year, ...) directamente para calcular la edad o la antigüedad: el resultado sale casi un año más alto para todos los que todavía no han cumplido años este año.

PostgreSQL: resta en lugar de DATEDIFF

Una consulta con DATEDIFF termina en PostgreSQL con un error:

MySQL 8.4
SELECT DATEDIFF('2026-06-12', '2026-06-01');
Error
ERROR: function datediff(unknown, unknown) does not exist

En lugar de una función, aquí se usa aritmética: la diferencia entre dos valores de tipo DATE es un número entero de días:

PostgreSQL 17.9
SELECT DATE '2026-06-12' - DATE '2026-06-01' AS days;
days
11

Con el tipo TIMESTAMP, la resta ya no devuelve un número, sino un intervalo:

PostgreSQL 17.9
SELECT TIMESTAMP '2026-06-12 18:30:00' - TIMESTAMP '2026-06-11 10:00:00' AS diff;
-- 1 day 08:30:00

Para convertir un intervalo en un número hay dos caminos:

  • EXTRACT(EPOCH FROM ...) pasa el intervalo a segundos, y a partir de ahí es aritmética normal:

    PostgreSQL 17.9
    SELECT EXTRACT(EPOCH FROM (TIMESTAMP '2026-06-12 18:30:00' - TIMESTAMP '2026-06-11 10:00:00')) / 3600 AS hours;
    -- 32.5: a diferencia de TIMESTAMPDIFF, se conserva la parte decimal
    
  • AGE(...) devuelve un intervalo «humano» en años, meses y días:

    PostgreSQL 17.9
    SELECT AGE(TIMESTAMP '2026-06-12', TIMESTAMP '1995-08-20') AS age;
    -- 30 years 9 mons 23 days
    

¿Por qué falla en PostgreSQL la consulta SELECT DATEDIFF('2026-06-12', '2026-06-01')?

Ejemplo en vivo: del registro al primer pedido

Calculemos con la base de datos del servicio de reparto cuántos días pasan desde que un usuario se registra hasta que hace su primer pedido, una métrica de producto clásica:

La versión para MySQL:

MySQL 8.4
SELECT
    u.user_id,
    DATE(u.registration_date) AS registered,
    DATE(MIN(o.order_date)) AS first_order,
    DATEDIFF(MIN(o.order_date), u.registration_date) AS days_to_order
FROM users u
JOIN orders o ON o.user_id = u.user_id
GROUP BY u.user_id, u.registration_date
ORDER BY u.user_id
LIMIT 5;

La misma consulta para PostgreSQL:

PostgreSQL 17.9
SELECT
    u.user_id,
    DATE(u.registration_date) AS registered,
    DATE(MIN(o.order_date)) AS first_order,
    MIN(o.order_date)::date - u.registration_date::date AS days_to_order
FROM users u
JOIN orders o ON o.user_id = u.user_id
GROUP BY u.user_id, u.registration_date
ORDER BY u.user_id
LIMIT 5;
user_idregisteredfirst_orderdays_to_order
12024-01-052024-01-1914
22024-01-142024-01-195
32022-06-122023-12-21557
42022-08-202023-03-10202
52022-02-282023-10-25604

Las consultas solo se diferencian en una línea, la forma de restar las fechas, y el resultado es el mismo.

La edad a partir de la fecha de nacimiento

Otro problema típico en el que es fácil equivocarse. Las versiones correctas:

MySQL 8.4
-- MySQL: TIMESTAMPDIFF cuenta años completos
SELECT TIMESTAMPDIFF(YEAR, '1995-08-20', CURDATE()) AS age;
PostgreSQL 17.9
-- PostgreSQL: AGE devuelve un intervalo y de él extraemos los años
SELECT date_part('year', AGE(DATE '1995-08-20')) AS age;

Las dos dan 30 para una persona nacida el 20 de agosto de 1995 (a fecha de 12 de junio de 2026): todavía no ha llegado su cumpleaños, así que tiene 30 años cumplidos. En cambio, el ingenuo (año actual - año de nacimiento) daría 31, uno más de la cuenta mientras no llegue el cumpleaños. Los dos bloques son ejecutables: pon tu fecha de nacimiento y compruébalo.

Chuleta: problema → función

ProblemaMySQLPostgreSQLSQL Server
Diferencia en díasDATEDIFF(d2, d1)d2::date - d1::dateDATEDIFF(day, d1, d2)
Diferencia en horasTIMESTAMPDIFF(HOUR, d1, d2)EXTRACT(EPOCH FROM (d2 - d1)) / 3600DATEDIFF(hour, d1, d2)
Diferencia en mesesTIMESTAMPDIFF(MONTH, d1, d2)AGE(d2, d1) + date_partDATEDIFF(month, d1, d2)
Edad en añosTIMESTAMPDIFF(YEAR, d1, NOW())date_part('year', AGE(d1))con CASE según el cumpleaños

Y la diferencia principal que conviene tener en la cabeza: TIMESTAMPDIFF de MySQL y AGE de PostgreSQL cuentan periodos completos, mientras que DATEDIFF de SQL Server cuenta los límites del periodo que se cruzan.

Siguientes pasos

Artículos relacionados