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

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

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

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

Антипаттерн: Ручная фильтрация в цикле

Для начала давайте посмотрим, как делать не нужно. Очень часто начинающие разработчики пытаются решить эту проблему "в лоб" на уровне кода перебора выборки, сохраняя ссылку на предыдущий обработанный документ в переменную. Выглядит это примерно так:


НомерПредыдущегоДокумента = Неопределено;
Если Не Рез.Пустой() Тогда
    Выборка = Рез.Выбрать();
    Пока Выборка.Следующий() Цикл
        Если НомерПредыдущегоДокумента = Неопределено ИЛИ Выборка.Номер <> НомерПредыдущегоДокумента Тогда
            Сообщить("Отправляется " + Выборка.Номер);
            НомерПредыдущегоДокумента = Выборка.Номер;
        КонецЕсли;
    КонецЦикла;        
КонецЕсли;

Почему этот подход считается признаком плохой архитектуры? Проблема кроется в избыточности получаемых данных. Запрос вытаскивает из базы данных, передает по сети на сервер 1С и помещает в оперативную память все дублирующиеся строки. Если документов тысячи, а строк в них десятки тысяч, такая выборка создаст огромную и совершенно бессмысленную нагрузку на систему. Все фильтрации, отсечения дублей и сортировки всегда должны выполняться на стороне СУБД средствами языка запросов.

Решение 1: Использование ключевого слова "РАЗЛИЧНЫЕ"

Самый простой способ избавиться от дублей прямо в запросе — использовать ключевое слово РАЗЛИЧНЫЕ (аналог DISTINCT в SQL). Этот метод идеально подходит, если в результате запроса вам нужны только реквизиты шапки документа (Ссылка, Номер, Дата и т.д.), и вам не нужны данные самих строк табличной части, по которым производился отбор.

Рассмотрим пример запроса:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    ТЧ_ОтметкиСклада.Ссылка КАК Ссылка,
    ТЧ_ОтметкиСклада.Ссылка.Номер КАК Номер,
    ТЧ_ОтметкиСклада.Ссылка.Дата КАК Дата
ИЗ
    Документ.ПредварительныйЗаказПоставщику.ОтметкиСклада КАК ТЧ_ОтметкиСклада
ГДЕ
    ТЧ_ОтметкиСклада.Сертификат = ЛОЖЬ
    И ТЧ_ОтметкиСклада.ПаспортИзделия = ЛОЖЬ

При использовании РАЗЛИЧНЫЕ система проанализирует все полученные строки и "схлопнет" полностью идентичные записи в одну. В итоге, даже если в документе 10 строк без сертификата, в выборку попадет только одна запись с данным документом.

Решение 2: Оптимальная архитектура — вложенный запрос с оператором "В"

С точки зрения оптимизации платформы 1С и чистоты кода, наиболее правильным подходом является разделение сущностей. Основной запрос должен обращаться к таблице документов, а фильтрация по табличной части выносится во вложенный запрос с использованием оператора В.

Посмотрим на пример такого запроса:


ВЫБРАТЬ 
    ДокументШапка.Ссылка КАК Ссылка, 
    ДокументШапка.Номер КАК Номер
ИЗ 
    Документ.ПредварительныйЗаказПоставщику КАК ДокументШапка
ГДЕ 
    ДокументШапка.Ссылка В (
        ВЫБРАТЬ 
            ТЧ.Ссылка 
        ИЗ 
            Документ.ПредварительныйЗаказПоставщику.ОтметкиСклада КАК ТЧ 
        ГДЕ 
            ТЧ.Сертификат = ЛОЖЬ 
            И ТЧ.ПаспортИзделия = ЛОЖЬ
    )

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

Решение 3: Группировка с итогами (если нужны и документы, и их строки)

Иногда возникает усложненная задача: нам нужно обойти каждый подходящий документ один раз (например, для формирования письма), но внутри этого обхода нам необходимо получить доступ к конкретным строкам табличной части, которые подошли под условие (чтобы перечислить проблемные товары в этом письме).

В этом случае методы с РАЗЛИЧНЫЕ или вложенным запросом не подойдут, так как они отсекают данные строк. Здесь нам на помощь приходит механизм итогов и специальный обход результата запроса.

В конец исходного запроса к табличной части необходимо добавить конструкцию ИТОГИ ПО:


ВЫБРАТЬ
    ТЧ.Ссылка КАК Ссылка,
    ТЧ.ДатаПоступления КАК ДатаПоступления,
    ТЧ.Сертификат КАК Сертификат
ИЗ
    Документ.ПредварительныйЗаказПоставщику.ОтметкиСклада КАК ТЧ
ГДЕ
    ТЧ.Сертификат = ЛОЖЬ

ИТОГИ ПО
    Ссылка

Теперь мы изменим код обхода результата запроса. Мы будем использовать двойной цикл и метод ОбходРезультатаЗапроса.ПоГруппировкам. Проанализируем этот код:


Рез = Запрос.Выполнить();
Если Не Рез.Пустой() Тогда
    // Получаем выборку верхнего уровня (уникальные документы)
    ВыборкаДокумент = Рез.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
    
    Пока ВыборкаДокумент.Следующий() Цикл
        // Этот блок кода выполнится ровно ОДИН РАЗ для каждого документа
        Сообщить("Обрабатываем документ: " + ВыборкаДокумент.Ссылка);
        
        // Получаем детальные записи для текущего документа
        ВыборкаДетали = ВыборкаДокумент.Выбрать();
        
        Пока ВыборкаДетали.Следующий() Цикл
            // Этот блок выполнится для каждой подходящей строки ТЧ документа
            Сообщить(" -- Строка с датой поступления: " + ВыборкаДетали.ДатаПоступления);
        КонецЦикла;
        
    КонецЦикла;
КонецЕсли;

Этот элегантный паттерн позволяет убить сразу двух зайцев: вы получаете возможность обработать документ единожды на уровне группировки и сохраняете доступ ко всем детальным записям табличной части во вложенном цикле. Выбор метода зависит исключительно от ваших конечных целей: нужны только документы — используйте подзапрос с условием В; нужны и документы, и детализированные строки — используйте ИТОГИ ПО.

← На главную