Как в запросе 1С посчитать разницу между днями рождения сотрудников?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Задача расчета разницы между днями рождения только на первый взгляд кажется простой. Основная сложность заключается в том, что нам нужно игнорировать год рождения человека, но при этом корректно учитывать високосные годы (29 февраля) и уметь вычислять разницу между «последним» именинником декабря и «первым» именинником января следующего года. В этой статье мы подробно разберем несколько подходов к решению этой задачи, проанализируем типичные ошибки и выберем наиболее надежный алгоритм.

Постановка задачи и основные трудности

Представим ситуацию: у нас есть список сотрудников с их датами рождения. Нам нужно получить таблицу, где для каждого человека указано, через сколько дней наступит следующий по календарю день рождения (своего коллеги). Рассмотрим ключевые проблемы, которые нам предстоит решить:

  1. Разные годы рождения: Функция РАЗНОСТЬДАТ между 1980 и 2023 годом выдаст тысячи дней, а нам нужно получить значение в диапазоне от 0 до 365.
  2. Високосные годы: Если просто заменить год рождения на текущий, то дата 29 февраля в невисокосном году вызовет ошибку или будет проигнорирована.
  3. Цикличность календаря: Для сотрудника, родившегося 31 декабря, «следующим» будет тот, кто родился в начале января. Обычное соединение по условию Дата1 < Дата2 здесь не сработает в лоб.

Вариант 1. Использование временных таблиц и нормализация дат

Проанализируем самый надежный метод — приведение всех дат к одному базовому году. Чтобы не потерять 29 февраля, в качестве базового года лучше всего выбрать заведомо високосный год, например, 2000-й. Разберем этот алгоритм по шагам.

Сначала создадим временную таблицу, в которой «обнулим» год рождения, заменив его на 2000-й. Для этого используем комбинацию функций ДОБАВИТЬКДАТЕ и ГОД.


ВЫБРАТЬ
    ДниРождения.ФИО КАК ФИО,
    ДниРождения.ДеньРождения КАК ДеньРождения,
    ДОБАВИТЬКДАТЕ(ДниРождения.ДеньРождения, ГОД, 2000 - ГОД(ДниРождения.ДеньРождения)) КАК ДеньРожденияБезГода
ПОМЕСТИТЬ ВТ_НормализованныеДаты
ИЗ
    РегистрСведений.ДниРождения КАК ДниРождения;

Теперь нам нужно решить проблему перехода через год. Для этого применим хитрость: создадим вторую временную таблицу, куда поместим те же данные, но добавим к каждой нормализованной дате ровно один год (сделаем их датами 2001 года). Объединив эти наборы, мы получим непрерывную последовательность дней рождения на два года вперед.


ВЫБРАТЬ
    Т.ФИО,
    Т.ДеньРожденияБезГода
ПОМЕСТИТЬ ВТ_Расширенная
ИЗ
    ВТ_НормализованныеДаты КАК Т

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    Т.ФИО,
    ДОБАВИТЬКДАТЕ(Т.ДеньРожденияБезГода, ГОД, 1)
ИЗ
    ВТ_НормализованныеДаты КАК Т;

Теперь мы можем легко найти «следующую» дату для каждого сотрудника из первого (2000-го) года, просто найдя минимальную дату в расширенной таблице, которая больше текущей.


ВЫБРАТЬ
    Т1.ФИО КАК ФИО,
    Т1.ДеньРожденияБезГода КАК ДатаСегодня,
    МИНИМУМ(Т2.ДеньРожденияБезГода) КАК ДатаСледующая
ПОМЕСТИТЬ ВТ_Соединение
ИЗ
    ВТ_НормализованныеДаты КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Расширенная КАК Т2 ПО Т1.ДеньРожденияБезГода < Т2.ДеньРожденияБезГода
СГРУППИРОВАТЬ ПО
    Т1.ФИО,
    Т1.ДеньРожденияБезГода;

Финальным шагом вычисляем разницу в днях с помощью стандартной функции РАЗНОСТЬДАТ.


ВЫБРАТЬ
    Т.ФИО,
    РАЗНОСТЬДАТ(Т.ДатаСегодня, Т.ДатаСледующая, ДЕНЬ) КАК РазницаДней
ИЗ
    ВТ_Соединение КАК Т;

Вариант 2. Использование функции ДЕНЬГОДА

Рассмотрим более простой, но менее точный подход, который предлагался в обсуждении. Функция ДЕНЬГОДА() возвращает порядковый номер дня в году (от 1 до 366). Этот метод хорош для быстрой сортировки, но требует осторожности с високосными годами.

Выясним причину, почему этот метод может ошибаться: в високосном году 1 марта — это 61-й день, а в обычном — 60-й. Если ваши данные содержат людей, рожденных в разные годы, прямое вычитание результатов ДЕНЬГОДА() может дать погрешность в 1 день. Однако, если использовать АВТОНОМЕРЗАПИСИ() для связи текущей строки со следующей, можно получить лаконичный код:


ВЫБРАТЬ
    Т1.Сотрудник,
    Т1.ДеньГ КАК ДеньСегодня,
    Т2.ДеньГ КАК ДеньЗавтра,
    Т2.ДеньГ - Т1.ДеньГ КАК Дни
ИЗ
    ВТ_Промежуточная П1
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Промежуточная П2 ПО П1.НомерСтроки + 1 = П2.НомерСтроки

Важно: Чтобы этот метод работал для последнего человека в списке, вам все равно придется добавить в таблицу «фиктивную» запись первого сотрудника, но с номером дня + 365 (или 366).

Вариант 3. Современный подход через Систему компоновки данных (СКД)

Если вы строите отчет, а не просто пишете запрос в консоли, задачу можно решить элегантнее с помощью функций ресурсов СКД. Рассмотрим функцию ВычислитьВыражение.

Этот метод позволяет не перегружать запрос сложными соединениями. Вы можете просто выбрать список сотрудников и их дат рождения, отсортировать их, а затем в вычисляемом поле написать выражение:

РазностьДат(ДеньРождения, ВычислитьВыражение("ДеньРождения", , , "Следующая", "Порядок"), "ДЕНЬ")

Проанализируем преимущества этого способа:

Рекомендации по точности расчетов

При работе с датами в 1С всегда помните о времени. Если в поле ДеньРождения записана дата со временем (например, 12:00:00), то РАЗНОСТЬДАТ может вернуть неожиданный результат из-за неполных суток. Мы рекомендуем всегда обертывать даты функцией НАЧАЛОПЕРИОДА(Дата, ДЕНЬ) перед любыми вычислениями.

Также, если в вашей базе есть сотрудники с одинаковыми днями рождения (например, два человека родились 15 мая), обычное соединение по условию Т1.Дата < Т2.Дата может привести к тому, что разница между ними будет посчитана как 0 или они «схлопнутся» в одну запись. Чтобы этого избежать, в условие соединения и сортировки всегда добавляйте уникальный идентификатор (например, Ссылка или ФИО), чтобы выстроить однозначную очередь именинников.

Подведем итог: для разовых задач в консоли лучше всего подходит метод с приведением к базовому високосному году (2000-й), так как он наиболее математически точен. Для разработки сложных отчетов стоит обратить внимание на возможности СКД по вычислению значений в соседних строках.

← На главную