Почему запрос с временными таблицами и ВНУТРЕННИМ СОЕДИНЕНИЕМ возвращает пустую таблицу значений?

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

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

Проанализируем сценарий: есть данные в регистре накопления и есть данные во второй таблице значений, полученной из другого источника. Наша цель — соединить их по номенклатуре и получить сводную таблицу. Но в результате выполнения кода итоговая ТЗ остается пустой.

Анализ исходного кода и выявление первой ошибки

Давайте посмотрим на первоначальный фрагмент кода, который представил автор проблемы. В нем есть синтаксическая ошибка, которую важно исправить в первую очередь.


// Неправильный вариант
ТЗ = Новый ТаблицаЗначений;
ТЗ.Текст = "ВЫБРАТЬ ..."; // Ошибка: у ТаблицыЗначений нет свойства "Текст"
Запрос.УстановитьПараметр(...);
ТЗ = Запрос.Выполнить().Выгрузить();

Здесь текст запроса присваивается свойству Текст объекта ТаблицаЗначений, что некорректно. Текст запроса должен быть присвоен объекту Запрос. Исправим это:


// Правильный вариант
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ ..."; // Текст присваивается объекту Запрос

// ... установка параметров ...

РезультатЗапроса = Запрос.Выполнить();
ТЗ = РезультатЗапроса.Выгрузить();

Даже после исправления этой очевидной ошибки проблема может сохраниться, и таблица значений по-прежнему будет пустой. Это означает, что корень проблемы лежит глубже — в самой логике запроса.

Основная причина пустого результата: природа ВНУТРЕННЕГО СОЕДИНЕНИЯ

Рассмотрим текст запроса, который используется для получения итоговых данных. Он состоит из трех частей (пакетов):

  1. Создание временной таблицы ВТ_Таб1 из остатков регистра накопления.
  2. Создание временной таблицы ВТ_Таб2 из таблицы значений, переданной как параметр.
  3. Соединение этих двух временных таблиц с помощью ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN).

Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|    Таб1.НоменклатураТаб1 КАК Номенклатура,
|    Таб1.Склад КАК Склад,
|    Таб1.КоличествоТаб1 КАК КоличествоТаб1
|ПОМЕСТИТЬ ВТ_Таб1
|ИЗ
|    РегистрНакопления.Таб1.Остатки(&НачалоПериода) КАК Таб1
|;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|    Таб2.НоменклатураТаб2 КАК Номенклатура,
|    Таб2.КоличествоТаб2 КАК КоличествоТаб2
|ПОМЕСТИТЬ ВТ_Таб2
|ИЗ
|    &Таб2 КАК Таб2
|;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|    Таб11.Номенклатура КАК Номенклатура,
|    Таб11.Склад КАК Склад,
|    Таб11.КоличествоТаб1 КАК КоличествоТаб1,
|    Таб22.КоличествоТаб2 КАК КоличествоТаб2
|ИЗ
|    ВТ_Таб1 КАК Таб11
|        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Таб2 КАК Таб22
|        ПО Таб11.Номенклатура = Таб22.Номенклатура";

Запрос.УстановитьПараметр("НачалоПериода", Дата);
Запрос.УстановитьПараметр("Таб2", Таблица); // Таблица - это ТЗ, полученная извне

ТЗ_Итог = Запрос.Выполнить().Выгрузить();

Ключевая конструкция здесь — ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Принцип ее работы заключается в том, что в итоговый результат попадают только те строки, для которых нашлось соответствие по условию связи (ПО Таб11.Номенклатура = Таб22.Номенклатура) в обеих таблицах.

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

Как и выяснилось в ходе обсуждения на форуме, именно это и было причиной проблемы. Таблица значений, получаемая из другой базы и передаваемая в параметр &Таб2, была пустой. Прежде чем выполнять сложный запрос, всегда проверяйте исходные данные!


// Перед выполнением запроса добавьте проверку
Таблица = ПолучитьТаблицуЗначений(); // Функция, получающая ТЗ

Если Таблица.Количество() = 0 Тогда
    Сообщить("Внимание! Таблица, полученная из внешней базы, пуста. Запрос не вернет данных.");
    Возврат; // Прерываем дальнейшее выполнение
КонецЕсли;

// ... дальнейший код с выполнением запроса ...

Такая простая проверка может сэкономить много времени на отладку. Для анализа содержимого временных таблиц также очень удобно использовать консоль запросов.

Как быть, если данные могут отсутствовать в одной из таблиц?

Что делать, если по логике задачи нам нужно получить все данные из одной таблицы и дополнить их данными из второй, если они там есть? В этом случае ВНУТРЕННЕЕ СОЕДИНЕНИЕ не подходит. Рассмотрим правильные альтернативы.

Решение 1: Использование ЛЕВОГО СОЕДИНЕНИЯ

Если вам нужно получить все записи из "левой" таблицы (той, что указана первой, в нашем случае ВТ_Таб1) и присоединить к ним совпадающие по ключу данные из "правой" таблицы (ВТ_Таб2), используйте ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN).

В этом случае, если для строки из ВТ_Таб1 не найдется соответствия в ВТ_Таб2, строка все равно попадет в результат, а поля из ВТ_Таб2 (КоличествоТаб2) будут заполнены значением NULL. Это значение можно обработать функцией ЕСТЬNULL().


|ВЫБРАТЬ
|    Таб11.Номенклатура КАК Номенклатура,
|    Таб11.Склад КАК Склад,
|    Таб11.КоличествоТаб1 КАК КоличествоТаб1,
|    ЕСТЬNULL(Таб22.КоличествоТаб2, 0) КАК КоличествоТаб2
|ИЗ
|    ВТ_Таб1 КАК Таб11
|        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Таб2 КАК Таб22
|        ПО Таб11.Номенклатура = Таб22.Номенклатура

Аналогично работает ПРАВОЕ СОЕДИНЕНИЕ (возвращает все из правой таблицы) и ПОЛНОЕ СОЕДИНЕНИЕ (возвращает все записи из обеих таблиц, соединяя, где возможно).

Решение 2: Использование ОБЪЕДИНЕНИЯ вместо соединения

В некоторых случаях, особенно когда нужно собрать данные из разных источников и сгруппировать, более надежным подходом является объединение данных через ОБЪЕДИНИТЬ ВСЕ с последующей группировкой.

Этот метод позволяет избежать потери данных, если в одной из таблиц нет совпадений. Давайте разберем его по шагам:

  1. Все данные из первой таблицы помещаются во временную таблицу, при этом для полей из второй таблицы добавляются "заглушки" (например, 0).
  2. Все данные из второй таблицы помещаются в ту же временную таблицу, но "заглушки" ставятся уже для полей из первой таблицы.
  3. Итоговая временная таблица группируется по ключевым полям (например, по номенклатуре) для получения суммарных значений.

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


"ВЫБРАТЬ
|    Таб1.Номенклатура,
|    Таб1.Количество КАК КоличествоТаб1,
|    0 КАК КоличествоТаб2
|ПОМЕСТИТЬ ВТ_Сводная
|ИЗ
|    &Таблица1 КАК Таб1
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
|    Таб2.Номенклатура,
|    0,
|    Таб2.Количество
|ИЗ
|    &Таблица2 КАК Таб2
|;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|    ВТ_Сводная.Номенклатура,
|    СУММА(ВТ_Сводная.КоличествоТаб1) КАК КоличествоТаб1,
|    СУММА(ВТ_Сводная.КоличествоТаб2) КАК КоличествоТаб2
|ИЗ
|    ВТ_Сводная
|СГРУППИРОВАТЬ ПО
|    ВТ_Сводная.Номенклатура"

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

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

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

  1. Проверяйте исходные данные. Перед выполнением сложного запроса убедитесь, что все таблицы и параметры, которые вы в него передаете, содержат данные. Простая проверка Таблица.Количество() > 0 может избавить от долгих поисков ошибки.
  2. Правильно выбирайте тип соединения.
    • ВНУТРЕННЕЕ СОЕДИНЕНИЕ — используйте, когда вам нужны только пересекающиеся данные.
    • ЛЕВОЕ (ПРАВОЕ) СОЕДИНЕНИЕ — когда нужно получить все данные из одной таблицы и дополнить их данными из другой.
    • ПОЛНОЕ СОЕДИНЕНИЕ — когда нужны все данные из обеих таблиц.
  3. Используйте отладочные инструменты. Консоль запросов — незаменимый помощник для пошагового выполнения пакетов и анализа содержимого временных таблиц на каждом этапе.
  4. Рассматривайте альтернативы. В некоторых задачах подход с ОБЪЕДИНИТЬ ВСЕ и последующей группировкой может быть проще и надежнее, чем сложные соединения.

Следуя этим простым рекомендациям, вы сможете избежать распространенных ошибок и писать более надежный и предсказуемый код для обработки данных в 1С.

← На главную