Как в запросе 1С выбрать данные по дню и месяцу

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

При разработке отчетов и механизмов автоматизации в 1С часто возникает задача сделать выборку записей по фиксированному числу и месяцу, абстрагируясь от конкретного года. Классический пример — получение списка физических лиц или владельцев дисконтных карт, у которых сегодня день рождения (например, 21 декабря).

В этой статье мы подробно разберем, как правильно составить такой запрос на языке 1С, рассмотрим основные функции работы с датами, выявим ошибки использования функции ДЕНЬГОДА и проанализируем варианты оптимизации производительности СУБД.

Базовое решение: использование функций ДЕНЬ и МЕСЯЦ

В языке запросов 1С предусмотрены специальные встроенные функции для извлечения отдельных составляющих даты: ДЕНЬ() и МЕСЯЦ(). Они принимают на вход поле типа Дата и возвращают соответствующее числовое значение.

Рассмотрим, как правильно передать параметры и написать текст запроса для поиска именинников, родившихся 21 декабря:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ФизическиеЛица.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.ФизическиеЛица КАК ФизическиеЛица
    |ГДЕ
    |	ДЕНЬ(ФизическиеЛица.ДатаРождения) = &ДеньРождения
    |	И МЕСЯЦ(ФизическиеЛица.ДатаРождения) = &МесяцРождения
    |	И ФизическиеЛица.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1)";

Запрос.УстановитьПараметр("ДеньРождения", День(мДеньРождения));
Запрос.УстановитьПараметр("МесяцРождения", Месяц(мДеньРождения));

РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

Пока Выборка.Следующий() Цикл
    // Обработка полученных данных
КонецЦикла;

Разберем по шагам ключевые моменты этого кода:

  1. Из исходной даты в коде 1С с помощью функций День() и Месяц() извлекаются числовые значения дня и месяца.
  2. Значения передаются в запрос через параметры &ДеньРождения и &МесяцРождения.
  3. В секции ГДЕ используются встроенные функции языка запросов ДЕНЬ() и МЕСЯЦ(), сопоставляющие значение в базе данных с переданными параметрами.

Почему нельзя использовать функцию ДЕНЬГОДА

Иногда разработчики пытаются упростить запрос и применить функцию ДЕНЬГОДА(), сравнивая порядковый номер дня в году для даты рождения и целевой даты. Однако данный подход ошибочен из-за существования високосных годов.

Проанализируем пример: дата 21 декабря в обычный (невисокосный) год является 355-м днем года, а в високосный год — 356-м днем. Если сравнить ДЕНЬГОДА(ДатаРождения) с ДЕНЬГОДА(&НужнаяДата), то выборка для людей, родившихся в високосный год, даст сбой.

Посмотрим на наглядную демонстрацию расчета порядкового дня года для даты 21 декабря в разные года:


// В обычный год (2010, 2011, 2013):
ДЕНЬГОДА(ДАТАВРЕМЯ(2010, 12, 21)) // Возвращает 355

// В високосный год (2012, 2016):
ДЕНЬГОДА(ДАТАВРЕМЯ(2012, 12, 21)) // Возвращает 356

Таким образом, использование функции ДЕНЬГОДА() приведет к пропуску данных, поэтому для поиска по числу и месяцу следует применять только связку ДЕНЬ() и МЕСЯЦ().

Нюансы и крайние случаи

При написании запросов отбора по датам важно учитывать следующие практические нюансы:

  1. Обработка пустых дат. В базе данных 1С незаполненное поле типа Дата хранит значение 0001-01-01 00:00:00. При передаче незаполненной даты в функции ДЕНЬ() и МЕСЯЦ() они возвращают значение 1. В результате при поиске именинников на 1 января в выборку попадут все элементы справочника с пустой датой рождения. Во избежание этого обязательно добавляем условие проверки: И ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1).
  2. Обработка родившихся 29 февраля. В невисокосный год 29 февраля отсутствует в календаре. Если требуется поздравить человека, родившегося 29 февраля, в невисокосный год (например, 28 февраля или 1 марта), необходимо программно проверять високосность текущего года и расширять условие запроса с помощью оператора ИЛИ.

Вопросы производительности и оптимизация

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

Для высоконагруженных систем разберем оптимальный вариант архитектуры:

  1. Добавить в справочник отдельные числовые реквизиты: ДеньРождения_Число (Число, 2) и ДеньРождения_Месяц (Число, 2) либо единый реквизит ДеньИМесяц (формата ММДД, Число, 4).
  2. Установить у этих реквизитов свойство Индексировать.
  3. Заполнять их автоматически в событии ПередЗаписью модуля объекта справочника.
  4. В запросе выполнять отбор напрямую по индексированным полям без вызова функций:
    
    ГДЕ
    	ФизическиеЛица.ДеньРождения_Месяц = &Месяц
    	И ФизическиеЛица.ДеньРождения_Число = &День
    

Такой подход позволяет СУБД мгновенно находить нужные записи через поиск по индексу (Index Seek) без сканирования всей таблицы.

← На главную