При разработке правил конвертации данных (КД 2.0) или самописных обработок переноса между базами часто возникает необходимость свернуть несколько документов в один общий сводный документ по определенным критериям (например, по контрагенту и складу за выбранный период). Написание подобных алгоритмов «от фонаря» часто приводит к скрытым логическим ошибкам, падению производительности базы и перерасходу оперативной памяти.
Разберем типичный пример такого кода, выясним его уязвимые места и рассмотрим правильные варианты оптимизации выборки данных платформой 1С:Предприятие.
Посмотрим на исходный вариант решения: разработчик выполняет запрос к табличной части Документ.РеализацияТоваровУслуг.Товары с группировкой по множеству полей шапки и строки, после чего выгружает результат одновременно в объект Выборка и таблицу значений ТЗ, организуя сложный ручной цикл со сменой складов и заполнением строк.
Проанализируем критические недостатки такого подхода:
СледующийПоЗначениюПоля("Контрагент") смещает указатель, но вложенный цикл Пока Выборка.Следующий() Цикл в линейной выборке (без предложения ИТОГИ) обходит строки подряд до самого конца всей выборки, не останавливаясь на смене контрагента. В результате первая итерация внешнего цикла прочтет абсолютно все записи, а последующие итерации по контрагентам просто не состоятся.ПозТЗ указывает на нулевой элемент, который копируется в шапку документа. Далее в первой же итерации цикла по строкам берется тот же самый индекс ПозТЗ, что приводит к повторному включению данных первой строки.РеализацияТоваровУслугТовары.Ссылка.Контрагент или РеализацияТоваровУслугТовары.Ссылка.Склад в полях выборки и группировки заставляют СУБД создавать множественные левые соединения физической таблицы строк документа с таблицей шапки, что существенно замедляет выполнение запроса.ГДЕ РеализацияТоваровУслугТовары.Ссылка.Дата МЕЖДУ ... накладывается на ссылку из табличной части, лишая СУБД возможности быстро использовать стандартный кластерный индекс документов по периоду.РезультатЗапроса.Выгрузить() и РезультатЗапроса.Выбрать() дублирует массив информации в памяти. Кроме того, создание колонок со вложенными таблицами значений (НоваяСтрока.Товары = ТЧ.Скопировать()) при больших объемах переносимых документов приводит к переполнению оперативной памяти рабочего процесса 1С.Наиболее чистое и производительное решение заключается в переносе логики группировки документов на сторону СУБД с помощью конструкции ИТОГИ ПО. Это позволяет получить готовую двухуровневую иерархию (шапка документа и его табличная часть) без необходимости вручную синхронизировать индексы и отслеживать смену складов.
Разберем по шагам построение оптимального запроса:
ВНУТРЕННЕЕ СОЕДИНЕНИЕ.Посмотрим на пример оптимизированного запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Док.Ссылка КАК Ссылка
|ПОМЕСТИТЬ ВТ_Документы
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Док
|ГДЕ
| Док.Проведен
| И Док.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
|ИНДЕКСИРОВАТЬ ПО
| Ссылка
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Док.Ссылка.Организация КАК Организация,
| Док.Ссылка.Контрагент КАК Контрагент,
| Док.Ссылка.ДоговорКонтрагента КАК ДоговорКонтрагента,
| Док.Ссылка.Склад КАК Склад,
| Док.Ссылка.ВалютаДокумента КАК ВалютаДокумента,
| Док.Ссылка.УчитыватьНДС КАК УчитыватьНДС,
| Док.Ссылка.СуммаВключаетНДС КАК СуммаВключаетНДС,
| Док.Ссылка.ВидОперации КАК ВидОперации,
| Док.Ссылка.ТипЦен КАК ТипЦен,
| 1 КАК КурсВзаиморасчетов,
| 1 КАК КратностьВзаиморасчетов,
| Товары.Номенклатура КАК Номенклатура,
| Товары.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
| Товары.Цена КАК Цена,
| Товары.СтавкаНДС КАК СтавкаНДС,
| 1 КАК Коэффициент,
| 1 КАК КоличествоМест,
| СУММА(Товары.Количество) КАК Количество,
| СУММА(Товары.Сумма) КАК Сумма,
| СУММА(Товары.СуммаНДС) КАК СуммаНДС
|ИЗ
| ВТ_Документы КАК ВТ_Документы
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Товары КАК Товары
| ПО ВТ_Документы.Ссылка = Товары.Ссылка
|
|СГРУППИРОВАТЬ ПО
| Док.Ссылка.Организация,
| Док.Ссылка.Контрагент,
| Док.Ссылка.ДоговорКонтрагента,
| Док.Ссылка.Склад,
| Док.Ссылка.ВалютаДокумента,
| Док.Ссылка.УчитыватьНДС,
| Док.Ссылка.СуммаВключаетНДС,
| Док.Ссылка.ВидОперации,
| Док.Ссылка.ТипЦен,
| Товары.Номенклатура,
| Товары.ЕдиницаИзмерения,
| Товары.Цена,
| Товары.СтавкаНДС
|
|ИТОГИ
| СУММА(Количество),
| СУММА(Сумма),
| СУММА(СуммаНДС)
|ПО
| Организация,
| Контрагент,
| ДоговорКонтрагента,
| Склад,
| ВалютаДокумента,
| УчитыватьНДС,
| СуммаВключаетНДС,
| ВидОперации,
| ТипЦен";
Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);
РезультатЗапроса = Запрос.Выполнить();
После применения конструкции ИТОГИ ПО отпадает необходимость вручную сравнивать значения реквизитов и использовать вспомогательные таблицы. Мы организуем обход верхнего уровня по группировкам, а затем вложенной выборкой получаем детализирующие строки текущего свернутого документа.
Рассмотрим, как выглядит обход результата:
// Создаем структуру результирующей таблицы
ТЗРез = Новый ТаблицаЗначений;
// Формируем колонки шапки
ТЗРез.Колонки.Добавить("Организация");
ТЗРез.Колонки.Добавить("Контрагент");
ТЗРез.Колонки.Добавить("ДоговорКонтрагента");
ТЗРез.Колонки.Добавить("Склад");
ТЗРез.Колонки.Добавить("ВалютаДокумента");
ТЗРез.Колонки.Добавить("УчитыватьНДС");
ТЗРез.Колонки.Добавить("СуммаВключаетНДС");
ТЗРез.Колонки.Добавить("Товары"); // Вложенная ТЗ для строк
// Задаем шаблон колонок для табличной части
ТЧШаблон = Новый ТаблицаЗначений;
ТЧШаблон.Колонки.Добавить("Номенклатура");
ТЧШаблон.Колонки.Добавить("ЕдиницаИзмерения");
ТЧШаблон.Колонки.Добавить("Количество");
ТЧШаблон.Колонки.Добавить("Цена");
ТЧШаблон.Колонки.Добавить("Сумма");
ТЧШаблон.Колонки.Добавить("СтавкаНДС");
ТЧШаблон.Колонки.Добавить("СуммаНДС");
// Выбираем только группировочные записи верхнего уровня
ВыборкаШапка = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаШапка.Следующий() Цикл
СтрокаДокумента = ТЗРез.Добавить();
ЗаполнитьЗначенияСвойств(СтрокаДокумента, ВыборкаШапка);
// Создаем персональную таблицу строк для текущего документа
ТЧДокумента = ТЧШаблон.СкопироватьКолонки();
// Выбираем детальные строки, относящиеся исключительно к текущей группе
ВыборкаСтроки = ВыборкаШапка.Выбрать();
Пока ВыборкаСтроки.Следующий() Цикл
НоваяСтрокаТЧ = ТЧДокумента.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрокаТЧ, ВыборкаСтроки);
КонецЦикла;
СтрокаДокумента.Товары = ТЧДокумента;
КонецЦикла;
ВыборкаДанных = ТЗРез;
Если документов за период много, сохранение всех вложенных таблиц значений в общей таблице ВыборкаДанных способно вызвать ошибку нехватки памяти (Out of memory). Для конвертации данных эффективнее применить потоковый метод: отказаться от промежуточной таблицы ТЗРез и производить выгрузку каждого документа по отдельности непосредственно в цикле.
Для этого в обработчике «Перед обработкой правила выгрузки» (ПВД) устанавливают признак Отказ = Истина; и используют системную процедуру ВыгрузитьПоПравилу():
Отказ = Истина; // Отменяем стандартный сбор данных в память
ВыборкаШапка = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаШапка.Следующий() Цикл
// Создаем структуру со свойствами документа для конвертации
СтруктураДокумента = Новый Структура;
СтруктураДокумента.Вставить("Организация", ВыборкаШапка.Организация);
СтруктураДокумента.Вставить("Контрагент", ВыборкаШапка.Контрагент);
СтруктураДокумента.Вставить("ДоговорКонтрагента", ВыборкаШапка.ДоговорКонтрагента);
СтруктураДокумента.Вставить("Склад", ВыборкаШапка.Склад);
ТаблицаТовары = ТЧШаблон.СкопироватьКолонки();
ВыборкаСтроки = ВыборкаШапка.Выбрать();
Пока ВыборкаСтроки.Следующий() Цикл
НоваяСтрока = ТаблицаТовары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ВыборкаСтроки);
КонецЦикла;
СтруктураДокумента.Вставить("Товары", ТаблицаТовары);
// Немедленно сериализуем один документ в поток выгрузки
ВыгрузитьПоПравилу(СтруктураДокумента, , , , "РеализацияТоваровУслуг");
КонецЦикла;
Такой подход гарантирует стабильную работу переноса при любых объемах данных, устраняет дублирование информации в оперативной памяти и формирует корректные учетные документы в конфигурации-приемнике.