В разработке на платформе 1С часто возникает задача: найти элементы одного справочника, у которых табличная часть полностью или частично заполнена определенным набором значений из другого справочника. Например, найти все коммерческие предложения, в которых есть товары "А", "Б" и "В", причем могут быть и другие товары.
Исходный вопрос на форуме звучал так: "Предположим, есть справочник1, есть справочник2. У элемента справочник1 есть тч, состоящая из элементов справочник2. Как написать условие запроса, чтобы отобранный элемент справочник1 содержал в своей тч все значения из переданного в запрос списка значений?".
Эта, на первый взгляд, простая задача имеет важные нюансы. В ходе обсуждения выяснилось, что фразу "содержал все значения" можно трактовать по-разному:
Рассмотрим, как решить обе эти задачи с помощью языка запросов 1С.
Это наиболее частый случай. Нам нужно убедиться, что все искомые элементы присутствуют, а наличие "лишних" записей в табличной части нас не волнует. Классический и правильный подход для этой задачи был предложен в обсуждении и заключается в использовании группировки и условия ИМЕЮЩИЕ.
Проанализируем логику по шагам:
Справочник1.ТабличнаяЧасть) мы отбираем только те строки, которые в принципе нас интересуют — то есть те, где реквизит со ссылкой на Справочник2 присутствует в нашем списке. Для этого используем оператор В (&СписокЗначений).Справочник1. В результате для каждого элемента Справочник1 мы получаем группу строк из его табличной части, которые совпали с нашим списком.Справочник2 с помощью агрегатной функции КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ...). Если это количество равно размеру нашего исходного списка, значит, мы нашли все, что искали. Сравнение выполняется в секции ИМЕЮЩИЕ.Посмотрим на пример запроса:
Предположим, у нас есть параметр запроса &СписокЗначений (тип СписокЗначений или Массив) и параметр &РазмерСпискаЗначений, в который мы заранее передаем количество элементов в этом списке.
ВЫБРАТЬ
ТЧ.Ссылка КАК СсылкаНаСправочник1
ИЗ
Справочник.Справочник1.ТабличнаяЧасть КАК ТЧ
ГДЕ
ТЧ.РеквизитСправочник2 В (&СписокЗначений)
СГРУППИРОВАТЬ ПО
ТЧ.Ссылка
ИМЕЮЩИЕ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТЧ.РеквизитСправочник2) = &РазмерСпискаЗначений
Разберем его подробнее:
ГДЕ ТЧ.РеквизитСправочник2 В (&СписокЗначений): Этот фильтр — первый шаг оптимизации. Он сразу отсекает все строки, которые точно не входят в наш список, уменьшая объем данных для дальнейшей группировки.СГРУППИРОВАТЬ ПО ТЧ.Ссылка: Объединяет все отобранные строки по их "родителю" — элементу Справочник1.ИМЕЮЩИЕ КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТЧ.РеквизитСправочник2) = &РазмерСпискаЗначений: Это сердце запроса. Ключевое слово РАЗЛИЧНЫЕ защищает нас от дублей как в табличной части, так и в самом списке. Если в списке [А, Б, В] (размер 3), а в табличной части есть строки [А, А, Б, В, Г], то после фильтрации по списку останутся [А, А, Б, В]. Количество различных в этой группе будет равно 3 (А, Б, В), что совпадает с размером списка. Элемент будет отобран.Важно: в параметр &РазмерСпискаЗначений нужно передавать количество именно уникальных элементов исходного списка, если в нем могут быть дубликаты. Проще всего перед выполнением запроса подготовить массив уникальных значений.
Этот сценарий, как отметили на форуме, соответствует названию темы "Полное соответствие списку значений". Здесь нам нужно найти элементы Справочник1, у которых табличная часть содержит только элементы из нашего списка, и при этом их общее количество совпадает.
Для решения этой задачи потребуется более сложный запрос с двумя проверками в условии ИМЕЮЩИЕ.
Логика здесь двойная:
Справочник1 равно размеру нашего списка. Это отсекает все элементы, где есть лишние строки.Посмотрим на пример запроса:
ВЫБРАТЬ
ТЧ.Ссылка КАК СсылкаНаСправочник1
ИЗ
Справочник.Справочник1.ТабличнаяЧасть КАК ТЧ
СГРУППИРОВАТЬ ПО
ТЧ.Ссылка
ИМЕЮЩИЕ
КОЛИЧЕСТВО(ТЧ.РеквизитСправочник2) = &РазмерСпискаЗначений
И МИНИМУМ(ВЫБОР
КОГДА ТЧ.РеквизитСправочник2 В (&СписокЗначений)
ТОГДА 1
ИНАЧЕ 0
КОНЕЦ) = 1
Как это работает:
В этом запросе мы убрали условие ГДЕ и проверяем все уже после группировки.
КОЛИЧЕСТВО(ТЧ.РеквизитСправочник2) = &РазмерСпискаЗначений: Первое условие в ИМЕЮЩИЕ. Оно простое: сравниваем общее число строк в табличной части (без РАЗЛИЧНЫЕ!) с размером нашего списка. Если в ТЧ 5 строк, а в списке 3, этот элемент сразу отбрасывается.И МИНИМУМ(...) = 1: Второе, более хитрое условие. Разберем его изнутри:
ВЫБОР ... КОНЕЦ выполняется для каждой строки внутри сгруппированной группы.КОГДА ТЧ.РеквизитСправочник2 В (&СписокЗначений) ТОГДА 1 ИНАЧЕ 0: Если значение в строке есть в нашем списке, конструкция вернет 1 (аналог ИСТИНА). Если нет — вернет 0 (аналог ЛОЖЬ).МИНИМУМ() применяется ко всему набору единиц и нулей, полученных от конструкции ВЫБОР для группы. МИНИМУМ вернет 1 только в том случае, если абсолютно все строки вернули 1. Если хотя бы одна строка не найдется в списке и вернет 0, то и минимальное значение для всей группы станет 0.Таким образом, комбинация этих двух условий гарантирует, что в табличной части ровно столько строк, сколько элементов в списке, и все эти строки без исключения присутствуют в этом списке.
Мы рассмотрели два подхода к решению задачи отбора по содержимому табличной части. Выбор конкретного решения зависит от бизнес-требований.
ГДЕ, группировкой и подсчетом КОЛИЧЕСТВО(РАЗЛИЧНЫЕ).ИМЕЮЩИЕ: на общее количество и на вхождение каждой строки через МИНИМУМ(ВЫБОР...).Стоит также помнить о дубликатах. Если в бизнес-задаче важно, чтобы и количество дублей в табличной части совпадало с количеством дублей в исходном списке (например, в списке [ТоварА, ТоварА, ТоварБ], и в ТЧ должно быть ровно два "ТовараА" и один "ТоварБ"), то задача значительно усложняется. Такие проблемы обычно решаются через временные таблицы с нумерацией строк и их последующим сложным соединением, что выходит за рамки стандартных группировок.