Почему запрос с несколькими вложенными таблицами возвращает пустой результат в 1С и как это исправить?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

Причина проблемы: декартово произведение множеств

Проанализируем исходный вариант запроса, с которым возникла сложность:


ВЫБРАТЬ
    ВложенныйЗапрос.ДатаПроверки КАК ДатаПроверки,
    ВложенныйЗапрос1.ДатаПечати КАК ДатаПечати
ИЗ
    (ВЫБРАТЬ ПЕРВЫЕ 1
        ЗаказПокупателяВремяПечати.ДатаПечати КАК ДатаПроверки
    ИЗ
        Документ.ЗаказПокупателя.ВремяПечати КАК ЗаказПокупателяВремяПечати
    ГДЕ
        ЗаказПокупателяВремяПечати.Ссылка = &Док
        И ЗаказПокупателяВремяПечати.ПечатнаяФорма = "Проверен"
    УПОРЯДОЧИТЬ ПО
        ДатаПроверки) КАК ВложенныйЗапрос,
    (ВЫБРАТЬ ПЕРВЫЕ 1
        ЗаказПокупателяВремяПечати.ДатаПечати КАК ДатаПечати
    ИЗ
        Документ.ЗаказПокупателя.ВремяПечати КАК ЗаказПокупателяВремяПечати
    ГДЕ
        ЗаказПокупателяВремяПечати.Ссылка = &Док
        И ЗаказПокупателяВремяПечати.ПечатнаяФорма = "На склад"
    УПОРЯДОЧИТЬ ПО
        ДатаПечати) КАК ВложенныйЗапрос1

Когда в секции ИЗ указываются две таблицы через запятую без явного указания условия связи, СУБД выполняет декартово произведение (CROSS JOIN). Результирующее количество строк равно произведению количества строк первой таблицы на количество строк второй таблицы.

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

Рассмотрим по шагам несколько способов решения этой задачи.

Способ 1. Оптимальное решение: условная агрегация через ВЫБОР и МИНИМУМ

Наиболее быстрый и канонический способ развернуть значения из разных строк табличной части в отдельные колонки одной строки (pivot) — использование агрегатных функций с конструкцией ВЫБОР КОГДА.

Посмотрим на пример запроса:


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

Разберем преимущества этого подхода:

  1. Один проход по данным: СУБД считывает табличную часть ровно один раз без дополнительных подзапросов, временных таблиц и операций соединения.
  2. Корректная обработка отсутствующих данных: если строка с печатной формой "Проверен" отсутствует, ветка ВЫБОР не сработает и вернет NULL. Агрегатная функция МИНИМУМ проигнорирует NULL, а итоговая колонка корректно примет значение NULL, не затирая данные второй колонки.
  3. Гарантия одной строки в результате: применение агрегатных функций без секции СГРУППИРОВАТЬ ПО всегда возвращает строго одну строку, даже если в табличной части документа вообще нет подходящих записей.

Способ 2. Использование ПОЛНОГО СОЕДИНЕНИЯ подзапросов

Если требуется сохранить логику с подзапросами и выборкой первых записей, таблицы необходимо связать с помощью ПОЛНОЕ СОЕДИНЕНИЕ по условию ИСТИНА. Это гарантирует, что при отсутствии записей в одной из таблиц данные из другой не пропадут.

Посмотрим на реализацию:


ВЫБРАТЬ
    ВложенныйЗапрос.ДатаПечати КАК ДатаПечати,
    ВложенныйЗапрос1.ДатаПроверки КАК ДатаПроверки
ИЗ
    (ВЫБРАТЬ ПЕРВЫЕ 1
        ЗаказПокупателяВремяПечати.ДатаПечати КАК ДатаПечати
    ИЗ
        Документ.ЗаказПокупателя.ВремяПечати КАК ЗаказПокупателяВремяПечати
    ГДЕ
        ЗаказПокупателяВремяПечати.Ссылка = &Док
        И ЗаказПокупателяВремяПечати.ПечатнаяФорма = "На склад"
    УПОРЯДОЧИТЬ ПО
        ЗаказПокупателяВремяПечати.ДатаПечати) КАК ВложенныйЗапрос
    ПОЛНОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ ПЕРВЫЕ 1
        ЗаказПокупателяВремяПечати.ДатаПечати КАК ДатаПроверки
    ИЗ
        Документ.ЗаказПокупателя.ВремяПечати КАК ЗаказПокупателяВремяПечати
    ГДЕ
        ЗаказПокупателяВремяПечати.Ссылка = &Док
        И ЗаказПокупателяВремяПечати.ПечатнаяФорма = "Проверен"
    УПОРЯДОЧИТЬ ПО
        ЗаказПокупателяВремяПечати.ДатаПечати) КАК ВложенныйЗапрос1
    ПО ИСТИНА

Обратите внимание: конструкция ПОЛНОЕ СОЕДИНЕНИЕ ... ПО ИСТИНА заставляет СУБД выполнять сопоставление подзапросов без индексного ключа, поэтому при работе с большими объемами данных такой вариант уступает условной агрегации по скорости выполнения.

Способ 3. ОБЪЕДИНИТЬ ВСЕ с последующей группировкой

Еще один рабочий вариант — объединить выборки через ОБЪЕДИНИТЬ ВСЕ, подставив в недостающие колонки NULL, а затем свернуть полученный результат во внешнем запросе с помощью функции МАКСИМУМ.

Рассмотрим структуру такого запроса:


ВЫБРАТЬ
    МАКСИМУМ(Объединение.ДатаПечати) КАК ДатаПечати,
    МАКСИМУМ(Объединение.ДатаПроверки) КАК ДатаПроверки
ИЗ
    (ВЫБРАТЬ ПЕРВЫЕ 1
        ТЧ.ДатаПечати КАК ДатаПечати,
        NULL КАК ДатаПроверки
    ИЗ
        Документ.ЗаказПокупателя.ВремяПечати КАК ТЧ
    ГДЕ
        ТЧ.Ссылка = &Док
        И ТЧ.ПечатнаяФорма = "На склад"
    УПОРЯДОЧИТЬ ПО
        ТЧ.ДатаПечати

    ОБЪЕДИНИТЬ ВСЕ

    ВЫБРАТЬ ПЕРВЫЕ 1
        NULL,
        ТЧ.ДатаПечати
    ИЗ
        Документ.ЗаказПокупателя.ВремяПечати КАК ТЧ
    ГДЕ
        ТЧ.Ссылка = &Док
        И ТЧ.ПечатнаяФорма = "Проверен"
    УПОРЯДОЧИТЬ ПО
        ТЧ.ДатаПечати) КАК Объединение

Альтернатива: обработка данных в памяти без запроса

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


ПараметрыОтбора = Новый Структура("ПечатнаяФорма", "Проверен");
НайденныеСтроки = Объект.ВремяПечати.НайтиСтроки(ПараметрыОтбора);

Если НайденныеСтроки.Количество() > 0 Тогда
    ДатаПроверки = НайденныеСтроки[0].ДатаПечати;
Иначе
    ДатаПроверки = Неопределено;
КонецЕсли;

Итог: для получения нескольких минимальных или максимальных реквизитов из одной таблицы всегда отдавайте предпочтение запросам с МИНИМУМ(ВЫБОР КОГДА ...). Это исключает декартово умножение, гарантирует получение одной строки и обеспечивает максимальную производительность СУБД.

← На главную