Как в запросе 1С выбрать данные за период между минимальной и максимальной датами из другого пакета?

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

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

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

Выясняем причину: почему не работает простая конструкция с подзапросами?

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


ГДЕ
    Док.Дата МЕЖДУ 
        (ВЫБРАТЬ МИНИМУМ(П1.Период) ИЗ ВТ_Пакет1 КАК П1) 
        И 
        (ВЫБРАТЬ МАКСИМУМ(П1.Период) ИЗ ВТ_Пакет1 КАК П1)

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

Парсер языка запросов 1С в этих конструкциях ожидает увидеть только:

  1. Конкретное значение (литерал, например, пустую дату '00010101').
  2. Параметр запроса (начинающийся с символа &).

Использование вложенных запросов в 1С разрешено только внутри оператора проверки на вхождение В (...), а также в секции ИЗ в качестве источника данных. Во всех остальных местах использование подзапросов вызовет ошибку синтаксиса. Давайте разберем по шагам, как красиво и эффективно обойти это ограничение без выгрузки данных во встроенный язык.

Решение 1: Внутреннее соединение с временной таблицей границ (Оптимальный способ)

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

Рассмотрим этот алгоритм детально. Сначала мы берем исходный пакет данных (назовем его ВТ_ИсходныеДанные) и получаем из него минимальную и максимальную даты:


// ПАКЕТ 1: Вычисляем границы периода
ВЫБРАТЬ
    МИНИМУМ(Исходные.Период) КАК МинДата,
    МАКСИМУМ(Исходные.Период) КАК МаксДата
ПОМЕСТИТЬ ВТ_ГраницыПериода
ИЗ
    ВТ_ИсходныеДанные КАК Исходные
;

// ПАКЕТ 2: Основной запрос с фильтрацией по периоду
ВЫБРАТЬ
    ДокументыПродаж.Ссылка КАК ДокументСсылка,
    ДокументыПродаж.Дата КАК ДатаДокумента
ИЗ
    Документ.РеализацияТоваровУслуг КАК ДокументыПродаж
    
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ГраницыПериода КАК Границы
    ПО ДокументыПродаж.Дата МЕЖДУ Границы.MинДата И Границы.МаксДата

Обратите внимание на то, как СУБД выполняет этот запрос. Поскольку временная таблица ВТ_ГраницыПериода не имеет группировок по разрезам, она гарантированно содержит **ровно одну строку** (или будет пустой, если исходных данных не было). При выполнении ВНУТРЕННЕГО СОЕДИНЕНИЯ СУБД легко сопоставит каждую строку таблицы документов с этой единственной строкой границ, не удваивая количество записей и эффективно используя индексы по полю Дата.

Решение 2: Декартово произведение (Соединение через запятую)

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


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

Этот способ абсолютно эквивалентен первому по производительности. Так как в таблице ВТ_ГраницыПериода находится только одна запись, оптимизатор СУБД автоматически преобразует такое перечисление таблиц в классическое соединение INNER JOIN. Вы можете использовать любой из этих двух вариантов в зависимости от стандартов разработки, принятых в вашей компании.

Решение 3: Использование двух точечных временных таблиц (Мин и Макс отдельно)

Рассмотрим альтернативный вариант, который упоминался разработчиками на форумах. Он заключается в создании двух раздельных временных таблиц: одна будет хранить только минимальную дату, а вторая — только максимальную. Для получения этих дат используется конструкция ВЫБРАТЬ ПЕРВЫЕ 1 с соответствующей сортировкой.


// Получаем минимальную дату
ВЫБРАТЬ ПЕРВЫЕ 1
    Исходные.Период КАК МинПериод
ПОМЕСТИТЬ ВТ_Минимум
ИЗ
    ВТ_ИсходныеДанные КАК Исходные
УПОРЯДОЧИТЬ ПО
    Исходные.Период АСЦ
;

// Получаем максимальную дату
ВЫБРАТЬ ПЕРВЫЕ 1
    Исходные.Период КАК МаксПериод
ПОМЕСТИТЬ ВТ_Максимум
ИЗ
    ВТ_ИсходныеДанные КАК Исходные
УПОРЯДОЧИТЬ ПО
    Исходные.Период УБЫВ
;

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

Хотя данный метод является полностью рабочим, он считается более громоздким. Нам приходится создавать три пакета запроса вместо двух и дважды сортировать исходную таблицу данных. Сортировка больших объемов данных во временных таблицах — операция ресурсоемкая, поэтому первый способ с использованием функций МИНИМУМ и МАКСИМУМ является более предпочтительным.

А что делать, если нужно передать период в параметры виртуальной таблицы?

Это самый важный архитектурный нюанс. Что делать, если нам нужно ограничить выборку не из физической таблицы документов, а из виртуальной таблицы регистра (например, получить обороты или остатки из РегистрНакопления.Продажи.Обороты)?

Критическое ограничение платформы: передать динамически вычисленные поля из временной таблицы напрямую в параметры виртуальной таблицы (такие как НачалоПериода или КонецПериода) внутри одного запроса **невозможно**. Эти параметры рассчитываются СУБД строго на этапе подготовки плана запроса, и они принимают только константы или готовые значения параметров (переданные через УстановитьПараметр()).

Давайте проанализируем три возможных пути решения этой задачи.

Путь А: Программное разделение запроса (Рекомендуемый подход для кода)

Если запрос пишется в общем или менеджерском модуле, лучшим решением с точки зрения производительности и стабильности будет разделение логики на два простых шага:

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

ЗапросГраниц = Новый Запрос;
ЗапросГраниц.Текст = 
    "ВЫБРАТЬ
    |	МИНИМУМ(Исходные.Период) КАК МинДата,
    |	МАКСИМУМ(Исходные.Период) КАК МаксДата
    |ИЗ
    |	РегистрСведений.НекиеДанные КАК Исходные";

Выборка = ЗапросГраниц.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
    МинДата = Выборка.МинДата;
    МаксДата = Выборка.МаксДата;
КонецЕсли;
  1. Передать полученные даты в качестве параметров во второй запрос, где вызывается виртуальная таблица:

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

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

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

Путь Б: Использование Системы компоновки данных (СКД)

Если задача решается в отчете или динамическом списке на СКД, платформа предоставляет встроенный механизм связей наборов данных:

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

Путь В: Наложение фильтра в секции ГДЕ или ПОСЛЕ соединения (Не рекомендуется!)

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


// КРАЙНЕ НЕОПТИМАЛЬНЫЙ ВАРИАНТ! НЕ ИСПОЛЬЗОВАТЬ НА БОЛЬШИХ БАЗАХ!
ВЫБРАТЬ
    Продажи.Номенклатура,
    Продажи.КоличествоОборот
ИЗ
    РегистрНакопления.Продажи.Обороты КАК Продажи
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ГраницыПериода КАК Границы
    ПО Продажи.Период МЕЖДУ Границы.МинДата И Границы.МаксДата

Почему так делать категорически нельзя? Если в вашем регистре накопления хранятся миллионы записей за несколько лет, то при обращении к виртуальной таблице без параметров периода платформа 1С попытается рассчитать итоги и обороты за всю историю существования базы. СУБД потратит огромное количество оперативной памяти и времени, что приведет к зависанию системы или ошибке превышения времени ожидания (таймауту). Всегда ограничивайте виртуальные таблицы параметрами на этапе их вызова!

Резюме

Для физических таблиц (документы, справочники, реальные таблицы регистров) идеальным решением является вычисление минимальной и максимальной дат в отдельную временную таблицу ВТ_ГраницыПериода из одной строки с последующим использованием ВНУТРЕННЕГО СОЕДИНЕНИЯ по условию МЕЖДУ. Для виртуальных таблиц наиболее надежным решением является разделение логики на два последовательных запроса во встроенном языке 1С с передачей вычисленных дат через параметры.

← На главную