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

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

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

1. Фильтрация строк табличной части конкретного документа

Табличная часть любого объекта конфигурации (документа, справочника) хранится в базе данных как отдельная таблица. В этой таблице всегда присутствует обязательное системное поле Ссылка, которое содержит ссылку на сам документ-владелец. Строка табличной части самостоятельной ссылки не имеет.

Для выборки строк конкретного документа необходимо установить условие в секции ГДЕ, передав ссылку на документ в качестве параметра:


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

Запрос.УстановитьПараметр("Ссылка", ДокументСсылка);
РезультатЗапроса = Запрос.Выполнить();

Важные нюансы при написании запроса:

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

2. Почему нельзя использовать запросы в цикле?

Проанализируем частую архитектурную ошибку: когда требуется найти подчиненные документы (например, «Заказ-наряды», привязанные к документу-основанию) и получить их табличные части («Запасы» и «Работы»), разработчик первым запросом находит ссылки на документы, а затем в цикле обхода выборки выполняет новые запросы для каждого документа.

Выполнение запросов в цикле (антипаттерн «N+1») приводит к серьезным проблемам с производительностью:

  1. Каждый вызов метода Выполнить() создает отдельный сетевой раунд-трип между клиентом/сервером 1С и СУБД.
  2. СУБД вынуждена многократно парсить, компилировать и строить план выполнения одного и того же запроса.
  3. На больших объемах данных печать или проведение документов начинают занимать недопустимо много времени.

3. Оптимальные варианты выборки связанных данных

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

Вариант А. Единый запрос с объединением таблиц

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


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

Запрос.УстановитьПараметр("ДокументОснование", СсылкаНаОснование);
Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    ОбластьСтрока.Параметры.Номенклатура = Выборка.Номенклатура;
    ОбластьСтрока.Параметры.Сумма = Выборка.Сумма;
    ТабличныйДокумент.Вывести(ОбластьСтрока);
КонецЦикла;

Обратите внимание на составные типы: если реквизит ДокументОснование имеет составной тип данных, обращение через точку Ссылка.ДокументОснование на уровне СУБД приведет к созданию множественных соединений (`LEFT JOIN`) со всеми возможными таблицами документов. Чтобы исключить падение производительности, уточняйте тип через функцию ВЫРАЗИТЬ:


ГДЕ
	ВЫРАЗИТЬ(ЗаказПокупателяЗапасы.Ссылка.ДокументОснование КАК Документ.ПриемИПередачаВРемонт) = &ДокументОснование

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

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


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

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

// Выполняем весь пакет за одно обращение к СУБД
МассивРезультатов = Запрос.ВыполнитьПакет();

// МассивРезультатов[1] содержит Запасы, МассивРезультатов[2] — Работы
ВыборкаЗапасы = МассивРезультатов[1].Выбрать();
Пока ВыборкаЗапасы.Следующий() Цикл
    // Обработка запасов
КонецЦикла;

ВыборкаРаботы = МассивРезультатов[2].Выбрать();
Пока ВыборкаРаботы.Следующий() Цикл
    // Обработка работ
КонецЦикла;

Такой подход полностью устраняет выполнение запросов в цикле, разделяет логику обработки разных табличных частей и обеспечивает максимальное быстродействие при формировании отчетов и печатных форм.

← На главную