Как настроить условие по дате в запросе 1С, чтобы искать документы в пределах дня без потери производительности?

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

Приветствуем вас, уважаемые коллеги! Каждому разработчику на платформе 1С:Предприятие регулярно приходится решать тривиальную, на первый взгляд, задачу — выборку документов или записей регистров за определенный день. Однако за простотой этой операции скрываются серьезные технические нюансы. Неправильно составленное условие может не только приводить к пустым результатам выборки, но и катастрофически снижать производительность всей информационной базы, вызывая избыточные блокировки СУБД.

В этой статье мы подробно проанализируем, почему простое сравнение дат с точностью до секунды часто не работает, рассмотрим правильные способы поиска данных в границах суток, разберем механизм «порчи» индексов и выясним, почему попытки сравнить реквизит таблицы с объектами типа Граница или МоментВремени в секции ГДЕ вызывают ошибку платформы.

Постановка проблемы: почему простое равенство дат не находит документы?

Давайте разберем типичную ситуацию, с которой сталкиваются начинающие разработчики. Допустим, перед нами стоит задача выбрать все документы за определенную дату, которая передается в запрос в качестве параметра. Интуитивно пишется следующий запрос:


ВЫБРАТЬ
    Реализация.Ссылка КАК Ссылка,
    Реализация.Дата КАК Дата
ИЗ
    Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
    Реализация.Дата = &Дата

Почему этот запрос чаще всего возвращает пустой результат? Дело в том, что в платформе 1С тип данных Дата всегда содержит как саму дату, так и время с точностью до секунды (например, "25.10.2023 14:35:12"). Если пользователь выбирает на форме дату без указания времени, то в параметр &Дата передается значение начала дня — "25.10.2023 00:00:00".

СУБД выполняет строгое сравнение и пытается найти документы, созданные ровно в полночь (в первую секунду дня). Естественно, реальные документы, созданные в течение рабочего дня, имеют другое время и под это условие не подпадают. Наша задача — переписать условие так, чтобы запрос захватывал абсолютно все документы в интервале от 00:00:00 до 23:59:59 выбранного дня.

Решение №1: Использование конструкции МЕЖДУ прямо в запросе

Первый рабочий и достаточно простой вариант — это вычисление начала и конца дня непосредственно внутри текста запроса с использованием стандартных функций НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА.

Посмотрим на пример такого решения:


ВЫБРАТЬ
    Реализация.Ссылка КАК Ссылка,
    Реализация.Дата КАК Дата
ИЗ
    Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
    Реализация.Дата МЕЖДУ НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ) И КОНЕЦПЕРИОДА(&Дата, ДЕНЬ)

Рассмотрим подробнее, как этот запрос обрабатывается платформой и СУБД:

  1. Параметр &Дата передается в запрос (например, "25.10.2023 15:30:00").
  2. Функция НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ) преобразует его в нижнюю границу суток: "25.10.2023 00:00:00".
  3. Функция КОНЕЦПЕРИОДА(&Дата, ДЕНЬ) преобразует его в верхнюю границу суток: "25.10.2023 23:59:59".
  4. Оператор МЕЖДУ выбирает все записи, попадающие в этот закрытый интервал времени.

Этот вариант полностью работоспособен и не нарушает логику использования индексов СУБД, поскольку вычисления производятся над константным параметром запроса, а не над физическим полем таблицы в базе данных.

Решение №2 (Лучшая практика): Подготовка параметров на стороне встроенного языка

Хотя первый вариант работает отлично, стандарты разработки «1С:Предприятия» рекомендуют выносить любые расчеты дат из текста запроса во встроенный язык. Это считается наиболее профессиональным подходом по ряду причин:

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


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

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

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

Функции НачалоДня() и КонецДня() встроенного языка моментально вычисляют нужные временные точки на сервере приложений 1С. В базу данных уходят уже готовые значения, что гарантирует превосходную производительность.

Критическая ошибка: Почему функция НАЧАЛОПЕРИОДА над полем таблицы — это катастрофа?

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

ГДЕ НАЧАЛОПЕРИОДА(Документ.Дата, ДЕНЬ) = НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ)

Внимание! Это классический пример антипаттерна, который категорически запрещено использовать в реальных конфигурациях. Выясним причину, почему этот код крайне опасен для производительности вашей системы.

Реквизит Дата у любого документа в 1С является индексируемым полем. Индекс — это упорядоченная древовидная структура данных (B-Tree), которая позволяет СУБД мгновенно находить нужные строки без чтения всей таблицы (операция Index Seek).

Когда мы пишем ГДЕ Документ.Дата >= &НачалоДня, СУБД обращается к индексу по полю Дата и быстро переходит к нужной ветке дерева поиска.

Но как только мы применяем функцию к самому индексируемому полю — НАЧАЛОПЕРИОДА(Документ.Дата, ДЕНЬ) — СУБД теряет возможность использовать индекс! Индекс построен по физическим значениям дат, а не по результатам работы функции. Чтобы выполнить сравнение, СУБД вынуждена сделать следующее:

  1. Прочитать абсолютно каждую запись в таблице документов (выполнить ресурсоемкую операцию Table Scan или Index Scan).
  2. Для каждой записи "на лету" вычислить результат функции начала дня.
  3. Сравнить полученное значение с параметром.

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

Почему Граница и МоментВремени вызывают ошибку в секции ГДЕ?

Иногда у разработчиков возникает идея использовать объекты типа Граница или МоментВремени для ограничения выборки документов по дате в условии ГДЕ. Например, пишется такой код:


// Попытка использовать Границу в условии ГДЕ
ГраницаВыборки = Новый Граница(КонецДня(ДатаПараметр), ВидГраницы.Включая);
Запрос.УстановитьПараметр("Граница", ГраницаВыборки);

И в самом запросе:

ГДЕ Документ.Дата <= &Граница

При выполнении такого кода платформа 1С выбросит исключение: «Неверные параметры в операции сравнения. Нельзя сравнивать поля неограниченной длины и поля несовместимых типов».

Проанализируем причину ошибки. Физическая таблица в базе данных СУБД хранит реквизит Дата как поле стандартного типа DATETIME. В то же время Граница и МоментВремени — это сложные высокоуровневые объекты платформы 1С, состоящие из даты и ссылки на уникальный идентификатор (UUID) документа-регистратора.

СУБД на физическом уровне не имеет типа данных, аналогичного Границе, и не может напрямую сравнить простую дату со сложным объектом. Именно поэтому их сравнение в секции ГДЕ приводит к ошибке несовместимости типов.

Важное правило: Объекты Граница и МоментВремени предназначены исключительно для использования в качестве параметров виртуальных таблиц (например, таблиц остатков и оборотов регистров накопления или бухгалтерии). Там платформа 1С самостоятельно под капотом транслирует эти объекты в сложные SQL-инструкции для корректного позиционирования на оси времени.

Нюансы работы с датами в Системе компоновки данных (СКД)

Если вы проектируете отчет на СКД и используете в запросе ручные условия фильтрации по датам, обратите внимание на специфику работы компоновщика макета.

Если вы назовете параметры запроса стандартными именами &НачалоПериода и &КонецПериода, СКД при включенном автозаполнении попытается перехватить управление ими. Платформа может автоматически связать их с параметрами виртуальных таблиц или общими настройками периода отчета, проигнорировав ваши явные настройки условий в тексте запроса.

Чтобы избежать неявного поведения в СКД, используйте для произвольных фильтров нестандартные, уникальные имена параметров (например, &ДатаНачалаФильтра и &ДатаКонцаФильтра). Это гарантирует, что ваши условия в секции ГДЕ отработают ровно так, как вы их спроектировали.

Полезный прием: Быстрая проверка на пустую дату в запросе

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

Для проверки на незаполненную дату в запросах 1С используется специальный литерал ДАТАВРЕМЯ(1, 1, 1), который на уровне СУБД преобразуется в минимальное значение даты ("01.01.0001 00:00:00").

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


ГДЕ
    Документ.Дата <> ДАТАВРЕМЯ(1, 1, 1)

Это условие отлично оптимизировано, полностью задействует индекс по полю даты и мгновенно отсекает некорректные записи.

Подведем итоги нашей совместной работы

Мы детально разобрали все ключевые аспекты работы с датами в запросах 1С. Давайте зафиксируем золотые правила, которые помогут вам писать быстрый и качественный код:

  1. Никогда не используйте прямое равенство = &Дата, если вам нужно выбрать документы за день. Всегда стройте фильтр в интервале суток.
  2. Наиболее оптимальным решением является подготовка параметров НачалоДня и КонецДня во встроенном языке с последующей передачей их в конструкцию МЕЖДУ в запросе.
  3. Категорически избегайте использования любых функций над индексируемыми полями таблиц базы данных в условиях ГДЕ. Это «убивает» индекс и замедляет работу всей системы.
  4. Помните, что объекты Граница и МоментВремени несовместимы с простыми датами в условиях сравнения. Используйте их только в параметрах виртуальных таблиц регистров.
  5. Для отсечения пустых дат в условиях запроса всегда применяйте сравнение с конструкцией ДАТАВРЕМЯ(1, 1, 1).

Применяйте эти правила на практике, и ваши программные решения будут работать быстро, надежно и стабильно даже под самыми высокими нагрузками!

← На главную