При разработке отчетов, обработок и механизмов переноса данных в 1С нередко возникает необходимость отобрать только те документы, у которых табличная часть заполнена ровно одной строкой. Например, это требуется при анализе одиночных продаж, упрощенной обработке документов или поиске некорректно заполненных данных. Разберем по шагам, как решить эту задачу стандартными средствами языка запросов 1С, а также рассмотрим оптимизированные варианты для высоконагруженных систем.
Простейший и наиболее очевидный способ заключается в обращении напрямую к табличной части документа с последующей группировкой по ссылке. Для фильтрации результата по количеству строк используется секция ИМЕЮЩИЕ.
Рассмотрим синтаксис и логику такого запроса:
Документ.РеализацияТоваровУслуг.Товары).Ссылка.Ссылка с помощью конструкции СГРУППИРОВАТЬ ПО.ИМЕЮЩИЕ КОЛИЧЕСТВО(*) = 1.Посмотрим на пример текста запроса:
ВЫБРАТЬ
Товары.Ссылка КАК Ссылка
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Товары
СГРУППИРОВАТЬ ПО
Товары.Ссылка
ИМЕЮЩИЕ
КОЛИЧЕСТВО(*) = 1
Разберем важные нюансы этого запроса:
КОЛИЧЕСТВО(*) подсчитывает общее число строк в группе. Не требуется дополнительно указывать КОЛИЧЕСТВО(РАЗЛИЧНЫЕ НомерСтроки), так как в рамках одной табличной части номера строк и так уникальны. Использование ключевого слова РАЗЛИЧНЫЕ создает лишнюю нагрузку на СУБД из-за дополнительной сортировки записей.ИМЕЮЩИЕ конструкцией ВЫБОР КОГДА КОЛИЧЕСТВО(...) = 1 ТОГДА ... ИНАЧЕ NULL в списке полей. Такой подход вернет в результат миллионы строк со значением NULL для всех неподходящих документов, что приведет к перерасходу памяти и снижению скорости работы.Если табличная часть содержит миллионы записей, полное сканирование таблицы с группировкой и вычислением агрегатных функций может выполняться медленно. Проанализируем, как работает кластерный индекс табличной части в платформе 1С: он строится по полям (Ссылка, НомерСтроки).
Это дает возможность выполнить точечную выборку без тяжелой группировки. Мы можем выбрать все строки с НомерСтроки = 1 и левым соединением проверить, что строка с НомерСтроки = 2 отсутствует в документе.
Посмотрим на пример реализации:
ВЫБРАТЬ
ТЧ1.Ссылка КАК Ссылка
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК ТЧ1
ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Товары КАК ТЧ2
ПО ТЧ1.Ссылка = ТЧ2.Ссылка
И ТЧ2.НомерСтроки = 2
ГДЕ
ТЧ1.НомерСтроки = 1
И ТЧ2.Ссылка ЕСТЬ NULL
Выясним причину высокой эффективности этого метода:
GROUP BY по всей таблице.НомерСтроки = 2), он автоматически отсеивается условием ТЧ2.Ссылка ЕСТЬ NULL.Если нам требуется отобрать документы не за всю историю базы данных, а за конкретный период или по определенной организации, прямое обращение к табличной части вызовет сканирование всей таблицы. Рассмотрим правильную структуру пакета запросов с временными таблицами.
Разберем по шагам построение запроса:
Ссылка.Посмотрим на пример пакета запросов:
ВЫБРАТЬ
ДокументШапка.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_ОтфильтрованныеДокументы
ИЗ
Документ.РеализацияТоваровУслуг КАК ДокументШапка
ГДЕ
ДокументШапка.Проведен = ИСТИНА
И ДокументШапка.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
И ДокументШапка.Организация = &Организация
ИНДЕКСИРОВАТЬ ПО
Ссылка
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ.Ссылка КАК Ссылка
ИЗ
ВТ_ОтфильтрованныеДокументы КАК ВТ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Товары КАК Товары
ПО ВТ.Ссылка = Товары.Ссылка
СГРУППИРОВАТЬ ПО
ВТ.Ссылка
ИМЕЮЩИЕ
КОЛИЧЕСТВО(*) = 1
В типовых конфигурациях документы часто содержат раздельные табличные части (например, Товары и Услуги). Если бизнес-логика требует, чтобы суммарно во всем документе была ровно одна строка, объединим выборки перед группировкой.
Рассмотрим пример запроса с конструкцией ОБЪЕДИНИТЬ ВСЕ:
ВЫБРАТЬ
ВсеСтроки.Ссылка КАК Ссылка
ИЗ
(ВЫБРАТЬ
Товары.Ссылка КАК Ссылка
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Товары
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Услуги.Ссылка КАК Ссылка
ИЗ
Документ.РеализацияТоваровУслуг.Услуги КАК Услуги) КАК ВсеСтроки
СГРУППИРОВАТЬ ПО
ВсеСтроки.Ссылка
ИМЕЮЩИЕ
КОЛИЧЕСТВО(*) = 1
Если объект документа уже получен в контексте выполнения программного кода (например, в модуле формы или при проведении в переменной ЭтотОбъект / ДокументОбъект), выполнять отдельный запрос к базе данных не требуется. Достаточно обратиться к свойству коллекции табличной части.
Посмотрим на пример кода:
Если ДокументОбъект.Товары.Количество() = 1 Тогда
// Выполняем действия для документа с одной строкой
КонецЕсли;
Важно: Если у вас есть только переменная типа ДокументСсылка, не следует считывать весь объект целиком через Ссылка.ПолучитьОбъект() только ради проверки количества строк — для ссылок всегда используйте точечный запрос к базе данных.