В практике разработчика 1С часто возникает задача фильтрации документов не по заголовку, а по содержимому их табличных частей. Типичный пример: нужно найти документы, которые стали «неактуальными» из-за того, что все указанные в них объекты (например, сотрудники или номенклатура) помечены на удаление. Особенность задачи в том, что нам не подходят документы, где помечена только часть сотрудников — нам нужны только те, где каждый сотрудник в списке имеет признак ПометкаУдаления.
В этой статье мы разберем несколько эффективных способов реализации такого отбора, проанализируем логику работы агрегатных функций с типом Булево и рассмотрим вопросы производительности запросов.
Это самый лаконичный и быстрый способ. В языке запросов 1С значения типа Булево при сравнении и агрегации ведут себя подобно числам: Ложь трактуется как 0, а Истина — как 1. Следовательно, Ложь всегда меньше, чем Истина.
Проанализируем ситуацию: если в табличной части хотя бы один сотрудник НЕ помечен на удаление (его ПометкаУдаления = Ложь), то минимальное значение этого поля по всем строкам документа будет равно Ложь. Если же абсолютно все сотрудники помечены, то и минимальным, и максимальным значением будет Истина.
Рассмотрим пример реализации такого запроса:
ВЫБРАТЬ
ВозвратЗадолженности.Ссылка КАК Ссылка
ИЗ
Документ.ВозвратСотрудникомЗадолженности.Задолженности КАК ВозвратЗадолженности
СГРУППИРОВАТЬ ПО
ВозвратЗадолженности.Ссылка
ИМЕЮЩИЕ
МИНИМУМ(ВозвратЗадолженности.Сотрудник.ПометкаУдаления) = ИСТИНА
Почему это работает? Рассмотрим математическую модель. Допустим, в документе 3 строки:
Функция МИНИМУМ(1, 0, 1) вернет 0 (Ложь). Документ не попадет в выборку. Если же во всех строках будет 1, то МИНИМУМ(1, 1, 1) вернет 1 (Истина), и условие ИМЕЮЩИЕ будет выполнено.
Этот метод считается более универсальным и «читаемым» для многих разработчиков, так как он явно переводит логику в числовой формат. Мы присваиваем каждой строке вес: 0, если условие соблюдено, и 1, если нет. Если сумма весов по документу равна 0, значит, «плохих» строк в нем нет.
Посмотрим на пример кода:
ВЫБРАТЬ
ТабЧасть.Ссылка КАК Ссылка
ИЗ
Документ.ВозвратСотрудникомЗадолженности.Задолженности КАК ТабЧасть
СГРУППИРОВАТЬ ПО
Ссылка
ИМЕЮЩИЕ
СУММА(ВЫБОР
КОГДА ТабЧасть.Сотрудник.ПометкаУдаления = ЛОЖЬ
ТОГДА 1
ИНАЧЕ 0
КОНЕЦ) = 0
Разберем, что здесь происходит. Мы проходим по каждой строке табличной части. Если мы встречаем сотрудника без пометки удаления, мы прибавляем к сумме единицу. Если в итоге сумма осталась равной нулю, значит, ни одного «живого» сотрудника в документе не нашлось. Этот метод удобен тем, что условие внутри ВЫБОР можно легко усложнять, добавляя проверки на пустые ссылки или другие реквизиты.
Еще один надежный вариант — посчитать, сколько всего строк в табличной части документа, и сравнить это число с количеством строк, которые удовлетворяют нашему условию. Если эти числа совпадают, значит, условие выполняется для всех строк без исключения.
ВЫБРАТЬ
ТЧ.Ссылка КАК Ссылка,
СУММА(ВЫБОР
КОГДА ТЧ.Сотрудник.ПометкаУдаления
ТОГДА 1
ИНАЧЕ 0
КОНЕЦ) КАК КолвоПомеченных,
КОЛИЧЕСТВО(ТЧ.НомерСтроки) КАК ОбщееКолвоСтрок
ПОМЕСТИТЬ ВТ_Статистика
ИЗ
Документ.ВозвратСотрудникомЗадолженности.Задолженности КАК ТЧ
СГРУППИРОВАТЬ ПО
ТЧ.Ссылка;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Статистика.Ссылка
ИЗ
ВТ_Статистика КАК ВТ_Статистика
ГДЕ
ВТ_Статистика.КолвоПомеченных = ВТ_Статистика.ОбщееКолвоСтрок
И ВТ_Статистика.ОбщееКолвоСтрок > 0
Обратите внимание на условие ОбщееКолвоСтрок > 0. Это важный нюанс: если в документе вообще нет строк в табличной части, его статус может быть неопределенным с точки зрения бизнес-логики. Данный запрос позволяет четко отсечь пустые документы.
С точки зрения производительности СУБД (особенно на больших объемах данных), операции группировки и агрегации (SUM, MIN) могут быть достаточно «тяжелыми». Иногда эффективнее применить метод исключения: найти все документы, где есть хотя бы один непомеченный сотрудник, и исключить их из общего списка.
ВЫБРАТЬ
ДокументОбъект.Ссылка
ИЗ
Документ.ВозвратСотрудникомЗадолженности КАК ДокументОбъект
ГДЕ
ДокументОбъект.Ссылка В
(ВЫБРАТЬ РАЗЛИЧНЫЕ
ТЧ.Ссылка
ИЗ
Документ.ВозвратСотрудникомЗадолженности.Задолженности КАК ТЧ)
И НЕ ДокументОбъект.Ссылка В
(ВЫБРАТЬ РАЗЛИЧНЫЕ
ТЧ.Ссылка
ИЗ
Документ.ВозвратСотрудникомЗадолженности.Задолженности КАК ТЧ
ГДЕ
ТЧ.Сотрудник.ПометкаУдаления = ЛОЖЬ)
Проанализируем этот подход. Первый подзапрос гарантирует, что в документе есть хотя бы одна строка (он не пустой). Второй подзапрос находит все «неподходящие» нам документы. Оператор НЕ В отсекает их. В некоторых сценариях такой запрос отработает быстрее, так как СУБД может использовать индексы по полю Ссылка эффективнее, чем при группировке всей таблицы.
При написании таких запросов следует помнить о нескольких технических моментах:
ТЧ.Сотрудник.ПометкаУдаления вызывает неявное левое соединение (LEFT JOIN) с таблицей справочника Сотрудники. Если табличная часть содержит тысячи строк, это может привести к падению производительности. В высоконагруженных системах лучше сначала отобрать помеченных сотрудников во временную таблицу, а затем соединять её с табличной частью документа.Сотрудник не заполнено, обращение к его пометке удаления вернет NULL. Агрегатные функции игнорируют NULL, что может исказить результат. Всегда полезно добавлять проверку на ЗНАЧЕНИЕ(Справочник.Сотрудники.ПустаяСсылка).Документ.Имя), а к табличной части присоединяйтесь через ЛЕВОЕ СОЕДИНЕНИЕ.Подводя итог, можно сказать, что выбор конкретного метода зависит от объема данных и требований к чистоте выборки (нужны ли пустые документы). Для большинства повседневных задач метод с МИНИМУМ() является оптимальным по соотношению краткости и эффективности.