Как в запросе 1С сделать условие по дню и месяцу (выборка дней рождения без учета года)

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

В процессе разработки бизнес-приложений на платформе 1С перед программистом часто возникает задача выборки физических лиц, клиентов или контрагентов, у которых день рождения приходится на определенный период. Например, нам нужно выбрать всех именинников с 1 марта по 30 апреля, чтобы настроить для них праздничную рассылку или предложить персональную скидку.

Сложность заключается в том, что в базе данных даты рождения хранятся с учетом реального года рождения (например, 15.04.1985). Если мы попытаемся использовать стандартное условие МЕЖДУ для периода с марта по апрель текущего года, запрос ничего не найдет, так как годы не совпадут. Нам необходимо построить условие так, чтобы сравнивались только день и месяц, полностью игнорируя год.

В этой статье мы подробно разберем несколько эффективных способов решения этой задачи в запросах 1С, рассмотрим скрытые проблемы високосных лет и научимся обходить их как настоящие профессионалы.

Метод 1. Математическое сравнение (Свертка в число «Месяц * 100 + День»)

Это один из самых лаконичных, изящных и быстродействующих способов сравнения дат без учета года. Суть метода заключается в том, чтобы прямо в запросе преобразовать комбинацию дня и месяца в обычное целое число по формуле: Месяц * 100 + День.

Давайте проанализируем, как это работает на практике:

  1. Дата 1 марта превратится в: 3 * 100 + 1 = 301.
  2. Дата 30 апреля превратится в: 4 * 100 + 30 = 430.
  3. Любая промежуточная дата, например 15 апреля, превратится в: 4 * 100 + 15 = 415.

Поскольку число 415 гарантированно находится в числовом диапазоне между 301 и 430, мы можем использовать простейший математический отбор. Рассмотрим, как выглядит такое условие в тексте запроса:


ВЫБРАТЬ
    Контрагенты.Ссылка КАК Контрагент,
    Контрагенты.ДатаРождения КАК ДатаРождения
ИЗ
    Справочник.Контрагенты КАК Контрагенты
ГДЕ
    (МЕСЯЦ(Контрагенты.ДатаРождения) * 100 + ДЕНЬ(Контрагенты.ДатаРождения)) 
        МЕЖДУ (МЕСЯЦ(&ДатаНачала) * 100 + ДЕНЬ(&ДатаНачала)) 
        И (МЕСЯЦ(&ДатаКонца) * 100 + ДЕНЬ(&ДатаКонца))

Важный нюанс (Переход через Новый год): Данный простой вариант отлично работает, когда период отбора находится внутри одного календарного года (например, с марта по апрель). Но что делать, если пользователю нужно найти именинников в период с 25 декабря по 10 января?

В этом случае число начала (1225) будет больше числа конца (110), и условие МЕЖДУ вернет пустой результат. Чтобы решить эту проблему, добавим в запрос ветвление с помощью оператора ВЫБОР:


ВЫБРАТЬ
    Контрагенты.Ссылка КАК Контрагент,
    Контрагенты.ДатаРождения КАК ДатаРождения
ИЗ
    Справочник.Контрагенты КАК Контрагенты
ГДЕ
    ВЫБОР 
        КОГДА (МЕСЯЦ(&ДатаНачала) * 100 + ДЕНЬ(&ДатаНачала)) <= (МЕСЯЦ(&ДатаКонца) * 100 + ДЕНЬ(&ДатаКонца))
            ТОГДА (МЕСЯЦ(Контрагенты.ДатаРождения) * 100 + ДЕНЬ(Контрагенты.ДатаРождения)) 
                МЕЖДУ (МЕСЯЦ(&ДатаНачала) * 100 + ДЕНЬ(&ДатаНачала)) 
                И (МЕСЯЦ(&ДатаКонца) * 100 + ДЕНЬ(&ДатаКонца))
        ИНАЧЕ (МЕСЯЦ(Контрагенты.ДатаРождения) * 100 + ДЕНЬ(Контрагенты.ДатаРождения)) >= (МЕСЯЦ(&ДатаНачала) * 100 + ДЕНЬ(&ДатаНачала))
            ИЛИ (МЕСЯЦ(Контрагенты.ДатаРождения) * 100 + ДЕНЬ(Контрагенты.ДатаРождения)) <= (МЕСЯЦ(&ДатаКонца) * 100 + ДЕНЬ(&ДатаКонца))
    КОНЕЦ

Метод 2. Приведение дат к високосному 2000-му году

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

Если текущий год не является високосным (например, 2025 год), попытка перенести дату рождения человека, родившегося 29.02.1980, на текущий год приведет к системной ошибке СУБД или к потере этой записи в выборке.

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

Посмотрим на пример реализации этого метода:


ВЫБРАТЬ
    Контрагенты.Ссылка КАК Контрагент,
    Контрагенты.ДатаРождения КАК ДатаРождения
ИЗ
    Справочник.Контрагенты КАК Контрагенты
ГДЕ
    ДОБАВИТЬКДАТЕ(Контрагенты.ДатаРождения, ГОД, 2000 - ГОД(Контрагенты.ДатаРождения)) 
        МЕЖДУ &НачалоПериода2000 И &КонецПериода2000

Перед тем как запустить данный запрос на выполнение, нам необходимо подготовить параметры &НачалоПериода2000 и &КонецПериода2000 во внешнем коде на встроенном языке. Мы должны привести их к 2000-му году:


НачалоПериода2000 = Дата(2000, Месяц(ДатаНачала), День(ДатаНачала), 0, 0, 0);
КонецПериода2000  = Дата(2000, Месяц(ДатаКонца), День(ДатаКонца), 23, 59, 59);

Запрос = Новый Запрос;
Запрос.Текст = "Укажите текст запроса выше";
Запрос.УстановитьПараметр("НачалоПериода2000", НачалоПериода2000);
Запрос.УстановитьПараметр("КонецПериода2000", КонецПериода2000);
Результат = Запрос.Выполнить();

Этот метод считается наиболее надежным и профессиональным, так как СУБД без проблем обрабатывает дату 29 февраля в рамках 2000-го года, а код запроса остается чистым и легко читаемым.

Метод 3. Динамический сдвиг на текущий год (Кадровый метод)

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

Давайте разберем по шагам логику, которая используется для такого расчета:

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

Посмотрим на пример реализации такого условия в запросе:


ВЫБРАТЬ
    ТекДанные.ФизическоеЛицо КАК ФизическоеЛицо,
    ТекДанные.ДатаРождения КАК ДатаРождения,
    ВЫБОР
        КОГДА ДОБАВИТЬКДАТЕ(ТекДанные.ДатаРождения, ГОД, РАЗНОСТЬДАТ(ТекДанные.ДатаРождения, &ДатаОтчета, ГОД)) < &ДатаОтчета
            ТОГДА ДОБАВИТЬКДАТЕ(ТекДанные.ДатаРождения, ГОД, 1 + РАЗНОСТЬДАТ(ТекДанные.ДатаРождения, &ДатаОтчета, ГОД))
        ИНАЧЕ ДОБАВИТЬКДАТЕ(ТекДанные.ДатаРождения, ГОД, РАЗНОСТЬДАТ(ТекДанные.ДатаРождения, &ДатаОтчета, ГОД))
    КОНЕЦ КАК БлижайшийДеньРождения
ПОМЕСТИТЬ ВтДаты
ИЗ
    РегистрСведений.ТекущиеКадровыеДанныеСотрудников КАК ТекДанные
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВтДаты.ФизическоеЛицо,
    ВтДаты.ДатаРождения,
    ВтДаты.БлижайшийДеньРождения
ИЗ
    ВтДаты КАК ВтДаты
ГДЕ
    ВтДаты.БлижайшийДеньРождения МЕЖДУ &ДатаНачала И &ДатаКонца

Этот метод незаменим, когда вам нужно построить отчет вида «Ближайшие дни рождения сотрудников в течение следующих 30 дней».

Какой метод выбрать?

Выбор конкретного метода зависит от архитектуры вашего решения:

Также помните, что при работе с большими базами данных (сотни тысяч или миллионы контрагентов) использование функций ГОД, МЕСЯЦ, ДЕНЬ или ДОБАВИТЬКДАТЕ в секции ГДЕ приводит к тому, что СУБД не может использовать индексы по полю даты рождения (происходит сканирование таблицы вместо поиска по индексу). В таких высоконагруженных системах рекомендуется хранить день и месяц рождения в отдельном индексируемом числовом реквизите при записи карточки контрагента.

← На главную