При работе с реквизитами типа Дата в платформе 1С (например, при получении списка именинников из справочника ИнформационныеКарты или Контрагенты) часто возникает задача сделать отбор по дню и месяцу, полностью проигнорировав год рождения. Рассмотрим основные способы решения этой задачи в языке запросов 1С, а также важные нюансы, связанные с пустыми датами, 29 февраля и производительностью.
Язык запросов 1С предоставляет встроенные функции ДЕНЬ() и МЕСЯЦ(), которые позволяют извлечь числовые значения дня и месяца из поля даты прямо в запросе.
Если необходимо получить список именинников на текущий день (или конкретную дату, переданную в параметр &ТекДата), проанализируем следующий пример запроса:
ВЫБРАТЬ
ИнформационныеКарты.Ссылка КАК Ссылка,
ИнформационныеКарты.ДеньРождения КАК ДеньРождения
ИЗ
Справочник.ИнформационныеКарты КАК ИнформационныеКарты
ГДЕ
ИнформационныеКарты.ДеньРождения <> ДАТАВРЕМЯ(1, 1, 1)
И ДЕНЬ(ИнформационныеКарты.ДеньРождения) = ДЕНЬ(&ТекДата)
И МЕСЯЦ(ИнформационныеКарты.ДеньРождения) = МЕСЯЦ(&ТекДата)
Если требуется выбрать элементы за фиксированный день (например, 21 декабря), условие в секции ГДЕ можно записать через конкретные числа:
ГДЕ
ИнформационныеКарты.ДеньРождения <> ДАТАВРЕМЯ(1, 1, 1)
И ДЕНЬ(ИнформационныеКарты.ДеньРождения) = 21
И МЕСЯЦ(ИнформационныеКарты.ДеньРождения) = 12
Важное замечание о пустых датах: Незаполненная дата в платформе 1С хранится как 01.01.0001 00:00:00. Вызов функций ДЕНЬ() и МЕСЯЦ() для пустой даты возвращает значение 1. Поэтому при поиске именинников на 1 января без проверки ДеньРождения <> ДАТАВРЕМЯ(1, 1, 1) в выборку ошибочно попадут все элементы с незаполненной датой рождения.
Если задача усложняется и требуется выбрать дни рождения за произвольный период (например, на ближайшую неделю), простой отбор по номерам дней и месяцев работать не будет. В этом случае дату рождения приводят к текущему году с помощью функции ДОБАВИТЬКДАТЕ().
Рассмотрим пример запроса для отбора контрагентов, чьи дни рождения попадают в интервал от &ДатаНач до &ДатаКон:
ВЫБРАТЬ
Контрагенты.Ссылка КАК Контрагент,
Контрагенты.ДатаРождения КАК ДатаРождения
ИЗ
Справочник.Контрагенты КАК Контрагенты
ГДЕ
НЕ Контрагенты.ЭтоГруппа
И Контрагенты.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1)
И ВЫБОР
КОГДА ((ДОБАВИТЬКДАТЕ(Контрагенты.ДатаРождения, ГОД, (ГОД(&ДатаНач) - ГОД(Контрагенты.ДатаРождения))) >= &ДатаНач
И ДОБАВИТЬКДАТЕ(Контрагенты.ДатаРождения, ГОД, (ГОД(&ДатаНач) - ГОД(Контрагенты.ДатаРождения))) <= &ДатаКон)
ИЛИ (ДОБАВИТЬКДАТЕ(Контрагенты.ДатаРождения, ГОД, (ГОД(&ДатаКон) - ГОД(Контрагенты.ДатаРождения))) <= &ДатаКон
И ДОБАВИТЬКДАТЕ(Контрагенты.ДатаРождения, ГОД, (ГОД(&ДатаКон) - ГОД(Контрагенты.ДатаРождения))) >= &ДатаНач)) ТОГДА
ИСТИНА
ИНАЧЕ
ЛОЖЬ
КОНЕЦ
Данный подход корректно обрабатывает даже те периоды, которые пересекают границу года (например, с 25 декабря по 10 января).
При обработке дат рождения следует учитывать високосные года:
ДОБАВИТЬКДАТЕ() для приведения года дата 29.02 в невисокосном году автоматически трансформируется платформой 1С в 28.02.ДЕНЬ = 29 И МЕСЯЦ = 2, то в невисокосные года такие люди не попадут в отчет. Чтобы поздравить их 28 февраля, в условие запроса при проверке на 28 февраля можно добавить логический блок ИЛИ (ДЕНЬ(Дата) = 29 И МЕСЯЦ(Дата) = 2).Использование функций ДЕНЬ(), МЕСЯЦ() или ДОБАВИТЬКДАТЕ() непосредственно в условии ГДЕ делает невозможным использование индексов СУБД по полю даты. В результате СУБД выполняет сканирование всех записей таблицы (Table Scan), что на справочниках объемом в десятки и сотни тысяч записей приводит к падению скорости выполнения запроса.
Для высоконагруженных систем рекомендуется следующий архитектурный подход:
ДеньРожденияДень и ДеньРожденияМесяц (или один строковый реквизит вида "ММДД").ПередЗаписью модуля объекта при изменении основной даты рождения.