При разработке сложных механизмов проведения документов, построения последовательностей или кастомных регистров разработчики часто сталкиваются с необходимостью жесткого отбора документов на оси времени. В платформе «1С:Предприятие» для однозначного позиционирования существует тип данных МоментВремени, сочетающий в себе дату-время документа и его уникальную ссылку. Однако попытка использовать это поле в секции ГДЕ обычного запроса часто преподносит неприятные сюрпризы: документы на границах периода неожиданно «выпадают» из выборки, а операторы сравнения ведут себя нелогично.
В этой статье мы подробно разберем, почему виртуальное поле МоментВремени некорректно отрабатывает в условиях ГДЕ, почему нельзя использовать объект Граница в обычных запросах, и как правильно решить эту задачу с точки зрения производительности СУБД.
Рассмотрим классическую ситуацию. У нас есть запрос, в котором мы хотим отобрать документы за определенный интервал, используя параметры типа МоментВремени:
ВЫБРАТЬ
ОтбираемыйДокумент.Ссылка КАК Ссылка
ПОМЕСТИТЬ ТаблицаДокументов
ИЗ
Документ.РеализацияТоваров КАК ОтбираемыйДокумент
ГДЕ
ОтбираемыйДокумент.Проведен
И ОтбираемыйДокумент.МоментВремени >= &ДатаНачала
И ОтбираемыйДокумент.МоментВремени <= &ДатаОкончания
На практике при выполнении такого запроса программисты замечают, что документ, соответствующий крайней верхней границе (&ДатаОкончания), игнорируется платформой и не попадает в итоговую выборку. При этом использование оператора МЕЖДУ дает точно такой же ошибочный результат. Условие >= отрабатывает корректно, а вот <= при равенстве моментов возвращает ложь.
Давайте разберем по шагам, почему так происходит, и заглянем «под капот» работы платформы 1С и СУБД.
Поле МоментВремени является виртуальным. В таблицах баз данных (будь то MS SQL Server, PostgreSQL или файловая база) такого поля физически не существует. Платформа конструирует его «на лету» из двух реальных колонок физической таблицы документа:
_Date_Time)._IDRRef).Когда мы пишем условие ОтбираемыйДокумент.МоментВремени <= &МоментВремени, транслятор запросов 1С (SDBL-процессор) вынужден преобразовывать это простое условие в сложную логическую конструкцию для СУБД. В SQL-файле это выглядит следующим образом:
WHERE (T1._Date_Time <= @P1 AND (T1._Date_Time < @P2 OR (T1._Date_Time = @P3 AND (T1._IDRRef <= @P4))))
Из-за такой громоздкой структуры СУБД не всегда может корректно построить план запроса, а в некоторых версиях платформы (особенно при работе с файловыми базами данных или устаревшими релизами SQL-серверов) логика сравнения уникальных идентификаторов _IDRRef при равенстве дат дает сбой. Ссылка типа varbinary(16) сравнивается побайтово, и логические ветвления СУБД могут отсекать граничные значения.
Пытаясь обойти проблему, разработчики часто совершают две классические ошибки.
Ошибка №1: Сравнение параметров типа МоментВремени напрямую
Если попытаться сравнить два параметра этого типа в вычисляемых полях запроса, например:
ВЫБРАТЬ
&МоментПараметр1 <= &МоментПараметр2 КАК РезультатСравнения
Платформа незамедлительно вернет ошибку: «Неверные параметры в операции сравнения. Нельзя сравнивать поля неограниченной длины и поля несовместимых типов». Платформа 1С не умеет сопоставлять сложные составные типы данных вне контекста полей базы данных.
Ошибка №2: Передача объекта «Граница» в условие ГДЕ
Еще одна частая попытка решить проблему — использовать класс Граница с видом границы Включая:
Запрос.УстановитьПараметр("ДатаОкончания", Новый Граница(КонецПериода, ВидГраницы.Включая));
В этом случае запрос аварийно завершится с ошибкой несовместимости типов. Объект Граница спроектирован исключительно для передачи в параметры виртуальных таблиц (таких как Остатки, Обороты, СрезПоследних). Обычный транслятор секции ГДЕ не умеет преобразовывать логику включения/исключения границы в SQL-код для физических таблиц документов.
Самый надежный, быстрый и правильный с точки зрения оптимизации СУБД способ обойти эту проблему — отказаться от прямого использования виртуального поля МоментВремени в секции ГДЕ и разложить условие на две составляющие: Дату и Ссылку.
Проанализируем ситуацию: нам нужно отобрать документы, которые находятся строго внутри интервала, заданного двумя моментами времени (или конкретными документами-границами). Мы можем передать в параметры запроса отдельно даты границ и отдельно ссылки этих документов.
Рассмотрим, как будет выглядеть правильный текст запроса:
ВЫБРАТЬ
Реализация.Ссылка КАК Ссылка
ИЗ
Документ.РеализацияТоваров КАК Реализация
ГДЕ
Реализация.Проведен
И (Реализация.Дата > &ДатаНачала
ИЛИ (Реализация.Дата = &ДатаНачала И Реализация.Ссылка >= &СсылкаНачала))
И (Реализация.Дата < &ДатаОкончания
ИЛИ (Реализация.Дата = &ДатаОкончания И Реализация.Ссылка <= &СсылкаОкончания))
В модуле перед выполнением запроса мы инициализируем параметры следующим образом:
// В качестве входящих границ у нас есть ГраницаНачала и ГраницаОкончания (типа МоментВремени)
Запрос.УстановитьПараметр("ДатаНачала", ГраницаНачала.Дата);
Запрос.УстановитьПараметр("СсылкаНачала", ГраницаНачала.Ссылка);
Запрос.УстановитьПараметр("ДатаОкончания", ГраницаОкончания.Дата);
Запрос.УстановитьПараметр("СсылкаОкончания", ГраницаОкончания.Ссылка);
Такой подход обладает сразу несколькими неоспоримыми преимуществами:
<= &СсылкаОкончания при равенстве секунд отрабатывает на уровне СУБД безупречно.Дата и Ссылка). Оптимизатор может эффективно использовать индекс по дате документа (Index Seek) вместо сканирования таблицы (Index Scan).Если ваша задача связана с получением итогов, остатков или оборотов по регистру сведений/накопления на определенный момент времени, лучшим решением будет полный отказ от фильтрации документов в физической таблице. Перенесите логику в параметры виртуальной таблицы регистра:
ВЫБРАТЬ
ПродажиОбороты.Номенклатура,
ПродажиОбороты.КоличествоОборот
ИЗ
РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания, , ) КАК ПродажиОбороты
В данном случае в параметры &ДатаНачала и &ДатаОкончания вы можете смело передавать объекты типа Граница:
ГраницаКонца = Новый Граница(МоментВремениДокумента, ВидГраницы.Включая);
Запрос.УстановитьПараметр("ДатаОкончания", ГраницаКонца);
Платформа самостоятельно сгенерирует оптимальный и корректный SQL-код, который правильно рассчитает итоги регистра с точностью до конкретного документа включительно.
Подведем итог и сформулируем правила работы с временными точками в запросах 1С:
МоментВремени в секции ГДЕ физических таблиц документов. Сравнение <= часто работает некорректно и «теряет» документы на границе периода.Граница в параметры условия ГДЕ — это приведет к ошибке несовместимости типов. Границы предназначены только для параметров виртуальных таблиц.