Практика SQL
4 мин чтения·

Разница между датами в SQL: DATEDIFF и его аналоги

Посчитать, сколько дней прошло между двумя датами, в SQL можно везде — но в каждой СУБД по-своему. В MySQL и SQL Server есть функция DATEDIFF, но с разными аргументами; в PostgreSQL её нет вообще — там даты просто вычитают. Сводная картина:

СУБДРазница в дняхОсобенность
MySQLDATEDIFF(конец, начало)Считает только дни, конец — первым аргументом
SQL ServerDATEDIFF(day, начало, конец)Единица измерения — первым аргументом
PostgreSQLконец::date - начало::dateФункции DATEDIFF нет, даты вычитаются

Разберём каждую СУБД, а затем решим живые задачи: от «сколько дней клиент думал до первого заказа» до возраста по дате рождения.

DATEDIFF в MySQL

MySQL-версия функции принимает две даты и возвращает разницу в днях. Первым аргументом идёт более поздняя дата:

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

Если перепутать аргументы местами, результат станет отрицательным — это частый источник багов в отчётах:

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

Часы, месяцы и годы DATEDIFF в MySQL считать не умеет — для них есть TIMESTAMPDIFF, у которого порядок аргументов противоположный: сначала начало, потом конец:

MySQL 8.1
SELECT TIMESTAMPDIFF(HOUR, '2026-06-11 10:00:00', '2026-06-12 18:30:00') AS hours;
-- 32: TIMESTAMPDIFF возвращает только полные часы (32.5 округляется вниз)

SELECT TIMESTAMPDIFF(MONTH, '2026-01-15', '2026-06-12') AS months;
-- 4: полных месяцев между датами

Что вернёт в MySQL запрос SELECT DATEDIFF('2026-06-01', '2026-06-12')?

DATEDIFF в SQL Server

В SQL Server у функции три аргумента: единица измерения, начало, конец. Обратите внимание — даты идут в обратном порядке относительно MySQL:

MySQL 8.1
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 есть коварная особенность: он считает не полные периоды, а число пересечённых границ единицы измерения. Разница «в годах» между 31 декабря и 1 января — один год:

MySQL 8.1
SELECT DATEDIFF(year, '2025-12-31', '2026-01-01') AS years;
-- 1, хотя прошёл всего один день

Поэтому для возраста и стажа в SQL Server DATEDIFF(year, ...) напрямую использовать нельзя — результат завышается почти на год для всех, у кого день рождения ещё впереди.

PostgreSQL: вычитание вместо DATEDIFF

Запрос с DATEDIFF в PostgreSQL закончится ошибкой:

MySQL 8.1
SELECT DATEDIFF('2026-06-12', '2026-06-01');
Ошибка
ERROR: function datediff(unknown, unknown) does not exist

Вместо функции здесь арифметика: разница двух значений типа DATE — это целое число дней:

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

С типом TIMESTAMP вычитание возвращает уже не число, а интервал:

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

Чтобы превратить интервал в число, есть два пути:

  • EXTRACT(EPOCH FROM ...) — переводит интервал в секунды, дальше обычная арифметика:

    PostgreSQL 17.5
    SELECT EXTRACT(EPOCH FROM (TIMESTAMP '2026-06-12 18:30:00' - TIMESTAMP '2026-06-11 10:00:00')) / 3600 AS hours;
    -- 32.5: в отличие от TIMESTAMPDIFF, дробная часть сохраняется
    
  • AGE(...) — возвращает «человеческий» интервал в годах, месяцах и днях:

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

Почему запрос SELECT DATEDIFF('2026-06-12', '2026-06-01') падает в PostgreSQL?

Живой пример: от регистрации до первого заказа

Посчитаем по базе сервиса доставки, сколько дней проходит от регистрации пользователя до его первого заказа, — классическая продуктовая метрика:

Вариант для MySQL:

MySQL 8.1
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;

Тот же запрос для PostgreSQL:

PostgreSQL 17.5
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

Запросы отличаются одной строкой — способом вычесть даты, — а результат одинаковый.

Возраст по дате рождения

Ещё одна типовая задача, в которой легко ошибиться. Правильные варианты:

MySQL 8.1
-- MySQL: TIMESTAMPDIFF считает полные годы
SELECT TIMESTAMPDIFF(YEAR, '1995-08-20', CURDATE()) AS age;
PostgreSQL 17.5
-- PostgreSQL: AGE возвращает интервал, из него извлекаем годы
SELECT date_part('year', AGE(DATE '1995-08-20')) AS age;

Оба варианта дают 30 для человека, родившегося 20 августа 1995 года (на 12 июня 2026 года): день рождения ещё не наступил, полных лет — 30. А вот наивное (текущий год - год рождения) дало бы 31 — на единицу больше до дня рождения. Оба блока исполняемые — подставьте свою дату рождения и проверьте.

Шпаргалка: задача → функция

ЗадачаMySQLPostgreSQLSQL Server
Разница в дняхDATEDIFF(d2, d1)d2::date - d1::dateDATEDIFF(day, d1, d2)
Разница в часахTIMESTAMPDIFF(HOUR, d1, d2)EXTRACT(EPOCH FROM (d2 - d1)) / 3600DATEDIFF(hour, d1, d2)
Разница в месяцахTIMESTAMPDIFF(MONTH, d1, d2)AGE(d2, d1) + date_partDATEDIFF(month, d1, d2)
Возраст в годахTIMESTAMPDIFF(YEAR, d1, NOW())date_part('year', AGE(d1))через CASE с днём рождения

И главное различие, которое стоит держать в голове: MySQL TIMESTAMPDIFF и PostgreSQL AGE считают полные периоды, а SQL Server DATEDIFFпересечения границ периода.

Что дальше

Читайте по теме