При разработке отчетов и механизмов автоматизации в 1С часто возникает задача сделать выборку записей по фиксированному числу и месяцу, абстрагируясь от конкретного года. Классический пример — получение списка физических лиц или владельцев дисконтных карт, у которых сегодня день рождения (например, 21 декабря).
В этой статье мы подробно разберем, как правильно составить такой запрос на языке 1С, рассмотрим основные функции работы с датами, выявим ошибки использования функции ДЕНЬГОДА и проанализируем варианты оптимизации производительности СУБД.
В языке запросов 1С предусмотрены специальные встроенные функции для извлечения отдельных составляющих даты: ДЕНЬ() и МЕСЯЦ(). Они принимают на вход поле типа Дата и возвращают соответствующее числовое значение.
Рассмотрим, как правильно передать параметры и написать текст запроса для поиска именинников, родившихся 21 декабря:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ФизическиеЛица.Ссылка КАК Ссылка
|ИЗ
| Справочник.ФизическиеЛица КАК ФизическиеЛица
|ГДЕ
| ДЕНЬ(ФизическиеЛица.ДатаРождения) = &ДеньРождения
| И МЕСЯЦ(ФизическиеЛица.ДатаРождения) = &МесяцРождения
| И ФизическиеЛица.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1)";
Запрос.УстановитьПараметр("ДеньРождения", День(мДеньРождения));
Запрос.УстановитьПараметр("МесяцРождения", Месяц(мДеньРождения));
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Обработка полученных данных
КонецЦикла;
Разберем по шагам ключевые моменты этого кода:
День() и Месяц() извлекаются числовые значения дня и месяца.&ДеньРождения и &МесяцРождения.ГДЕ используются встроенные функции языка запросов ДЕНЬ() и МЕСЯЦ(), сопоставляющие значение в базе данных с переданными параметрами.Иногда разработчики пытаются упростить запрос и применить функцию ДЕНЬГОДА(), сравнивая порядковый номер дня в году для даты рождения и целевой даты. Однако данный подход ошибочен из-за существования високосных годов.
Проанализируем пример: дата 21 декабря в обычный (невисокосный) год является 355-м днем года, а в високосный год — 356-м днем. Если сравнить ДЕНЬГОДА(ДатаРождения) с ДЕНЬГОДА(&НужнаяДата), то выборка для людей, родившихся в високосный год, даст сбой.
Посмотрим на наглядную демонстрацию расчета порядкового дня года для даты 21 декабря в разные года:
// В обычный год (2010, 2011, 2013):
ДЕНЬГОДА(ДАТАВРЕМЯ(2010, 12, 21)) // Возвращает 355
// В високосный год (2012, 2016):
ДЕНЬГОДА(ДАТАВРЕМЯ(2012, 12, 21)) // Возвращает 356
Таким образом, использование функции ДЕНЬГОДА() приведет к пропуску данных, поэтому для поиска по числу и месяцу следует применять только связку ДЕНЬ() и МЕСЯЦ().
При написании запросов отбора по датам важно учитывать следующие практические нюансы:
Дата хранит значение 0001-01-01 00:00:00. При передаче незаполненной даты в функции ДЕНЬ() и МЕСЯЦ() они возвращают значение 1. В результате при поиске именинников на 1 января в выборку попадут все элементы справочника с пустой датой рождения. Во избежание этого обязательно добавляем условие проверки: И ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1).ИЛИ.С точки зрения работы СУБД использование любых функций (включая ДЕНЬ() и МЕСЯЦ()) над реквизитами в условии ГДЕ приводит к тому, что СУБД не может задействовать стандартный индекс по полю типа Дата. В результате выполняется полный просмотр таблицы (Table Scan), что на крупных справочниках (сотни тысяч записей) может снизить скорость выполнения запроса.
Для высоконагруженных систем разберем оптимальный вариант архитектуры:
ДеньРождения_Число (Число, 2) и ДеньРождения_Месяц (Число, 2) либо единый реквизит ДеньИМесяц (формата ММДД, Число, 4).ПередЗаписью модуля объекта справочника.
ГДЕ
ФизическиеЛица.ДеньРождения_Месяц = &Месяц
И ФизическиеЛица.ДеньРождения_Число = &День
Такой подход позволяет СУБД мгновенно находить нужные записи через поиск по индексу (Index Seek) без сканирования всей таблицы.