Как правильно составить и обойти единый запрос по документам и их табличным частям в 1С?

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

Типичная задача при интеграции, экспорте данных в XML или JSON — выгрузить список документов за указанный период вместе со строками их табличных частей. Начинающие разработчики нередко совершают классическую ошибку: сначала выбирают список документов, а затем в цикле обхода выполняют отдельный запрос к табличной части каждого найденного документа.

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

Способ 1. Запрос с секцией ИТОГИ и обход по группировкам (Рекомендуемый)

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

Посмотрим на пример построения запроса и обхода выборки для формирования XML-файла:


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

Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);

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

// Начинаем формирование XML
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ИмяФайлаВыгрузки);
ЗаписьXML.ЗаписатьНачалоЭлемента("Docs");

// Получаем выборку верхнего уровня (документы)
ВыборкаДокументы = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаДокументы.Следующий() Цикл
	
	ЗаписьXML.ЗаписатьНачалоЭлемента("Doc");
	ЗаписьXML.ЗаписатьАтрибут("num", Строка(ВыборкаДокументы.Номер));
	ЗаписьXML.ЗаписатьАтрибут("date", XMLСтрока(ВыборкаДокументы.Дата));
	
	ЗаписьXML.ЗаписатьНачалоЭлемента("Table");
	
	// Получаем подчиненную выборку детальных записей (строки ТЧ текущего документа)
	ВыборкаСтроки = ВыборкаДокументы.Выбрать();
	
	Пока ВыборкаСтроки.Следующий() Цикл
		
		// Проверяем наличие строки (на случай, если ТЧ документа пуста)
		Если ЗначениеЗаполнено(ВыборкаСтроки.КодТовара) Тогда
			ЗаписьXML.ЗаписатьНачалоЭлемента("SKU");
			ЗаписьXML.ЗаписатьЭлемент("code", Строка(ВыборкаСтроки.КодТовара));
			ЗаписьXML.ЗаписатьЭлемент("quantity", XMLСтрока(ВыборкаСтроки.Количество));
			ЗаписьXML.ЗаписатьЭлемент("price", XMLСтрока(ВыборкаСтроки.Цена));
			ЗаписьXML.ЗаписатьЭлемент("sum", XMLСтрока(ВыборкаСтроки.Сумма));
			ЗаписьXML.ЗаписатьКонецЭлемента(); // SKU
		КонецЕсли;
		
	КонецЦикла;
	
	ЗаписьXML.ЗаписатьКонецЭлемента(); // Table
	ЗаписьXML.ЗаписатьКонецЭлемента(); // Doc
	
КонецЦикла;

ЗаписьXML.ЗаписатьКонецЭлемента(); // Docs
ЗаписьXML.Закрыть();

Разберем по шагам ключевые особенности этого подхода:

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

Способ 2. Плоская выборка с контролем смены документа в цикле

Второй вариант — выборка детальных записей без секции ИТОГИ, но с обязательной сортировкой по ссылке документа. В процессе обхода плоского результата отслеживается момент, когда значение ссылки меняется, что означает переход к следующему документу.

Посмотрим на пример реализации данного алгоритма:


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

Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);

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

ТекущийДокумент = Неопределено;

Пока Выборка.Следующий() Цикл
	
	Если ТекущийДокумент <> Выборка.ДокументСсылка Тогда
		
		Если ТекущийДокумент <> Неопределено Тогда
			// Закрываем теги предыдущего документа
			ЗаписьXML.ЗаписатьКонецЭлемента(); // Table
			ЗаписьXML.ЗаписатьКонецЭлемента(); // Doc
		КонецЕсли;
		
		// Открываем теги нового документа
		ТекущийДокумент = Выборка.ДокументСсылка;
		ЗаписьXML.ЗаписатьНачалоЭлемента("Doc");
		ЗаписьXML.ЗаписатьАтрибут("num", Строка(Выборка.Номер));
		ЗаписьXML.ЗаписатьАтрибут("date", XMLСтрока(Выборка.Дата));
		ЗаписьXML.ЗаписатьНачалоЭлемента("Table");
		
	КонецЕсли;
	
	// Выгружаем строку табличной части
	Если ЗначениеЗаполнено(Выборка.КодТовара) Тогда
		ЗаписьXML.ЗаписатьНачалоЭлемента("SKU");
		ЗаписьXML.ЗаписатьЭлемент("code", Строка(Выборка.КодТовара));
		ЗаписьXML.ЗаписатьЭлемент("quantity", XMLСтрока(Выборка.Количество));
		ЗаписьXML.ЗаписатьКонецЭлемента(); // SKU
	КонецЕсли;
	
КонецЦикла;

// Закрываем последний открытый документ
Если ТекущийДокумент <> Неопределено Тогда
	ЗаписьXML.ЗаписатьКонецЭлемента(); // Table
	ЗаписьXML.ЗаписатьКонецЭлемента(); // Doc
КонецЕсли;

Способ 3. Пакетный запрос с временной таблицей

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

  1. Первый запрос выбирает ссылки на документы за период и помещает их во временную таблицу ВТ_Документы.
  2. Второй запрос выбирает реквизиты шапки из ВТ_Документы и основной таблицы документов.
  3. Третий запрос выбирает строки табличных частей, соединяясь с ВТ_Документы.

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

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

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

  1. Избегайте неявных соединений через точку: Конструкции вроде ТабличнаяЧасть.Ссылка.Склад или ТабличнаяЧасть.Номенклатура.Код заставляют платформу генерировать дополнительные LEFT JOIN к таблицам ссылок. Если полей много, соединяйте таблицы явно.
  2. Не используйте метод Выгрузить() на больших объемах: Выгрузка в ТаблицаЗначений или ДеревоЗначений требует единовременного выделения памяти под всю выборку. Использование Выбрать() с последовательным методом Следующий() работает в потоковом режиме и потребляет минимум оперативной памяти.
  3. Расчетные поля и итоги: Наличие сложных выражений в секции ВЫБРАТЬ не препятствует расчету итогов. Для вычисления суммовых показателей используйте СУММА(Поле) в секции ИТОГИ, а для строковых или ссылочных реквизитов шапки — агрегат МАКСИМУМ(Поле).
← На главную