Как правильно передать и сравнить дату в условии запроса 1С 8.3?

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

При разработке отчетов и обработок в 1С часто возникает задача отфильтровать данные справочника или регистра по дате, введенной пользователем на форме. Начинающие разработчики нередко сталкиваются с ситуацией, когда запрос возвращает пустой результат, хотя запись с указанной датой гарантированно присутствует в информационной базе. Разберем по шагам, почему возникает эта ошибка и как правильно организовать сравнение дат в языке запросов платформы 1С:Предприятие 8.3.

Анализ проблемы: почему запрос возвращает пустой результат

Проанализируем типичный ошибочный код, в котором текст запроса формируется путем конкатенации строк:


Запрос.Текст = "ВЫБРАТЬ
               |    ФизЛицо.Наименование,
               |    ФизЛицо.ДатаРождения
               |ИЗ
               |    Справочник.ФизическиеЛица КАК ФизЛицо
               |ГДЕ
               |    ФизЛицо.Наименование = """ + ФИОТекст + """
               |    И ФизЛицо.ДатаРождения = """ + Формат(ДатаРождения, "ДФ=dd.MM.yyyy; ДЛФ=D") + """";

В этом примере допущены сразу две серьезные ошибки:

  1. Несоответствие типов данных. Функция Формат() преобразует значение типа Дата в значение типа Строка. В итоге после подстановки в тексте запроса получается условие вида ФизЛицо.ДатаРождения = "01.01.2018". Поле базы данных имеет тип Дата, а сравнивается оно со строковым литералом. В строго типизированных СУБД такое сравнение всегда дает ложный результат (или ошибку), поэтому выборка оказывается пустой.
  2. Конкатенация строк вместо параметров. Склеивание текста запроса вручную нарушает стандарты разработки 1С, усложняет чтение кода и может приводить к ошибкам синтаксиса или уязвимостям.

Способ 1. Передача даты через параметры запроса (рекомендуемый стандарт)

Рассмотрим правильный подход: текст запроса должен оставаться статичным, а все пользовательские значения необходимо передавать через механизм параметров с помощью метода УстановитьПараметр().


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

Запрос.УстановитьПараметр("Наименование", ФИОТекст);
Запрос.УстановитьПараметр("ДатаРождения", НачалоДня(ДатаРождения));

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

Обратите внимание на использование функции НачалоДня(). В платформе 1С тип Дата всегда содержит время с точностью до секунды. Если у реквизита справочника в конфигураторе установлен состав даты «Дата», платформа сохраняет время как 00:00:00. Приведение введенной даты к началу дня гарантирует совпадение времени.

Способ 2. Поиск по диапазону дат с сохранением индексов (SARGability)

Иногда разработчики пытаются отсечь время непосредственно в тексте запроса с помощью встроенных функций платформы:


ГДЕ
    НАЧАЛОПЕРИОДА(ФизЛицо.ДатаРождения, ДЕНЬ) = НАЧАЛОПЕРИОДА(&ДатаРождения, ДЕНЬ)

Выясним, почему так делать не рекомендуется: применение функций к полям таблиц в блоке ГДЕ делает выражение «не-SARGable». СУБД не может применить индекс по полю ДатаРождения и вынуждена выполнять полное сканирование таблицы (Table Scan), что существенно замедляет работу на больших объемах данных.

Для эффективного поиска по дате без потери производительности сформируем границы интервала во встроенном языке и используем оператор МЕЖДУ:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ФизЛицо.Ссылка,
    |    ФизЛицо.Наименование,
    |    ФизЛицо.ДатаРождения
    |ИЗ
    |    Справочник.ФизическиеЛица КАК ФизЛицо
    |ГДЕ
    |    ФизЛицо.ДатаРождения МЕЖДУ &ДатаНачала И &ДатаОкончания";

Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ДатаРождения));
Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(ДатаРождения));

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

Способ 3. Использование литералов даты

Если дату необходимо жестко зафиксировать в исходном коде или тексте запроса без передачи переменной, используются специальные литералы:

  1. В тексте запроса: функция ДАТАВРЕМЯ(ГГГГ, ММ, ДД) или ДАТАВРЕМЯ(ГГГГ, ММ, ДД, ЧЧ, ММ, СС).
    
    // Пример условия в запросе с литералом даты
    ГДЕ
        ФизЛицо.ДатаРождения = ДАТАВРЕМЯ(2018, 1, 1)
    
  2. Во встроенном языке: строковый литерал в одинарных кавычках в формате 'ГГГГММДД' или 'ГГГГММДДЧЧММСС'.
    
    // Пример инициализации даты в коде
    ФиксированнаяДата = '20180101';
    Запрос.УстановитьПараметр("ДатаРождения", ФиксированнаяДата);
    

Для обозначения пустой даты в языке запросов используется литерал ДАТАВРЕМЯ(1, 1, 1), а во встроенном языке — '00010101000000' или метод ПустаяДата().

Способ 4. Поиск без использования запроса

Если требуется быстро найти элемент справочника по точному значению реквизита, можно воспользоваться методом менеджера справочника НайтиПоРеквизиту():


НайденнаяСсылка = Справочники.ФизическиеЛица.НайтиПоРеквизиту("ДатаРождения", НачалоДня(ДатаРождения));
Если ЗначениеЗаполнено(НайденнаяСсылка) Тогда
    // Элемент успешно найден
КонецЕсли;

Посмотрим на ограничения этого метода: он возвращает только одну первую найденную ссылку и эффективен только в том случае, если для реквизита в конфигураторе установлено свойство «Индексировать».

← На главную