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

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

При работе с запросами в 1С разработчики часто сталкиваются с ситуацией, когда ПОЛНОЕ СОЕДИНЕНИЕ (FULL JOIN) ведет себя не так, как ожидается. Типичный симптом: если в одной из соединяемых таблиц (например, в остатках) данных нет, а в другой (в оборотах) они есть, строка в итоговом результате пропадает или отображается некорректно. Рассмотрим подробнее, почему это происходит и как правильно проектировать такие запросы.

Разбор основной проблемы: коварство NULL

Проанализируем ситуацию, описанную в теме. Автор пытается соединить виртуальную таблицу Остатки с таблицей Обороты. Проблема заключается в том, что при отсутствии записи в таблице остатков, все поля этой таблицы в результате соединения принимают значение NULL. Если в блоке ВЫБРАТЬ мы обращаемся напрямую к полю ТоварыОрганизацийОстатки.Номенклатура, то для товаров, по которым есть продажи, но нет остатка, мы получим пустоту (NULL) в колонке номенклатуры.

Более того, если мы используем это же поле в секции СГРУППИРОВАТЬ ПО, система будет пытаться сгруппировать данные по значению NULL, что окончательно ломает логику отчета. Выясним причину: для корректного отображения данных нам нужно «склеить» ключевые поля (измерения) из обеих таблиц в одно общее поле.

Решение №1: Использование функции ЕСТЬNULL в полях выбора

Самый простой и действенный способ — использовать функцию ЕСТЬNULL для всех полей, по которым происходит соединение. Это позволит системе взять значение из второй таблицы, если в первой оно отсутствует. Рассмотрим, как должен выглядеть исправленный фрагмент кода:


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

Обратите внимание, что в секции СГРУППИРОВАТЬ ПО также необходимо использовать те же самые конструкции с ЕСТЬNULL или созданные псевдонимы, если СУБД это позволяет.

Решение №2: Проблема «точек» и неявных соединений

Проанализируем важное замечание из обсуждения: использование обращений через точку, таких как АналитикаУчетаНоменклатуры.Номенклатура, порождает неявные левые соединения с соответствующими справочниками. Если поле АналитикаУчетаНоменклатуры само по себе является NULL (из-за полного соединения), то любая попытка «пройти по точке» дальше может привести к непредсказуемым результатам в итоговом SQL-запросе.

Совет: старайтесь сначала выбирать ссылочные поля (саму аналитику), а уже потом, во внешнем запросе или через дополнительные соединения, получать строковые представления или реквизиты этих ссылок. Это сделает структуру запроса прозрачной и позволит избежать лишних вложенных JOIN в итоговом коде.

Решение №3: Параметры и границы периодов

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

Для правильного сопоставления рекомендуется использовать объект Граница:

  1. Для остатков на конец периода используйте: Новый Граница(КонецДня(ВыбДата), ВидГраницы.Включая).
  2. В запросе это позволит сопоставить «конечный остаток» с оборотами, завершившимися ровно в этот же момент времени.

Решение №4: Альтернатива через временные таблицы и ОБЪЕДИНИТЬ ВСЕ

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

1. Сначала выбираем остатки во временную таблицу ВТ_Данные:


ВЫБРАТЬ Аналитика, КоличествоОстаток КАК Остаток, 0 КАК Приход, 0 КАК Расход 
ПОМЕСТИТЬ ВТ_Данные ИЗ РегистрНакопления.ТоварыОрганизаций.Остатки(...)

2. Затем добавляем туда же обороты через ОБЪЕДИНИТЬ ВСЕ:


ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ Аналитика, 0, КоличествоПриход, КоличествоРасход 
ИЗ РегистрНакопления.ТоварыОрганизаций.Обороты(...)

3. В финальном запросе просто группируем данные из ВТ_Данные по полю Аналитика, суммируя ресурсы:


ВЫБРАТЬ Аналитика, СУММА(Остаток), СУММА(Приход), СУММА(Расход) 
ИЗ ВТ_Данные СГРУППИРОВАТЬ ПО Аналитика

Этот метод исключает проблему NULL, так как мы не соединяем таблицы горизонтально, а «складываем» их вертикально. В результате у нас всегда есть заполненное поле аналитики, по которому легко выполнить группировку.

Итоги

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

← На главную