Разница между датами в SQL: DATEDIFF и его аналоги
Посчитать, сколько дней прошло между двумя датами, в SQL можно везде — но в каждой СУБД по-своему. В MySQL и SQL Server есть функция DATEDIFF, но с разными аргументами; в PostgreSQL её нет вообще — там даты просто вычитают. Сводная картина:
Разберём каждую СУБД, а затем решим живые задачи: от «сколько дней клиент думал до первого заказа» до возраста по дате рождения.
DATEDIFF в MySQL
MySQL-версия функции принимает две даты и возвращает разницу в днях. Первым аргументом идёт более поздняя дата:
MySQL 8.1SELECT DATEDIFF('2026-06-12', '2026-06-01') AS days;
Если перепутать аргументы местами, результат станет отрицательным — это частый источник багов в отчётах:
MySQL 8.1SELECT DATEDIFF('2026-06-01', '2026-06-12') AS days; -- -11
Часы, месяцы и годы DATEDIFF в MySQL считать не умеет — для них есть TIMESTAMPDIFF, у которого порядок аргументов противоположный: сначала начало, потом конец:
MySQL 8.1SELECT 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.1SELECT 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.1SELECT DATEDIFF(year, '2025-12-31', '2026-01-01') AS years; -- 1, хотя прошёл всего один день
Поэтому для возраста и стажа в SQL Server DATEDIFF(year, ...) напрямую использовать нельзя — результат завышается почти на год для всех, у кого день рождения ещё впереди.
PostgreSQL: вычитание вместо DATEDIFF
Запрос с DATEDIFF в PostgreSQL закончится ошибкой:
MySQL 8.1SELECT DATEDIFF('2026-06-12', '2026-06-01');
Вместо функции здесь арифметика: разница двух значений типа DATE — это целое число дней:
PostgreSQL 17.5SELECT DATE '2026-06-12' - DATE '2026-06-01' AS days;
С типом TIMESTAMP вычитание возвращает уже не число, а интервал:
PostgreSQL 17.5SELECT 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.5SELECT 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.5SELECT 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.1SELECT 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.5SELECT 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;
Запросы отличаются одной строкой — способом вычесть даты, — а результат одинаковый.
Возраст по дате рождения
Ещё одна типовая задача, в которой легко ошибиться. Правильные варианты:
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 — на единицу больше до дня рождения. Оба блока исполняемые — подставьте свою дату рождения и проверьте.
Шпаргалка: задача → функция
И главное различие, которое стоит держать в голове: MySQL TIMESTAMPDIFF и PostgreSQL AGE считают полные периоды, а SQL Server DATEDIFF — пересечения границ периода.
Что дальше
- как устроены типы DATE, TIME и TIMESTAMP — в уроке Дата и время в SQL;
- функции для работы с датами с упражнениями — в уроке Работа с датой и временем;
- быстрые справки — в справочнике: DATEDIFF, TIMESTAMPDIFF;
- закрепить на задачах — в тренажёре SQL.
Читайте по теме
ROW_NUMBER, RANK и DENSE_RANK в SQL: отличия на одном примере
Три ранжирующие функции, один запрос — и разница видна
COALESCE в SQL: что это и как работает — примеры | SQL Academy
Первый не-NULL аргумент, и зачем рядом NULLIF
CTE в SQL: что такое Common Table Expression (WITH) — примеры
Подзапросы с именами, цепочки шагов и рекурсия