Почему не работает ЛЕВОЕ СОЕДИНЕНИЕ в запросе 1С и как правильно связывать таблицы

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

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

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


Исходный код с ошибками

Рассмотрим исходный программный код, который предоставил автор темы:


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

РезультатЗапроса = Запрос.Выполнить;
Выборка = РезультатЗапроса.Выбрать;
Пока Выборка.Следующий() Цикл
    НоваяСтрока = Реестр.Добавить();
    НоваяСтрока.Дата = Выборка.Дата;
    НоваяСтрока.Номер = Выборка.Номер;
    НоваяСтрока.Способ = Выборка.Способ;
    НоваяСтрока.Цена = Выборка.Цена;
    НоваяСтрока.ПредметЗакупки = Выборка.Предмет;
    НоваяСтрока.СпецификацияСумма = Выборка.ОбщееВсехСумм;
КонецЦикла;

При выполнении этого кода автор получает пустые значения в колонке СпецификацияСумма. Давайте разберем по шагам, почему так происходит и какие ошибки здесь допущены.


Ошибка №1: Концептуальное непонимание условия связи в соединениях

Это самая главная и критическая логическая ошибка автора. В своем сообщении он пишет: «Соединяем поля таблиц из поля СпецификацияСумма временной таблицы ОбщаяСумма добавляем данные в поле ОбщееВсехСумм таблицы ОбщиеСведения, разве не так?».

Разберем реальное положение дел:

Конструкция в секции ПО (в стандартном SQL — ON) — это условие сопоставления строк, а не операция присваивания значений! СУБД использует это выражение исключительно для того, чтобы понять: какие строки из левой таблицы соответствуют строкам из правой таблицы.

Поскольку в исходной таблице РегистрСведений.ОбщиеСведения поле ОбщееВсехСумм изначально пустое (не заполнено или содержит дефолтные значения), СУБД пытается выполнить сопоставление:

ПустоеЗначение (или NULL) = ОбщаяСумма.СпецификацияСумма (где лежат реальные числа, например, 10 000)

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

Как исправить:

Связывать таблицы необходимо по ключевым полям (уникальным идентификаторам, ссылкам на документы, номерам закупок/контрактов). Например, по полю закупки или номеру договора:

ПО ОбщиеСведения.НомерЗакупки = ОбщаяСумма.СпецификацияЗакупки

А перенос данных (сопоставление значений) происходит в секции ВЫБРАТЬ с помощью назначения псевдонимов полей.


Ошибка №2: Лишняя запятая в секции ИЗ (синтаксический сбой)

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


|ИЗ
| РегистрСведений.ОбщиеСведения КАК ОбщиеСведения,
| ЛЕВОЕ СОЕДИНЕНИЕ ОбщаяСумма КАК ОбщаяСумма

В языке запросов 1С перечисление таблиц через запятую означает их полное декартово объединение (аналог CROSS JOIN в SQL). Использование запятой непосредственно перед оператором ЛЕВОЕ СОЕДИНЕНИЕ нарушает синтаксические правила языка запросов. Такой запрос либо вообще не пройдет синтаксический анализ СУБД, либо отработает некорректно.

Как исправить:

Запятую после первой таблицы необходимо убрать. Оператор соединения должен следовать сразу за описанием левой таблицы:


ИЗ
    РегистрСведений.ОбщиеСведения КАК ОбщиеСведения
    ЛЕВОЕ СОЕДИНЕНИЕ ОбщаяСумма КАК ОбщаяСумма ...

Ошибка №3: Опасность соединения по числовым полям и суммам

Даже если бы поле ОбщееВсехСумм было заполнено какими-то числами, связывать таблицы по суммам в СУБД — крайне плохая практика по следующим причинам:

  1. Проблема точности вычислений: Дробные числа с плавающей точкой могут храниться в СУБД с минимальной погрешностью на системном уровне. Прямое сравнение на равенство (=) может вернуть ложь, даже если визуально числа кажутся абсолютно идентичными (например, 1500.0000001 и 1500).
  2. Задвоение данных (дублирование строк): Сумма не является уникальным идентификатором. Если в системе появятся две совершенно разные закупки с одинаковой итоговой суммой (например, две разные закупки по 500 рублей), СУБД при соединении по сумме сопоставит каждую запись левой таблицы с обеими записями правой таблицы. Произойдет "размножение" строк в результирующей выборке, что приведет к грубым ошибкам в отчете или реестре.

Ошибка №4: Неверное обращение к полям выборки в цикле обхода

Обратим внимание на заполнение строки реестра в цикле:

НоваяСтрока.СпецификацияСумма = Выборка.ОбщееВсехСумм;

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

НоваяСтрока.СпецификацияСумма = Выборка.СпецификацияСумма;


Ошибка №5: Отсутствие обработки значений NULL

При левом соединении для некоторых записей левой таблицы может не найтись соответствия в правой (например, для какой-то закупки еще не заведена спецификация). В этом случае поле ОбщаяСумма.СпецификацияСумма вернет значение NULL.

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

Как исправить:

В тексте запроса необходимо использовать функцию ЕСТЬNULL(), чтобы заменять пустоту на числовой ноль:

ЕСТЬNULL(ОбщаяСумма.СпецификацияСумма, 0) КАК СпецификацияСумма


Пошаговое руководство по исправлению и оптимизации кода

Давайте по шагам перепишем наше решение в соответствии со стандартами разработки фирмы "1С".

Шаг 1: Индексация временной таблицы

Поскольку наша временная таблица ОбщаяСумма будет участвовать в соединении, ее обязательно нужно проиндексировать по полю связи. Это существенно повысит скорость выполнения запроса СУБД при росте объема данных.

Шаг 2: Исправление синтаксиса и условий связи

Свяжем таблицы по полю НомерЗакупки (или аналогичному ключевому полю связи, которое присутствует в обоих источниках) и избавимся от лишней запятой.

Посмотрим на итоговый, правильный вариант программного кода:


МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

Запрос.Текст = "ВЫБРАТЬ
               | Спецификация.СпецификацияЗакупки КАК НомерЗакупки,
               | СУММА(Спецификация.СпецификацияСумма) КАК СпецификацияСумма
               |ПОМЕСТИТЬ ОбщаяСумма
               |ИЗ
               | РегистрСведений.Спецификация КАК Спецификация
               |СГРУППИРОВАТЬ ПО
               | Спецификация.СпецификацияЗакупки
               |
               |ИНДЕКСИРОВАТЬ ПО
               | НомерЗакупки
               |;
               |
               |ВЫБРАТЬ
               | ОбщиеСведения.Дата КАК Дата,
               | ОбщиеСведения.Номер КАК Номер,
               | ОбщиеСведения.Способ КАК Способ,
               | ОбщиеСведения.Цена КАК Цена,
               | ОбщиеСведения.Предмет КАК Предмет,
               | ЕСТЬNULL(ОбщаяСумма.СпецификацияСумма, 0) КАК СпецификацияСумма
               |ИЗ
               | РегистрСведений.ОбщиеСведения КАК ОбщиеСведения
               | ЛЕВОЕ СОЕДИНЕНИЕ ОбщаяСумма КАК ОбщаяСумма
               | ПО ОбщиеСведения.Номер = ОбщаяСумма.НомерЗакупки";

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

Пока Выборка.Следующий() Цикл
    НоваяСтрока = Реестр.Добавить();
    НоваяСтрока.Дата = Выборка.Дата;
    НоваяСтрока.Номер = Выборка.Номер;
    НоваяСтрока.Способ = Выборка.Способ;
    НоваяСтрока.Цена = Выборка.Цена;
    НоваяСтрока.ПредметЗакупки = Выборка.Предмет;
    // Теперь мы берем корректно сопоставленную сумму из выборки
    НоваяСтрока.СпецификацияСумма = Выборка.СпецификацияСумма; 
КонецЦикла;

Что изменилось в исправленном варианте?

  1. Правильная логика связи: Мы связали таблицы по смысловому уникальному ключу (ОбщиеСведения.Номер = ОбщаяСумма.НомерЗакупки). Теперь СУБД корректно сопоставляет строки.
  2. Безопасность типов: Функция ЕСТЬNULL(ОбщаяСумма.СпецификацияСумма, 0) гарантирует, что если для закупки нет спецификации, в реестр запишется числовой ноль 0, а не значение NULL.
  3. Синтаксическая чистота: Убрана ошибочная запятая перед оператором ЛЕВОЕ СОЕДИНЕНИЕ. Запрос успешно скомпилируется на любой СУБД.
  4. Производительность: Добавлена инструкция ИНДЕКСИРОВАТЬ ПО НомерЗакупки для временной таблицы. Это критически важно при работе с большими базами данных.
  5. Корректный обход выборки: В цикле заполнения реестра мы обращаемся к полю Выборка.СпецификацияСумма, в котором как раз и возвращается результат левого соединения.

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

← На главную