Как выбрать элемент справочника и его табличную часть одним запросом без использования циклов?

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

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

Проблема "Запроса в цикле" и почему так делать нельзя

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


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

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

Пока Выборка.Следующий() Цикл
    // Это критическая ошибка: запрос внутри цикла!
    ЗапросТЧ = Новый Запрос;
    ЗапросТЧ.Текст =
    "ВЫБРАТЬ
    |    КонтрагентыСостав.GUID_1,
    |    КонтрагентыСостав.GUID_2
    |ИЗ
    |    Справочник.Контрагенты.Состав КАК КонтрагентыСостав
    |ГДЕ
    |    КонтрагентыСостав.Ссылка = &Ссылка";
    
    ЗапросТЧ.УстановитьПараметр("Ссылка", Выборка.Ссылка);
    ВыборкаТЧ = ЗапросТЧ.Выполнить().Выбрать();
    
    // Дальнейшая обработка...
КонецЦикла;

Проанализируем ситуацию: если в справочнике 1000 элементов, платформа сделает 1001 обращение к базе данных. Это создает огромную нагрузку на сетевой трафик и сервер СУБД. Наша цель — выполнить ровно один запрос и получить все необходимые данные сразу.

Решение через использование конструкции ИТОГИ ПО

Самый эффективный и "чистый" способ в 1С — это использование предложения ИТОГИ в тексте запроса. Это позволяет платформе самостоятельно сгруппировать данные табличной части по родительской ссылке. Рассмотрим, как составить такой запрос и, что не менее важно, как правильно обойти его результаты.

Разберем по шагам правильный алгоритм:

  1. Выбираем необходимые поля из таблицы состава (табличной части).
  2. Обязательно выбираем поле Ссылка, чтобы понимать, к какому объекту относятся строки.
  3. Используем конструкцию ИТОГИ ПО Ссылка.
  4. При обходе результата используем метод Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам).

Посмотрим на реализацию этого метода в коде:


Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Состав.Ссылка КАК Контрагент,
|    Состав.GUID_1 КАК GUID_1,
|    Состав.GUID_2 КАК GUID_2,
|    Состав.GUID_3 КАК GUID_3,
|    Состав.GUID_4 КАК GUID_4
|ИЗ
|    Справочник.Контрагенты.Состав КАК Состав
|УПОРЯДОЧИТЬ ПО
|    Контрагент
|ИТОГИ ПО
|    Контрагент";

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

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

Пока ВыборкаКонтрагент.Следующий() Цикл
    
    // Здесь мы находимся на уровне "шапки" (самого элемента справочника)
    Сообщить("Обработка контрагента: " + ВыборкаКонтрагент.Контрагент);
    
    // Теперь выбираем детальные записи (строки табличной части) для этого контрагента
    ВыборкаСтрок = ВыборкаКонтрагент.Выбрать();
    
    Пока ВыборкаСтрок.Следующий() Цикл
        // Здесь мы обрабатываем конкретные строки табличной части
        Если ЗначениеЗаполнено(ВыборкаСтрок.GUID_1) Тогда
            // Логика обработки GUID...
        КонецЕсли;
    КонецЦикла;
    
КонецЦикла;

Альтернативный метод: Агрегация данных прямо в запросе

Выясним причину, по которой иногда даже обход группировок может быть избыточным. Если ваша задача — просто найти первое или максимальное заполненное значение в табличной части и записать его в реквизиты самого объекта, можно воспользоваться группировкой с агрегатными функциями. Это еще быстрее, так как объем передаваемых данных сокращается до одной строки на один объект.

Проанализируем пример, где нам нужно получить максимальные значения полей из ТЧ:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|    Состав.Ссылка КАК Ссылка,
|    МАКСИМУМ(Состав.GUID_1) КАК GUID_1,
|    МАКСИМУМ(Состав.GUID_2) КАК GUID_2
|ИЗ
|    Справочник.Контрагенты.Состав КАК Состав
|СГРУППИРОВАТЬ ПО
|    Состав.Ссылка";

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    Объект = Выборка.Ссылка.ПолучитьОбъект();
    Объект.GUID_1 = Выборка.GUID_1;
    Объект.Записать();
КонецЦикла;

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

Даже если вы оптимизировали чтение данных одним запросом, запись объектов в цикле может оставаться медленной. Рассмотрим дополнительные приемы, которые помогут ускорить работу обработки:

  1. Использование транзакций: Оберните весь цикл записи в одну транзакцию. Это позволит СУБД не фиксировать каждое изменение на диске по отдельности, а сделать это одним пакетом. Используйте НачатьТранзакцию() перед циклом и ЗафиксироватьТранзакцию() после.
  2. Режим загрузки: Если при записи GUID вам не нужно выполнять сложные проверки в модуле объекта (например, проверка заполненности других полей или подписки на события), используйте свойство Объект.ОбменДанными.Загрузка = Истина. Это существенно ускорит выполнение метода Записать().
  3. Временные таблицы: Если вы обрабатываете только определенный список контрагентов, сначала поместите их ссылки во временную таблицу МенеджерВременныхТаблиц, проиндексируйте её и соединяйте с таблицей состава. Это исключит полное сканирование всей таблицы состава в базе данных.

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

← На главную