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