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

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

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

Причина 1: Неверный порядок секций в запросе (секция ГДЕ перед ЛЕВОЕ СОЕДИНЕНИЕ)

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


ИЗ РегистрСведений.СпецификацияКонтракта КАК СпецификацияКонтракта 
ГДЕ СпецификацияКонтракта.СпецификацияКонтрактаНомерЗакупки = "1234567891011" 
ЛЕВОЕ СОЕДИНЕНИЕ ВремТабПоставкаКоличество КАК ВремТабПоставкаКоличество...

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

Решение: Всегда переносите секцию ГДЕ в самый низ выборки текущего пакета запроса, после того как объявлены все связи между таблицами.

Причина 2: Скрытая синтаксическая ошибка с лишней закрывающей скобкой

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


ЛЕВОЕ СОЕДИНЕНИЕ ВремТабПоставкаКоличество КАК ВремТабПоставкаКоличество 
ПО СпецификацияКонтракта.СпецификацияКонтрактаНаименование = ВремТабПоставкаКоличество.ПоставкаНаименованиеПозиции)

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

Решение: Тщательно проверяйте парность всех скобок в условиях связи ПО. Если вы не объединяете сложные условия через логические операторы И / ИЛИ, круглые скобки вокруг простого равенства полей вообще можно опустить.

Причина 3: Логическая ошибка при расчете остатков с использованием NULL

Проанализируем еще одну критическую ошибку, которая не вызовет падения запроса на этапе компиляции, но приведет к неверным расчетам в процессе работы системы. Разработчик пытается вычислить остаток простым вычитанием:


СпецификацияКонтракта.СпецификацияКонтрактаКоличество - ВремТабПоставкаКоличество.ОстатокКоличество КАК ОстатокКоличество

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

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

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


СпецификацияКонтракта.СпецификацияКонтрактаКоличество - ЕСТЬNULL(ВремТабПоставкаКоличество.ОстатокКоличество, 0) КАК ОстатокКоличество

Причина 4: Избыточность и дублирование при создании временных таблиц

Рассмотрим структуру исходного запроса. Автор создает две отдельные временные таблицы: ВремТабПоставкаКоличество и ВремТабПоставкаСумма. Обе таблицы обращаются к одному и тому же источнику данных — РегистрСведений.Поставка, используют идентичные отборы по номеру закупки и различаются лишь выбираемым ресурсом (Количество или Сумма).

Это классический пример неоптимального кода, приводящий к двум последовательным сканированиям одного и того же физического индекса или таблицы на сервере СУБД. Намного эффективнее объединить эти данные в единую временную таблицу.

Причина 5: Антипаттерн "Соединение по строковому наименованию"

Соединение таблиц по текстовому полю (например, по наименованию позиции СпецификацияКонтрактаНаименование = ПоставкаНаименованиеПозиции) — это опасный подход, который влечет за собой две большие проблемы:

  1. Низкая производительность. Сравнение строковых типов неограниченной или большой длины на уровне СУБД происходит в разы медленнее, чем сравнение уникальных идентификаторов (GUID/ссылок) или чисел. База данных тратит огромные ресурсы на посимвольный анализ строк.
  2. Ненадежность данных. Если в одной таблице позиция называется "Цемент марка 400", а во второй — "Цемент марка 400 " (с невидимым пробелом в конце), или если в одном из названий случайно будет использована латинская буква "C" вместо кириллической, левое соединение не сопоставит эти строки. В итоге остатки рассчитаются неверно.

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

Причина 6: Проблема дублирования строк при левом соединении (Многие-к-одному)

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

Перед тем как соединять ведомую таблицу с ведущей, ведомую таблицу необходимо предварительно сгруппировать (просуммировать ресурсы) в разрезе ключевых полей связи.

---

Оптимальный шаблон решения проблемы

Объединив все исправления над синтаксисом и логикой, мы можем составить чистый, быстрый и полностью рабочий запрос. Мы объединяем два обращения к таблице поставок в один запрос с группировкой по ключевым полям, избавляемся от дублирования строк, переносим секции ГДЕ в корректные места и защищаем арифметические вычисления от значений NULL.


// Шаг 1: Получаем сгруппированные данные по поставкам в ОДНУ временную таблицу
ВЫБРАТЬ
	Поставка.ПоставкаНомерЗакупки КАК ПоставкаНомерЗакупки,
	Поставка.ПоставкаНаименованиеПозиции КАК ПоставкаНаименованиеПозиции,
	СУММА(Поставка.ПоставкаКоличество) КАК ОстатокКоличество,
	СУММА(Поставка.ПоставкаСумма) КАК ОстатокСуммы
ПОМЕСТИТЬ ВремТабПоставка
ИЗ
	РегистрСведений.Поставка КАК Поставка
ГДЕ
	Поставка.ПоставкаНомерЗакупки = "1234567891011"

СГРУППИРОВАТЬ ПО
	Поставка.ПоставкаНомерЗакупки,
	Поставка.ПоставкаНаименованиеПозиции
;

////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Соединяем спецификацию с временной таблицей поставок и вычисляем остатки
ВЫБРАТЬ
	СпецификацияКонтракта.СпецификацияКонтрактаНомерЗакупки КАК СпецификацияКонтрактаНомерЗакупки,
	СпецификацияКонтракта.СпецификацияКонтрактаНаименование КАК СпецификацияКонтрактаНаименование,
	СпецификацияКонтракта.СпецификацияКонтрактаКоличество КАК СпецификацияКонтрактаКоличество,
	СпецификацияКонтракта.СпецификацияКонтрактаЦенаЗаЕд КАК СпецификацияКонтрактаЦенаЗаЕд,
	СпецификацияКонтракта.СпецификацияКонтрактаЕдИзм КАК СпецификацияКонтрактаЕдИзм,
	СпецификацияКонтракта.СпецификацияКонтрактаСумма КАК СпецификацияКонтрактаСумма,
	
	// Безопасное вычитание с защитой от NULL значений
	СпецификацияКонтракта.СпецификацияКонтрактаКоличество - ЕСТЬNULL(ВремТабПоставка.ОстатокКоличество, 0) КАК ОстатокКоличество,
	СпецификацияКонтракта.СпецификацияКонтрактаСумма - ЕСТЬNULL(ВремТабПоставка.ОстатокСуммы, 0) КАК ОстатокСуммы
ИЗ
	РегистрСведений.СпецификацияКонтракта КАК СпецификацияКонтракта
		
		// Одно левое соединение вместо двух раздельных
		ЛЕВОЕ СОЕДИНЕНИЕ ВремТабПоставка КАК ВремТабПоставка
		ПО СпецификацияКонтракта.СпецификацияКонтрактаНаименование = ВремТабПоставка.ПоставкаНаименованиеПозиции
ГДЕ
	СпецификацияКонтракта.СпецификацияКонтрактаНомерЗакупки = "1234567891011"

Использование этого шаблона позволит вам навсегда избавиться от синтаксических ошибок при связывании таблиц, минимизировать нагрузку на СУБД и гарантировать математическую точность при расчете остатков и разниц в отчетах.

← На главную