В процессе разработки бизнес-приложений на платформе 1С перед программистом часто возникает задача выборки физических лиц, клиентов или контрагентов, у которых день рождения приходится на определенный период. Например, нам нужно выбрать всех именинников с 1 марта по 30 апреля, чтобы настроить для них праздничную рассылку или предложить персональную скидку.
Сложность заключается в том, что в базе данных даты рождения хранятся с учетом реального года рождения (например, 15.04.1985). Если мы попытаемся использовать стандартное условие МЕЖДУ для периода с марта по апрель текущего года, запрос ничего не найдет, так как годы не совпадут. Нам необходимо построить условие так, чтобы сравнивались только день и месяц, полностью игнорируя год.
В этой статье мы подробно разберем несколько эффективных способов решения этой задачи в запросах 1С, рассмотрим скрытые проблемы високосных лет и научимся обходить их как настоящие профессионалы.
Это один из самых лаконичных, изящных и быстродействующих способов сравнения дат без учета года. Суть метода заключается в том, чтобы прямо в запросе преобразовать комбинацию дня и месяца в обычное целое число по формуле: Месяц * 100 + День.
Давайте проанализируем, как это работает на практике:
3 * 100 + 1 = 301.4 * 100 + 30 = 430.4 * 100 + 15 = 415.Поскольку число 415 гарантированно находится в числовом диапазоне между 301 и 430, мы можем использовать простейший математический отбор. Рассмотрим, как выглядит такое условие в тексте запроса:
ВЫБРАТЬ
Контрагенты.Ссылка КАК Контрагент,
Контрагенты.ДатаРождения КАК ДатаРождения
ИЗ
Справочник.Контрагенты КАК Контрагенты
ГДЕ
(МЕСЯЦ(Контрагенты.ДатаРождения) * 100 + ДЕНЬ(Контрагенты.ДатаРождения))
МЕЖДУ (МЕСЯЦ(&ДатаНачала) * 100 + ДЕНЬ(&ДатаНачала))
И (МЕСЯЦ(&ДатаКонца) * 100 + ДЕНЬ(&ДатаКонца))
Важный нюанс (Переход через Новый год): Данный простой вариант отлично работает, когда период отбора находится внутри одного календарного года (например, с марта по апрель). Но что делать, если пользователю нужно найти именинников в период с 25 декабря по 10 января?
В этом случае число начала (1225) будет больше числа конца (110), и условие МЕЖДУ вернет пустой результат. Чтобы решить эту проблему, добавим в запрос ветвление с помощью оператора ВЫБОР:
ВЫБРАТЬ
Контрагенты.Ссылка КАК Контрагент,
Контрагенты.ДатаРождения КАК ДатаРождения
ИЗ
Справочник.Контрагенты КАК Контрагенты
ГДЕ
ВЫБОР
КОГДА (МЕСЯЦ(&ДатаНачала) * 100 + ДЕНЬ(&ДатаНачала)) <= (МЕСЯЦ(&ДатаКонца) * 100 + ДЕНЬ(&ДатаКонца))
ТОГДА (МЕСЯЦ(Контрагенты.ДатаРождения) * 100 + ДЕНЬ(Контрагенты.ДатаРождения))
МЕЖДУ (МЕСЯЦ(&ДатаНачала) * 100 + ДЕНЬ(&ДатаНачала))
И (МЕСЯЦ(&ДатаКонца) * 100 + ДЕНЬ(&ДатаКонца))
ИНАЧЕ (МЕСЯЦ(Контрагенты.ДатаРождения) * 100 + ДЕНЬ(Контрагенты.ДатаРождения)) >= (МЕСЯЦ(&ДатаНачала) * 100 + ДЕНЬ(&ДатаНачала))
ИЛИ (МЕСЯЦ(Контрагенты.ДатаРождения) * 100 + ДЕНЬ(Контрагенты.ДатаРождения)) <= (МЕСЯЦ(&ДатаКонца) * 100 + ДЕНЬ(&ДатаКонца))
КОНЕЦ
Часто разработчики пытаются решить задачу «в лоб», собирая из дня и месяца исходной даты новую дату за текущий отчетный год. Однако здесь кроется серьезная логическая ловушка, связанная с людьми, которые родились в високосный год 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-го года, а код запроса остается чистым и легко читаемым.
В типовых конфигурациях 1С (например, в «1С:Зарплата и управление персоналом») используется немного другой подход. Там решается задача определения ближайшего дня рождения сотрудников относительно текущей даты отчета.
Давайте разберем по шагам логику, которая используется для такого расчета:
РАЗНОСТЬДАТ вычисляется количество полных лет, прошедших с момента рождения до даты отчета.Посмотрим на пример реализации такого условия в запросе:
ВЫБРАТЬ
ТекДанные.ФизическоеЛицо КАК ФизическоеЛицо,
ТекДанные.ДатаРождения КАК ДатаРождения,
ВЫБОР
КОГДА ДОБАВИТЬКДАТЕ(ТекДанные.ДатаРождения, ГОД, РАЗНОСТЬДАТ(ТекДанные.ДатаРождения, &ДатаОтчета, ГОД)) < &ДатаОтчета
ТОГДА ДОБАВИТЬКДАТЕ(ТекДанные.ДатаРождения, ГОД, 1 + РАЗНОСТЬДАТ(ТекДанные.ДатаРождения, &ДатаОтчета, ГОД))
ИНАЧЕ ДОБАВИТЬКДАТЕ(ТекДанные.ДатаРождения, ГОД, РАЗНОСТЬДАТ(ТекДанные.ДатаРождения, &ДатаОтчета, ГОД))
КОНЕЦ КАК БлижайшийДеньРождения
ПОМЕСТИТЬ ВтДаты
ИЗ
РегистрСведений.ТекущиеКадровыеДанныеСотрудников КАК ТекДанные
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВтДаты.ФизическоеЛицо,
ВтДаты.ДатаРождения,
ВтДаты.БлижайшийДеньРождения
ИЗ
ВтДаты КАК ВтДаты
ГДЕ
ВтДаты.БлижайшийДеньРождения МЕЖДУ &ДатаНачала И &ДатаКонца
Этот метод незаменим, когда вам нужно построить отчет вида «Ближайшие дни рождения сотрудников в течение следующих 30 дней».
Выбор конкретного метода зависит от архитектуры вашего решения:
Также помните, что при работе с большими базами данных (сотни тысяч или миллионы контрагентов) использование функций ГОД, МЕСЯЦ, ДЕНЬ или ДОБАВИТЬКДАТЕ в секции ГДЕ приводит к тому, что СУБД не может использовать индексы по полю даты рождения (происходит сканирование таблицы вместо поиска по индексу). В таких высоконагруженных системах рекомендуется хранить день и месяц рождения в отдельном индексируемом числовом реквизите при записи карточки контрагента.