Приветствуем вас, уважаемые коллеги! Каждому разработчику на платформе 1С:Предприятие регулярно приходится решать тривиальную, на первый взгляд, задачу — выборку документов или записей регистров за определенный день. Однако за простотой этой операции скрываются серьезные технические нюансы. Неправильно составленное условие может не только приводить к пустым результатам выборки, но и катастрофически снижать производительность всей информационной базы, вызывая избыточные блокировки СУБД.
В этой статье мы подробно проанализируем, почему простое сравнение дат с точностью до секунды часто не работает, рассмотрим правильные способы поиска данных в границах суток, разберем механизм «порчи» индексов и выясним, почему попытки сравнить реквизит таблицы с объектами типа Граница или МоментВремени в секции ГДЕ вызывают ошибку платформы.
Давайте разберем типичную ситуацию, с которой сталкиваются начинающие разработчики. Допустим, перед нами стоит задача выбрать все документы за определенную дату, которая передается в запрос в качестве параметра. Интуитивно пишется следующий запрос:
ВЫБРАТЬ
Реализация.Ссылка КАК Ссылка,
Реализация.Дата КАК Дата
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Дата = &Дата
Почему этот запрос чаще всего возвращает пустой результат? Дело в том, что в платформе 1С тип данных Дата всегда содержит как саму дату, так и время с точностью до секунды (например, "25.10.2023 14:35:12"). Если пользователь выбирает на форме дату без указания времени, то в параметр &Дата передается значение начала дня — "25.10.2023 00:00:00".
СУБД выполняет строгое сравнение и пытается найти документы, созданные ровно в полночь (в первую секунду дня). Естественно, реальные документы, созданные в течение рабочего дня, имеют другое время и под это условие не подпадают. Наша задача — переписать условие так, чтобы запрос захватывал абсолютно все документы в интервале от 00:00:00 до 23:59:59 выбранного дня.
Первый рабочий и достаточно простой вариант — это вычисление начала и конца дня непосредственно внутри текста запроса с использованием стандартных функций НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА.
Посмотрим на пример такого решения:
ВЫБРАТЬ
Реализация.Ссылка КАК Ссылка,
Реализация.Дата КАК Дата
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Дата МЕЖДУ НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ) И КОНЕЦПЕРИОДА(&Дата, ДЕНЬ)
Рассмотрим подробнее, как этот запрос обрабатывается платформой и СУБД:
&Дата передается в запрос (например, "25.10.2023 15:30:00").НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ) преобразует его в нижнюю границу суток: "25.10.2023 00:00:00".КОНЕЦПЕРИОДА(&Дата, ДЕНЬ) преобразует его в верхнюю границу суток: "25.10.2023 23:59:59".МЕЖДУ выбирает все записи, попадающие в этот закрытый интервал времени.Этот вариант полностью работоспособен и не нарушает логику использования индексов СУБД, поскольку вычисления производятся над константным параметром запроса, а не над физическим полем таблицы в базе данных.
Хотя первый вариант работает отлично, стандарты разработки «1С:Предприятия» рекомендуют выносить любые расчеты дат из текста запроса во встроенный язык. Это считается наиболее профессиональным подходом по ряду причин:
Разберем по шагам, как правильно реализовать этот подход на встроенном языке:
// 1. Создаем объект Запрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка КАК Ссылка,
| Реализация.Дата КАК Дата
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Дата МЕЖДУ &НачалоДня И &КонецДня";
// 2. Рассчитываем границы дня во встроенном языке и передаем в параметры
Запрос.УстановитьПараметр("НачалоДня", НачалоДня(ДатаПараметр));
Запрос.УстановитьПараметр("КонецДня", КонецДня(ДатаПараметр));
// 3. Выполняем запрос
РезультатЗапроса = Запрос.Выполнить();
Функции НачалоДня() и КонецДня() встроенного языка моментально вычисляют нужные временные точки на сервере приложений 1С. В базу данных уходят уже готовые значения, что гарантирует превосходную производительность.
В процессе обсуждения на форумах часто предлагают и другой вариант решения, который кажется очень простым и красивым:
ГДЕ НАЧАЛОПЕРИОДА(Документ.Дата, ДЕНЬ) = НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ)
Внимание! Это классический пример антипаттерна, который категорически запрещено использовать в реальных конфигурациях. Выясним причину, почему этот код крайне опасен для производительности вашей системы.
Реквизит Дата у любого документа в 1С является индексируемым полем. Индекс — это упорядоченная древовидная структура данных (B-Tree), которая позволяет СУБД мгновенно находить нужные строки без чтения всей таблицы (операция Index Seek).
Когда мы пишем ГДЕ Документ.Дата >= &НачалоДня, СУБД обращается к индексу по полю Дата и быстро переходит к нужной ветке дерева поиска.
Но как только мы применяем функцию к самому индексируемому полю — НАЧАЛОПЕРИОДА(Документ.Дата, ДЕНЬ) — СУБД теряет возможность использовать индекс! Индекс построен по физическим значениям дат, а не по результатам работы функции. Чтобы выполнить сравнение, СУБД вынуждена сделать следующее:
На тестовой базе с сотней документов разница во времени выполнения составит миллисекунды. Но в реальной базе (например, в «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, 1, 1).Применяйте эти правила на практике, и ваши программные решения будут работать быстро, надежно и стабильно даже под самыми высокими нагрузками!