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

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

При разработке отчетов, обработок и печатных форм в 1С часто возникает задача собрать воедино данные табличных частей документов (например, товары и услуги из ЗаказПокупателя) и сопоставить их с текущими остатками, резервами или размещением из регистров накопления. В зависимости от бизнес-логики под «объединением» разработчики могут понимать два принципиально разных механизма: горизонтальное соединение (добавление колонок остатков к строкам документа через ЛЕВОЕ СОЕДИНЕНИЕ) или вертикальное объединение (добавление строк остатков под строки документа через ОБЪЕДИНИТЬ ВСЕ).

Разберем подробно оба подхода, их реализацию и ключевые правила оптимизации запросов в платформе 1С:Предприятие.

Вариант 1. Соединение данных через пакет запросов и временные таблицы (Рекомендуемый подход)

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

Обратим внимание на важное правило: во вложенных запросах и запросах создания временных таблиц нельзя использовать секции ИТОГИ и УПОРЯДОЧИТЬ ПО. Сортировку и расчет итогов необходимо переносить в финальный результирующий запрос пакета.

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


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	ВложенныйЗапрос.Номенклатура КАК Номенклатура,
|	ВЫРАЗИТЬ(ВложенныйЗапрос.Номенклатура.НаименованиеПолное КАК СТРОКА(1000)) КАК НаименованиеПолное,
|	ВложенныйЗапрос.Номенклатура.Код КАК Код,
|	ВложенныйЗапрос.Номенклатура.Артикул КАК Артикул,
|	ВложенныйЗапрос.Количество КАК Количество,
|	ВложенныйЗапрос.ЕдиницаИзмерения.Представление КАК ЕдиницаИзмерения,
|	ВложенныйЗапрос.ПроцентСкидкиНаценки + ВложенныйЗапрос.ПроцентАвтоматическихСкидок КАК Скидка,
|	ВложенныйЗапрос.Цена КАК Цена,
|	ВложенныйЗапрос.Сумма КАК Сумма,
|	ВложенныйЗапрос.СуммаНДС КАК СуммаНДС,
|	ВложенныйЗапрос.Характеристика КАК Характеристика,
|	NULL КАК Серия,
|	ВложенныйЗапрос.НомерСтроки КАК НомерСтроки,
|	ВложенныйЗапрос.Метка КАК Метка
|ПОМЕСТИТЬ ВТ_ТоварыИУслуги
|ИЗ
|	(ВЫБРАТЬ
|		СчетНаОплату.Номенклатура КАК Номенклатура,
|		СчетНаОплату.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
|		СчетНаОплату.ПроцентСкидкиНаценки КАК ПроцентСкидкиНаценки,
|		СчетНаОплату.ПроцентАвтоматическихСкидок КАК ПроцентАвтоматическихСкидок,
|		СчетНаОплату.Цена КАК Цена,
|		СУММА(СчетНаОплату.Количество) КАК Количество,
|		СУММА(СчетНаОплату.Сумма) КАК Сумма,
|		СУММА(СчетНаОплату.СуммаНДС) КАК СуммаНДС,
|		СчетНаОплату.ХарактеристикаНоменклатуры КАК Характеристика,
|		МИНИМУМ(СчетНаОплату.НомерСтроки) КАК НомерСтроки,
|		0 КАК Метка
|	ИЗ
|		Документ.ЗаказПокупателя.Товары КАК СчетНаОплату
|	ГДЕ
|		СчетНаОплату.Ссылка = &ТекущийДокумент
|	СГРУППИРОВАТЬ ПО
|		СчетНаОплату.Номенклатура,
|		СчетНаОплату.ЕдиницаИзмерения,
|		СчетНаОплату.ПроцентСкидкиНаценки,
|		СчетНаОплату.ПроцентАвтоматическихСкидок,
|		СчетНаОплату.Цена,
|		СчетНаОплату.ХарактеристикаНоменклатуры
|	
|	ОБЪЕДИНИТЬ ВСЕ
|	
|	ВЫБРАТЬ
|		СчетНаОплату.Номенклатура,
|		СчетНаОплату.Номенклатура.ЕдиницаХраненияОстатков,
|		СчетНаОплату.ПроцентСкидкиНаценки,
|		СчетНаОплату.ПроцентАвтоматическихСкидок,
|		СчетНаОплату.Цена,
|		СУММА(СчетНаОплату.Количество),
|		СУММА(СчетНаОплату.Сумма),
|		СУММА(СчетНаОплату.СуммаНДС),
|		NULL,
|		МИНИМУМ(СчетНаОплату.НомерСтроки),
|		1
|	ИЗ
|		Документ.ЗаказПокупателя.Услуги КАК СчетНаОплату
|	ГДЕ
|		СчетНаОплату.Ссылка = &ТекущийДокумент
|	СГРУППИРОВАТЬ ПО
|		СчетНаОплату.Номенклатура,
|		СчетНаОплату.ПроцентСкидкиНаценки,
|		СчетНаОплату.ПроцентАвтоматическихСкидок,
|		СчетНаОплату.Цена,
|		СчетНаОплату.Номенклатура.ЕдиницаХраненияОстатков) КАК ВложенныйЗапрос
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	ЗаказыПокупателейОстатки.Номенклатура КАК Номенклатура,
|	ЗаказыПокупателейОстатки.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
|	ЗаказыПокупателейОстатки.КоличествоОстаток КАК ОсталосьОтгрузить,
|	ЕСТЬNULL(РезервыКомпании.КоличествоОстаток, 0) + ЕСТЬNULL(РезервыКПередаче.КоличествоОстаток, 0) - ЕСТЬNULL(РезервыКПолучению.КоличествоОстаток, 0) КАК Резерв,
|	ЕСТЬNULL(РазмещениеЗаказовПокупателей.КоличествоОстаток, 0) КАК Заказано
|ПОМЕСТИТЬ ВТ_ОстаткиИРезервы
|ИЗ
|	РегистрНакопления.ЗаказыПокупателей.Остатки(&ДатаАнализа, ЗаказПокупателя = &Заказ) КАК ЗаказыПокупателейОстатки
|	ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыВРезервеНаСкладах.Остатки(&ДатаАнализа, ДокументРезерва = &Заказ) КАК РезервыКомпании
|		ПО ЗаказыПокупателейОстатки.ХарактеристикаНоменклатуры = РезервыКомпании.ХарактеристикаНоменклатуры
|		И ЗаказыПокупателейОстатки.Номенклатура = РезервыКомпании.Номенклатура
|	ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.РазмещениеЗаказовПокупателей.Остатки(&ДатаАнализа, ЗаказПокупателя = &Заказ) КАК РазмещениеЗаказовПокупателей
|		ПО ЗаказыПокупателейОстатки.Номенклатура = РазмещениеЗаказовПокупателей.Номенклатура
|		И ЗаказыПокупателейОстатки.ХарактеристикаНоменклатуры = РазмещениеЗаказовПокупателей.ХарактеристикаНоменклатуры
|	ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыКПередачеСоСкладов.Остатки(&ДатаАнализа, ДокументРезерва = &Заказ) КАК РезервыКПередаче
|		ПО ЗаказыПокупателейОстатки.ХарактеристикаНоменклатуры = РезервыКПередаче.ХарактеристикаНоменклатуры
|		И ЗаказыПокупателейОстатки.Номенклатура = РезервыКПередаче.Номенклатура
|	ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыКПолучениюНаСклады.Остатки(&ДатаАнализа, ДокументРезерва = &Заказ) КАК РезервыКПолучению
|		ПО ЗаказыПокупателейОстатки.ХарактеристикаНоменклатуры = РезервыКПолучению.ХарактеристикаНоменклатуры
|		И ЗаказыПокупателейОстатки.Номенклатура = РезервыКПолучению.Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	ВТ_ТоварыИУслуги.Номенклатура КАК Номенклатура,
|	ВТ_ТоварыИУслуги.НаименованиеПолное КАК НаименованиеПолное,
|	ВТ_ТоварыИУслуги.Код КАК Код,
|	ВТ_ТоварыИУслуги.Артикул КАК Артикул,
|	ВТ_ТоварыИУслуги.Количество КАК Количество,
|	ВТ_ТоварыИУслуги.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
|	ВТ_ТоварыИУслуги.Скидка КАК Скидка,
|	ВТ_ТоварыИУслуги.Цена КАК Цена,
|	ВТ_ТоварыИУслуги.Сумма КАК Сумма,
|	ВТ_ТоварыИУслуги.СуммаНДС КАК СуммаНДС,
|	ВТ_ТоварыИУслуги.Характеристика КАК Характеристика,
|	ВТ_ТоварыИУслуги.Серия КАК Серия,
|	ВТ_ТоварыИУслуги.НомерСтроки КАК НомерСтроки,
|	ВТ_ТоварыИУслуги.Метка КАК Метка,
|	ЕСТЬNULL(ВТ_ОстаткиИРезервы.ОсталосьОтгрузить, 0) КАК ОсталосьОтгрузить,
|	ЕСТЬNULL(ВТ_ОстаткиИРезервы.Резерв, 0) КАК Резерв,
|	ЕСТЬNULL(ВТ_ОстаткиИРезервы.Заказано, 0) КАК Заказано
|ИЗ
|	ВТ_ТоварыИУслуги КАК ВТ_ТоварыИУслуги
|	ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ОстаткиИРезервы КАК ВТ_ОстаткиИРезервы
|		ПО ВТ_ТоварыИУслуги.Номенклатура = ВТ_ОстаткиИРезервы.Номенклатура
|		И (ВТ_ТоварыИУслуги.Характеристика = ВТ_ОстаткиИРезервы.ХарактеристикаНоменклатуры
|			ИЛИ ВТ_ТоварыИУслуги.Характеристика ЕСТЬ NULL И ВТ_ОстаткиИРезервы.ХарактеристикаНоменклатуры ЕСТЬ NULL)
|УПОРЯДОЧИТЬ ПО
|	Метка,
|	НомерСтроки";

Вариант 2. Вертикальное объединение выборок через ОБЪЕДИНИТЬ ВСЕ

Если цель заключается именно в получении общего плоского списка, где строки остатков идут отдельными строками (например, для последующей ручной обработки дерева значений или особого макета), используется оператор ОБЪЕДИНИТЬ ВСЕ.

При использовании ОБЪЕДИНИТЬ ВСЕ необходимо соблюдать строгие правила:

  1. Количество полей во всех объединяемых запросах должно быть строго одинаковым.
  2. Типы колонок в позициях выборок должны совпадать или быть приведены к совместимым типам.
  3. Имена результирующих колонок всегда определяются по первому запросу в конструкции.
  4. Если в одной из выборок поле отсутствует, на его место подставляется значение NULL или типизированная константа (например, 0 или пустая строка).

Рассмотрим структуру запроса с объединением трех источников:


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

Важные рекомендации по производительности запросов

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

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