При разработке сложных отчетов или обработок на платформе 1С:Предприятие 8 программисты часто сталкиваются с ситуацией, когда запрос, который должен возвращать данные, выдает абсолютно пустую таблицу. Особенно часто это случается при использовании Левого соединения между временной таблицей (созданной на основе ТаблицыЗначений) и виртуальной таблицей регистра накопления. Давайте вместе разберем причины этой проблемы и найдем правильные способы её решения.
Рассмотрим типичную ситуацию: нам нужно вывести обороты по взаиморасчетам за определенные месяцы. Для этого мы сначала создаем таблицу периодов, а затем пытаемся "присоединить" к ней данные из регистра.
// Пример исходного кода автора
Запрос ТЗ = Новый ТаблицаЗначений;
Тз.Колонки.Добавить("Месяц", Новый ОписаниеТипов("Дата", Новый КвалификаторыДаты(ЧастиДаты.Дата)));
СтрокаТЗ = ТЗ.Добавить;
СтрокаТЗ.Месяц = НачалоМесяца(Дата1);
// Заполнение ТЗ в цикле
Для а = 1 по 3 цикл
ДатаХ = НачалоМесяца(ДобавитьМесяц(Дата1, -а));
СтрокаТЗ = ТЗ.Добавить;
СтрокаТЗ.Месяц = НачалоМесяца(ДатаХ);
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| ТаблицаПериодов.Месяц
|ПОМЕСТИТЬ ТаблицаПериодов
|ИЗ &ТЗ КАК ТаблицаПериодов;
|
|ВЫБРАТЬ
| ТаблицаПериодов.Месяц,
| ВзаиморасчетыСКонтрагентамиОбороты.СуммаВзаиморасчетовОборот
|ИЗ ТаблицаПериодов КАК ТаблицаПериодов
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВзаиморасчетыСКонтрагентами.Обороты(&Дата1, &Дата2, Месяц, ) КАК ВзаиморасчетыСКонтрагентамиОбороты
|ПО ТаблицаПериодов.Месяц = ВзаиморасчетыСКонтрагентамиОбороты.Период
|ГДЕ ВзаиморасчетыСКонтрагентамиОбороты.Организация = &Организация";
Проанализируем работу ЛЕВОГО СОЕДИНЕНИЯ. Его смысл заключается в том, что в итоговую выборку попадут все записи из левой таблицы (ТаблицаПериодов), даже если для них не нашлось соответствия в правой таблице (виртуальная таблица оборотов). В тех строках, где соответствия нет, значения полей из правой таблицы будут равны NULL.
Однако, как только мы добавляем условие в секцию ГДЕ, например: ГДЕ ВзаиморасчетыСКонтрагентамиОбороты.Организация = &Организация, логика меняется. Когда система встречает строку из ТаблицаПериодов, для которой нет оборотов, поле Организация принимает значение NULL. Сравнение NULL = &Организация всегда возвращает Ложь. В результате такая строка отсекается, и мы получаем поведение, идентичное Внутреннему соединению. Если данных нет ни по одному месяцу для данной организации, вся таблица будет пустой.
Как исправить: Все условия по полям присоединяемой виртуальной таблицы нужно переносить либо в Параметры виртуальной таблицы, либо непосредственно в условие соединения ПО.
// Правильный подход: фильтрация внутри виртуальной таблицы
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВзаиморасчетыСКонтрагентами.Обороты(
| &Дата1,
| &Дата2,
| Месяц,
| Организация = &Организация И Контрагент.Покупатель = ИСТИНА
|) КАК ВзаиморасчетыСКонтрагентамиОбороты
Выясним причину, по которой условие ПО ТаблицаПериодов.Месяц = ВзаиморасчетыСКонтрагентамиОбороты.Период может не срабатывать. В 1С тип данных Дата всегда содержит время. Если в вашей ТаблицеЗначений дата записана как 01.01.2023 12:00:00, а виртуальная таблица с периодичностью Месяц выдает 01.01.2023 00:00:00, то с точки зрения СУБД эти значения не равны.
Разберем важный нюанс: при создании колонок в ТаблицеЗначений мы использовали КвалификаторыДаты(ЧастиДаты.Дата). Это обрезает время только визуально в некоторых интерфейсах, но в памяти объекта или при передаче в SQL время может сохраняться. Посмотрим на безопасный способ подготовки данных:
НачалоДня() или НачалоМесяца() при заполнении ТаблицыЗначений в коде.НАЧАЛОПЕРИОДА() для обоих полей соединения.
|ПО НАЧАЛОПЕРИОДА(ТаблицаПериодов.Месяц, ДЕНЬ) = НАЧАЛОПЕРИОДА(ВзаиморасчетыСКонтрагентамиОбороты.Период, ДЕНЬ)
Проанализируем ситуацию, когда ТаблицаЗначений передается в запрос как параметр. Для того чтобы 1С корректно построила временную таблицу в базе данных SQL, колонки ТЗ должны быть строго типизированы. Если колонка имеет составной тип или тип не определен, запрос может отработать непредсказуемо.
Посмотрим, как правильно объявить колонку:
ОписаниеТипаДата = Новый ОписаниеТипов("Дата");
ТЗ.Колонки.Добавить("Месяц", ОписаниеТипаДата);
Если мы добились того, что ЛЕВОЕ СОЕДИНЕНИЕ заработало, мы увидим в результате NULL там, где оборотов не было. Для пользователя это выглядит как пустота. Кроме того, с NULL нельзя проводить арифметические операции. Рассмотрим, как превратить пустоту в ноль:
ВЫБРАТЬ
ТаблицаПериодов.Месяц,
ЕСТЬNULL(ВзаиморасчетыСКонтрагентамиОбороты.СуммаВзаиморасчетовОборот, 0) КАК Сумма
Функция ЕСТЬNULL проверяет первый аргумент, и если он равен NULL, возвращает второй (в нашем случае 0).
Если результат всё равно пустой, проанализируем данные по шагам. Опытные разработчики рекомендуют использовать Консоль запросов:
Контрагент.Покупатель = ИСТИНА может не срабатывать, если в справочнике у контрагентов этот флаг не проставлен, хотя взаиморасчеты ведутся.Хотя это напрямую не влияет на "пустоту" результата, это критически важно для производительности. Если в вашей ТаблицеПериодов будет много записей, соединение будет медленным. Проанализируем, как добавить индекс:
|ВЫБРАТЬ
| ТаблицаПериодов.Месяц
|ПОМЕСТИТЬ ТаблицаПериодов
|ИЗ &ТЗ КАК ТаблицаПериодов
|ИНДЕКСИРОВАТЬ ПО Месяц;
Для того чтобы ваш запрос к виртуальным таблицам всегда возвращал верные данные, придерживайтесь следующих правил:
ГДЕ.00:00:00 первого числа месяца).&Дата2 в виртуальной таблице оборотов должен включать последнее мгновение месяца (используйте КонецМесяца()).Соблюдение этих структурных правил позволит вам избежать большинства "мистических" ошибок при работе с запросами в 1С и обеспечит высокую скорость работы вашей конфигурации.