В процессе разработки на платформе 1С:Предприятие 8 часто возникает задача собрать данные из одинаковых по смыслу табличных частей разных документов. Например, нам нужно проанализировать упаковки товаров во всех документах поступления, реализации и перемещения. Типичная ошибка начинающего разработчика — выполнять запрос в цикле для каждого вида документа. Разберем, почему это плохо и как реализовать задачу максимально эффективно.
Рассмотрим стандартную ситуацию: в базе есть несколько десятков видов документов (ПоступлениеТоваров, ОприходованиеТоваров, ПеремещениеТоваров и др.), и у каждого есть табличная часть Товары. Программист часто пытается решить задачу «в лоб»:
МассивНужныхВидовДокументов = Новый Массив;
МассивНужныхВидовДокументов.Добавить("Документ.ПоступлениеТоваров");
МассивНужныхВидовДокументов.Добавить("Документ.ОприходованиеТоваров");
Для Каждого ТекСтрокаВидаДокумента Из МассивНужныхВидовДокументов Цикл
ТекстЗапроса = "ВЫБРАТЬ Ссылка ИЗ " + ТекСтрокаВидаДокумента + ".Товары ГДЕ ...";
// Выполнение запроса
КонецЦикла;
Проанализируем этот подход. Каждый вызов метода Запрос.Выполнить() — это отдельное обращение приложения к серверу 1С, а сервера 1С — к СУБД (SQL Server, PostgreSQL и т.д.). Если документов 58, мы получим 58 сетевых задержек и 58 операций открытия/закрытия курсоров в базе данных. Это создает избыточную нагрузку на планировщик запросов СУБД. Правильный путь — объединить все подзапросы в один большой запрос с помощью конструкции ОБЪЕДИНИТЬ ВСЕ (UNION ALL).
Выясним, как собрать один эффективный запрос, если список документов нам известен заранее. Основная хитрость здесь заключается в правильной работе со строками. Использование оператора «+» для сложения строк в цикле — плохая практика, так как при каждой итерации выделяется новый блок памяти. Вместо этого мы будем использовать Массив и функцию СтрСоединить.
Рассмотрим пример кода:
МассивЗапросов = Новый Массив;
ШаблонЗапроса =
"ВЫБРАТЬ
| ТчТовары.Ссылка КАК Ссылка,
| ТчТовары.Упаковка КАК Упаковка
|ИЗ
| &ИмяТаблицы КАК ТчТовары
|ГДЕ
| ТчТовары.Упаковка <> ЗНАЧЕНИЕ(Справочник.УпаковкиНоменклатуры.ПустаяСсылка)";
СписокДокументов = "ПоступлениеТоваров,ОприходованиеТоваров,ПеремещениеТоваров"; // и так далее
МассивВидов = СтрРазделить(СписокДокументов, ",");
Для Каждого ВидДокумента Из МассивВидов Цикл
ПолноеИмяТаблицы = "Документ." + ВидДокумента + ".Товары";
ТекстПодзапроса = СтрЗаменить(ШаблонЗапроса, "&ИмяТаблицы", ПолноеИмяТаблицы);
МассивЗапросов.Добавить(ТекстПодзапроса);
КонецЦикла;
ИтоговыйТекстЗапроса = СтрСоединить(МассивЗапросов, Символы.ПС + "ОБЪЕДИНИТЬ ВСЕ" + Символы.ПС);
Запрос = Новый Запрос(ИтоговыйТекстЗапроса);
Результат = Запрос.Выполнить();
Этот подход позволяет СУБД построить единый план выполнения. Важный нюанс: использование ОБЪЕДИНИТЬ ВСЕ предпочтительнее простого ОБЪЕДИНИТЬ (UNION), так как исключает избыточную операцию проверки на уникальность строк (DISTINCT), что существенно ускоряет выполнение.
А что делать, если мы не знаем точный список документов? Например, в конфигурацию могут быть добавлены новые виды документов, и мы хотим, чтобы наш код автоматически подхватывал их ТЧ Товары. Проанализируем ситуацию, используя объект Метаданные.
Разберем алгоритм по шагам:
Метаданные.Документы.Товары.Посмотрим на программную реализацию:
МассивЗапросов = Новый Массив;
Шаблон = "ВЫБРАТЬ Ссылка КАК Ссылка ИЗ Документ.[ИмяДокумента].Товары КАК Тч";
Для Каждого МетаДокумент Из Метаданные.Документы Цикл
Если МетаДокумент.ТабличныеЧасти.Найти("Товары") <> Неопределено Тогда
ТекстПодзапроса = СтрЗаменить(Шаблон, "[ИмяДокумента]", МетаДокумент.Имя);
МассивЗапросов.Добавить(ТекстПодзапроса);
КонецЕсли;
КонецЦикла;
Если МассивЗапросов.Количество() > 0 Тогда
Запрос = Новый Запрос(СтрСоединить(МассивЗапросов, Символы.ПС + "ОБЪЕДИНИТЬ ВСЕ" + Символы.ПС));
// Выполняем...
КонецЕсли;
Такой подход делает ваше решение универсальным и отказоустойчивым к изменениям структуры метаданных.
Когда документов много (например, 58, как упоминалось в обсуждении), итоговый текст запроса может стать очень объемным. Если данных в табличных частях миллионы строк, простая выборка может занять значительное время. В таких случаях мы рекомендуем использовать временные таблицы.
Проанализируем, как это работает:
результат объединения помещается во временную таблицу ПОМЕСТИТЬ ВтВсеТовары. После этого к временной таблице можно применить дополнительные фильтры, группировки или соединения с другими справочниками. Это позволяет СУБД проиндексировать промежуточный результат и работать с ним быстрее.
Также стоит помнить об ограничениях СУБД. Например, в MS SQL Server существует ограничение на количество объединяемых таблиц в одном запросе (около 256). Для 58 документов это не критично, но если вы работаете с крайне сложной системой, возможно, придется разбивать запросы на несколько пакетов.
Подводя итоги, выделим основные правила работы с множественными табличными частями:
Соблюдение этих простых правил позволит вам писать чистый, быстрый и масштабируемый код, который будет эффективно работать даже на крупных корпоративных базах данных.