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

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

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

Рассмотрим типичную ситуацию: нам нужно вывести обороты по взаиморасчетам за определенные месяцы. Для этого мы сначала создаем таблицу периодов, а затем пытаемся "присоединить" к ней данные из регистра.


// Пример исходного кода автора
Запрос ТЗ = Новый ТаблицаЗначений; 
Тз.Колонки.Добавить("Месяц", Новый ОписаниеТипов("Дата", Новый КвалификаторыДаты(ЧастиДаты.Дата))); 

СтрокаТЗ = ТЗ.Добавить; 
СтрокаТЗ.Месяц = НачалоМесяца(Дата1); 

// Заполнение ТЗ в цикле
Для а = 1 по 3 цикл 
    ДатаХ = НачалоМесяца(ДобавитьМесяц(Дата1, -а)); 
    СтрокаТЗ = ТЗ.Добавить; 
    СтрокаТЗ.Месяц = НачалоМесяца(ДатаХ); 
КонецЦикла; 

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

Ошибка №1: Секция ГДЕ превращает Левое соединение во Внутреннее

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

Однако, как только мы добавляем условие в секцию ГДЕ, например: ГДЕ ВзаиморасчетыСКонтрагентамиОбороты.Организация = &Организация, логика меняется. Когда система встречает строку из ТаблицаПериодов, для которой нет оборотов, поле Организация принимает значение NULL. Сравнение NULL = &Организация всегда возвращает Ложь. В результате такая строка отсекается, и мы получаем поведение, идентичное Внутреннему соединению. Если данных нет ни по одному месяцу для данной организации, вся таблица будет пустой.

Как исправить: Все условия по полям присоединяемой виртуальной таблицы нужно переносить либо в Параметры виртуальной таблицы, либо непосредственно в условие соединения ПО.


// Правильный подход: фильтрация внутри виртуальной таблицы
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВзаиморасчетыСКонтрагентами.Обороты(
|    &Дата1, 
|    &Дата2, 
|    Месяц, 
|    Организация = &Организация И Контрагент.Покупатель = ИСТИНА
|) КАК ВзаиморасчетыСКонтрагентамиОбороты

Ошибка №2: Несоответствие типов и точности дат

Выясним причину, по которой условие ПО ТаблицаПериодов.Месяц = ВзаиморасчетыСКонтрагентамиОбороты.Период может не срабатывать. В 1С тип данных Дата всегда содержит время. Если в вашей ТаблицеЗначений дата записана как 01.01.2023 12:00:00, а виртуальная таблица с периодичностью Месяц выдает 01.01.2023 00:00:00, то с точки зрения СУБД эти значения не равны.

Разберем важный нюанс: при создании колонок в ТаблицеЗначений мы использовали КвалификаторыДаты(ЧастиДаты.Дата). Это обрезает время только визуально в некоторых интерфейсах, но в памяти объекта или при передаче в SQL время может сохраняться. Посмотрим на безопасный способ подготовки данных:

  1. Всегда используйте функцию НачалоДня() или НачалоМесяца() при заполнении ТаблицыЗначений в коде.
  2. В самом тексте запроса для страховки можно использовать функцию НАЧАЛОПЕРИОДА() для обоих полей соединения.

|ПО НАЧАЛОПЕРИОДА(ТаблицаПериодов.Месяц, ДЕНЬ) = НАЧАЛОПЕРИОДА(ВзаиморасчетыСКонтрагентамиОбороты.Период, ДЕНЬ)

Ошибка №3: Типизация Таблицы Значений

Проанализируем ситуацию, когда ТаблицаЗначений передается в запрос как параметр. Для того чтобы 1С корректно построила временную таблицу в базе данных SQL, колонки ТЗ должны быть строго типизированы. Если колонка имеет составной тип или тип не определен, запрос может отработать непредсказуемо.

Посмотрим, как правильно объявить колонку:


ОписаниеТипаДата = Новый ОписаниеТипов("Дата");
ТЗ.Колонки.Добавить("Месяц", ОписаниеТипаДата);

Использование ЕСТЬNULL для корректного отображения

Если мы добились того, что ЛЕВОЕ СОЕДИНЕНИЕ заработало, мы увидим в результате NULL там, где оборотов не было. Для пользователя это выглядит как пустота. Кроме того, с NULL нельзя проводить арифметические операции. Рассмотрим, как превратить пустоту в ноль:


ВЫБРАТЬ
    ТаблицаПериодов.Месяц,
    ЕСТЬNULL(ВзаиморасчетыСКонтрагентамиОбороты.СуммаВзаиморасчетовОборот, 0) КАК Сумма

Функция ЕСТЬNULL проверяет первый аргумент, и если он равен NULL, возвращает второй (в нашем случае 0).

Эффективная отладка с помощью Консоли запросов

Если результат всё равно пустой, проанализируем данные по шагам. Опытные разработчики рекомендуют использовать Консоль запросов:

  1. Выполните первую часть запроса — выберите данные только из временной таблицы периодов. Убедитесь, что даты там именно те, которые вы ожидаете.
  2. Выполните запрос только к виртуальной таблице оборотов с вашими параметрами дат и фильтрами. Есть ли там вообще данные? Часто оказывается, что за выбранный период по данной организации просто нет движений.
  3. Проверьте значения реквизитов. Например, условие Контрагент.Покупатель = ИСТИНА может не срабатывать, если в справочнике у контрагентов этот флаг не проставлен, хотя взаиморасчеты ведутся.

Индексация временных таблиц

Хотя это напрямую не влияет на "пустоту" результата, это критически важно для производительности. Если в вашей ТаблицеПериодов будет много записей, соединение будет медленным. Проанализируем, как добавить индекс:


|ВЫБРАТЬ 
| ТаблицаПериодов.Месяц 
|ПОМЕСТИТЬ ТаблицаПериодов 
|ИЗ &ТЗ КАК ТаблицаПериодов
|ИНДЕКСИРОВАТЬ ПО Месяц;

Подводим итоги

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

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

← На главную