При разработке печатных форм, отчетов и алгоритмов обработки данных в платформе 1С:Предприятие часто возникает задача получить строки табличной части конкретного документа по имеющейся ссылке. Разберем типичные ошибки начинающих разработчиков при фильтрации табличных частей, выясним, почему нельзя выполнять запросы в цикле, и рассмотрим оптимальные варианты выборки данных из связанных документов.
Табличная часть любого объекта конфигурации (документа, справочника) хранится в базе данных как отдельная таблица. В этой таблице всегда присутствует обязательное системное поле Ссылка, которое содержит ссылку на сам документ-владелец. Строка табличной части самостоятельной ссылки не имеет.
Для выборки строк конкретного документа необходимо установить условие в секции ГДЕ, передав ссылку на документ в качестве параметра:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказПокупателяЗапасы.НомерСтроки КАК НомерСтроки,
| ЗаказПокупателяЗапасы.Номенклатура КАК Номенклатура,
| ЗаказПокупателяЗапасы.Цена КАК Цена,
| ЗаказПокупателяЗапасы.Количество КАК Количество,
| ЗаказПокупателяЗапасы.Сумма КАК Сумма
|ИЗ
| Документ.ЗаказПокупателя.Запасы КАК ЗаказПокупателяЗапасы
|ГДЕ
| ЗаказПокупателяЗапасы.Ссылка = &Ссылка
|УПОРЯДОЧИТЬ ПО
| ЗаказПокупателяЗапасы.НомерСтроки";
Запрос.УстановитьПараметр("Ссылка", ДокументСсылка);
РезультатЗапроса = Запрос.Выполнить();
Важные нюансы при написании запроса:
ЗаказПокупателяЗапасы.Ссылка.Ссылка является ошибочной и избыточной. Достаточно использовать ЗаказПокупателяЗапасы.Ссылка.УПОРЯДОЧИТЬ ПО [ИмяТабличнойЧасти].НомерСтроки.Номенклатура.Наименование), так как это создает дополнительное неявное соединение с таблицей справочника. Оптимальнее использовать функцию ПРЕДСТАВЛЕНИЕ(ЗаказПокупателяЗапасы.Номенклатура) КАК НоменклатураПредставление.Проанализируем частую архитектурную ошибку: когда требуется найти подчиненные документы (например, «Заказ-наряды», привязанные к документу-основанию) и получить их табличные части («Запасы» и «Работы»), разработчик первым запросом находит ссылки на документы, а затем в цикле обхода выборки выполняет новые запросы для каждого документа.
Выполнение запросов в цикле (антипаттерн «N+1») приводит к серьезным проблемам с производительностью:
Выполнить() создает отдельный сетевой раунд-трип между клиентом/сервером 1С и СУБД.Рассмотрим два правильных подхода к решению задачи, когда нужно получить данные табличных частей по списку связанных документов за одно обращение к СУБД.
Если структуры табличных частей совместимы и их требуется вывести единым списком, используем оператор ОБЪЕДИНИТЬ ВСЕ с прямой фильтрацией по полю документа-основания:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПРЕДСТАВЛЕНИЕ(ЗаказПокупателяЗапасы.Номенклатура) КАК Номенклатура,
| ЗаказПокупателяЗапасы.Сумма КАК Сумма,
| ЗаказПокупателяЗапасы.НомерСтроки КАК НомерСтроки
|ИЗ
| Документ.ЗаказПокупателя.Запасы КАК ЗаказПокупателяЗапасы
|ГДЕ
| ЗаказПокупателяЗапасы.Ссылка.ДокументОснование = &ДокументОснование
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ПРЕДСТАВЛЕНИЕ(ЗаказПокупателяРаботы.Номенклатура),
| ЗаказПокупателяРаботы.Сумма,
| ЗаказПокупателяРаботы.НомерСтроки
|ИЗ
| Документ.ЗаказПокупателя.Работы КАК ЗаказПокупателяРаботы
|ГДЕ
| ЗаказПокупателяРаботы.Ссылка.ДокументОснование = &ДокументОснование
|
|УПОРЯДОЧИТЬ ПО
| НомерСтроки";
Запрос.УстановитьПараметр("ДокументОснование", СсылкаНаОснование);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ОбластьСтрока.Параметры.Номенклатура = Выборка.Номенклатура;
ОбластьСтрока.Параметры.Сумма = Выборка.Сумма;
ТабличныйДокумент.Вывести(ОбластьСтрока);
КонецЦикла;
Обратите внимание на составные типы: если реквизит ДокументОснование имеет составной тип данных, обращение через точку Ссылка.ДокументОснование на уровне СУБД приведет к созданию множественных соединений (`LEFT JOIN`) со всеми возможными таблицами документов. Чтобы исключить падение производительности, уточняйте тип через функцию ВЫРАЗИТЬ:
ГДЕ
ВЫРАЗИТЬ(ЗаказПокупателяЗапасы.Ссылка.ДокументОснование КАК Документ.ПриемИПередачаВРемонт) = &ДокументОснование
Наиболее масштабируемый и универсальный подход — использование пакетного запроса. Сначала отбираем список ссылок на подчиненные документы во временную таблицу, а затем соединяем с ней нужные табличные части:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказПокупателя.Ссылка КАК Ссылка
|ПОМЕСТИТЬ ВТ_ПодчиненныеЗаказы
|ИЗ
| Документ.ЗаказПокупателя КАК ЗаказПокупателя
|ГДЕ
| ЗаказПокупателя.ДокументОснование = &ДокументОснование
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ПРЕДСТАВЛЕНИЕ(Запасы.Номенклатура) КАК Номенклатура,
| Запасы.Сумма КАК Сумма
|ИЗ
| Документ.ЗаказПокупателя.Запасы КАК Запасы
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ПодчиненныеЗаказы КАК ВТ_ПодчиненныеЗаказы
| ПО Запасы.Ссылка = ВТ_ПодчиненныеЗаказы.Ссылка
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ПРЕДСТАВЛЕНИЕ(Работы.Номенклатура) КАК Номенклатура,
| Работы.Сумма КАК Сумма
|ИЗ
| Документ.ЗаказПокупателя.Работы КАК Работы
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ПодчиненныеЗаказы КАК ВТ_ПодчиненныеЗаказы
| ПО Работы.Ссылка = ВТ_ПодчиненныеЗаказы.Ссылка";
Запрос.УстановитьПараметр("ДокументОснование", СсылкаНаОснование);
// Выполняем весь пакет за одно обращение к СУБД
МассивРезультатов = Запрос.ВыполнитьПакет();
// МассивРезультатов[1] содержит Запасы, МассивРезультатов[2] — Работы
ВыборкаЗапасы = МассивРезультатов[1].Выбрать();
Пока ВыборкаЗапасы.Следующий() Цикл
// Обработка запасов
КонецЦикла;
ВыборкаРаботы = МассивРезультатов[2].Выбрать();
Пока ВыборкаРаботы.Следующий() Цикл
// Обработка работ
КонецЦикла;
Такой подход полностью устраняет выполнение запросов в цикле, разделяет логику обработки разных табличных частей и обеспечивает максимальное быстродействие при формировании отчетов и печатных форм.