Почему не работает сравнение по Моменту Времени в условии ГДЕ запроса 1С и как это исправить?

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

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

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

В чем суть проблемы?

Рассмотрим классическую ситуацию. У нас есть запрос, в котором мы хотим отобрать документы за определенный интервал, используя параметры типа МоментВремени:


ВЫБРАТЬ
    ОтбираемыйДокумент.Ссылка КАК Ссылка
ПОМЕСТИТЬ ТаблицаДокументов
ИЗ
    Документ.РеализацияТоваров КАК ОтбираемыйДокумент
ГДЕ
    ОтбираемыйДокумент.Проведен
    И ОтбираемыйДокумент.МоментВремени >= &ДатаНачала 
    И ОтбираемыйДокумент.МоментВремени <= &ДатаОкончания

На практике при выполнении такого запроса программисты замечают, что документ, соответствующий крайней верхней границе (&ДатаОкончания), игнорируется платформой и не попадает в итоговую выборку. При этом использование оператора МЕЖДУ дает точно такой же ошибочный результат. Условие >= отрабатывает корректно, а вот <= при равенстве моментов возвращает ложь.

Давайте разберем по шагам, почему так происходит, и заглянем «под капот» работы платформы 1С и СУБД.

Как СУБД видит Момент Времени?

Поле МоментВремени является виртуальным. В таблицах баз данных (будь то MS SQL Server, PostgreSQL или файловая база) такого поля физически не существует. Платформа конструирует его «на лету» из двух реальных колонок физической таблицы документа:

  1. Поле даты и времени (в SQL-базах обычно транслируется как _Date_Time).
  2. Поле уникального идентификатора ссылки документа (транслируется как _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-код для физических таблиц документов.

Решение проблемы: Разделение условий на Дату и Ссылку

Самый надежный, быстрый и правильный с точки зрения оптимизации СУБД способ обойти эту проблему — отказаться от прямого использования виртуального поля МоментВремени в секции ГДЕ и разложить условие на две составляющие: Дату и Ссылку.

Проанализируем ситуацию: нам нужно отобрать документы, которые находятся строго внутри интервала, заданного двумя моментами времени (или конкретными документами-границами). Мы можем передать в параметры запроса отдельно даты границ и отдельно ссылки этих документов.

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


ВЫБРАТЬ
    Реализация.Ссылка КАК Ссылка
ИЗ
    Документ.РеализацияТоваров КАК Реализация
ГДЕ
    Реализация.Проведен
    И (Реализация.Дата > &ДатаНачала
        ИЛИ (Реализация.Дата = &ДатаНачала И Реализация.Ссылка >= &СсылкаНачала))
    И (Реализация.Дата < &ДатаОкончания
        ИЛИ (Реализация.Дата = &ДатаОкончания И Реализация.Ссылка <= &СсылкаОкончания))

В модуле перед выполнением запроса мы инициализируем параметры следующим образом:


// В качестве входящих границ у нас есть ГраницаНачала и ГраницаОкончания (типа МоментВремени)
Запрос.УстановитьПараметр("ДатаНачала", ГраницаНачала.Дата);
Запрос.УстановитьПараметр("СсылкаНачала", ГраницаНачала.Ссылка);

Запрос.УстановитьПараметр("ДатаОкончания", ГраницаОкончания.Дата);
Запрос.УстановитьПараметр("СсылкаОкончания", ГраницаОкончания.Ссылка);

Такой подход обладает сразу несколькими неоспоримыми преимуществами:

Альтернативный вариант: Использование виртуальных таблиц

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


ВЫБРАТЬ
    ПродажиОбороты.Номенклатура,
    ПродажиОбороты.КоличествоОборот
ИЗ
    РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания, , ) КАК ПродажиОбороты

В данном случае в параметры &ДатаНачала и &ДатаОкончания вы можете смело передавать объекты типа Граница:


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

Платформа самостоятельно сгенерирует оптимальный и корректный SQL-код, который правильно рассчитает итоги регистра с точностью до конкретного документа включительно.

Выводы и рекомендации

Подведем итог и сформулируем правила работы с временными точками в запросах 1С:

  1. Избегайте прямого сравнения виртуального поля МоментВремени в секции ГДЕ физических таблиц документов. Сравнение <= часто работает некорректно и «теряет» документы на границе периода.
  2. Никогда не передавайте объект Граница в параметры условия ГДЕ — это приведет к ошибке несовместимости типов. Границы предназначены только для параметров виртуальных таблиц.
  3. Для надежного и быстрого отбора документов внутри одной секунды раскладывайте условие на явное сравнение даты и ссылки (как показано в примере выше). Это гарантирует точный результат и высокую скорость работы СУБД.
← На главную