Как правильно соединить 3 таблицы в запросе 1С?

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

При разработке отчетов и обработок в 1С часто возникает задача объединить разнородные данные по ключевому полю (например, по контрагенту, номенклатуре или физическому лицу), когда адрес, паспортные данные, контактная информация или свойства хранятся в разных таблицах либо в виде записей регистра сведений типа «Объект – Реквизит – Значение». Разберем основные подходы к решению этой задачи, выясним причину частых ошибок начинающих разработчиков и рассмотрим наиболее эффективные варианты построения запросов.

Анализ типичной ошибки: разница между объединением и соединением

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

Чтобы скомпоновать данные горизонтально в одну строку по ключевому полю, необходимо использовать именно конструкции соединения таблиц — ЛЕВОЕ СОЕДИНЕНИЕ или ВНУТРЕННЕЕ СОЕДИНЕНИЕ.

Способ 1. Соединение через базовую таблицу ключей (Левое соединение)

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

Рассмотрим пример, когда данные берутся из справочника Контрагенты и регистра сведений ДанныеЧленовКооператива:


ВЫБРАТЬ
	Контрагенты.Ссылка КАК Контрагент,
	ЧленыАдрес.Значение КАК Адрес,
	ЧленыВозраст.Значение КАК Возраст,
	ЧленыПаспорт.Значение КАК НомерПаспорта
ИЗ
	Справочник.Контрагенты КАК Контрагенты
		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДанныеЧленовКооператива КАК ЧленыАдрес
		ПО Контрагенты.Ссылка = ЧленыАдрес.Контрагент
			И (ЧленыАдрес.Реквизит = ЗНАЧЕНИЕ(ПланВидовХарактеристик.РеквизитыДаныхЧленовКооператива.Адрес))
		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДанныеЧленовКооператива КАК ЧленыВозраст
		ПО Контрагенты.Ссылка = ЧленыВозраст.Контрагент
			И (ЧленыВозраст.Реквизит = ЗНАЧЕНИЕ(ПланВидовХарактеристик.РеквизитыДаныхЧленовКооператива.Возраст))
		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДанныеЧленовКооператива КАК ЧленыПаспорт
		ПО Контрагенты.Ссылка = ЧленыПаспорт.Контрагент
			И (ЧленыПаспорт.Реквизит = ЗНАЧЕНИЕ(ПланВидовХарактеристик.РеквизитыДаныхЧленовКооператива.НомерПаспорта))

Важный момент: если требуется отобрать только тех контрагентов, по которым уже есть хотя бы одна запись в регистре, мы можем сначала сформировать временную таблицу уникальных контрагентов из регистра с помощью конструкции ВЫБРАТЬ РАЗЛИЧНЫЕ, а затем использовать ее в качестве ведущей для левых соединений.

Способ 2. Разворот данных через агрегатные функции и группировку (Оптимальный способ)

Когда все свойства объекта хранятся в одном регистре сведений, выполнение множественных соединений одной и той же физической таблицы неоптимально с точки зрения нагрузки на СУБД. Намного эффективнее выполнить один проход по регистру с последующей группировкой и использованием конструкции ВЫБОР внутри агрегатной функции МАКСИМУМ.

Разберем по шагам, как устроен такой запрос:


ВЫБРАТЬ
	ДанныеЧленовКооператива.Контрагент КАК Контрагент,
	МАКСИМУМ(ВЫБОР
			КОГДА ДанныеЧленовКооператива.Реквизит = ЗНАЧЕНИЕ(ПланВидовХарактеристик.РеквизитыДаныхЧленовКооператива.Адрес)
				ТОГДА ДанныеЧленовКооператива.Значение
			ИНАЧЕ NULL
		КОНЕЦ) КАК Адрес,
	МАКСИМУМ(ВЫБОР
			КОГДА ДанныеЧленовКооператива.Реквизит = ЗНАЧЕНИЕ(ПланВидовХарактеристик.РеквизитыДаныхЧленовКооператива.Возраст)
				ТОГДА ДанныеЧленовКооператива.Значение
			ИНАЧЕ NULL
		КОНЕЦ) КАК Возраст,
	МАКСИМУМ(ВЫБОР
			КОГДА ДанныеЧленовКооператива.Реквизит = ЗНАЧЕНИЕ(ПланВидовХарактеристик.РеквизитыДаныхЧленовКооператива.НомерПаспорта)
				ТОГДА ДанныеЧленовКооператива.Значение
			ИНАЧЕ NULL
		КОНЕЦ) КАК НомерПаспорта
ИЗ
	РегистрСведений.ДанныеЧленовКооператива КАК ДанныеЧленовКооператива
СГРУППИРОВАТЬ ПО
	ДанныеЧленовКооператива.Контрагент

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

Способ 3. Соединение произвольных таблиц значений в запросе

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

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

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

Посмотрим на практический пример передачи двух таблиц и их соединения по общему ключу:


СвойстваНоменклатуры = Объект.СвойстваНоменклатуры.Выгрузить();
ТаблицаНоменклатуры = Объект.ТабличнаяЧасть.Выгрузить();

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

Запрос = Новый Запрос;
Запрос.Текст = ТекстЗапроса;
Запрос.УстановитьПараметр("СвойстваНоменклатуры", СвойстваНоменклатуры);
Запрос.УстановитьПараметр("ТаблицаНоменклатуры", ТаблицаНоменклатуры);

РезультатЗапроса = Запрос.Выполнить();
Объект.СвойстваНоменклатуры.Загрузить(РезультатЗапроса.Выгрузить());

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

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

← На главную