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

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

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

Типичные ошибки при сравнении множеств в запросе

Прежде чем перейти к рабочим решениям, выясним причину частых ошибок:

  1. Ошибка предварительного отбора в секции ГДЕ: если мы пишем условие ГДЕ ТЧ.Элемент В (&Массив) и проверяем количество через ИМЕЮЩИЕ КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТЧ.Элемент) = &КоличествоМассива, запрос найдет объекты, содержащие все элементы массива, но пропустит те строки табличной части, которые не попали в массив. В итоге будут ошибочно выбраны объекты с «лишними» строками.
  2. Дубли строк в табличной части: использование функции КОЛИЧЕСТВО(*) или обращение к номеру строки через МАКСИМУМ(НомерСтроки) некорректно, если в табличной части могут присутствовать повторяющиеся значения.
  3. Дубликаты в переданном массиве: если в исходном массиве 1С есть дубли, значение Массив.Количество() вернет завышенное число. Перед передачей параметров в запрос массив необходимо очистить от дубликатов.
  4. Пустой массив параметров: передача пустого массива в оператор В (&Массив) вызывает исключение времени выполнения («Неверные параметры в операции сравнения»). Ситуацию с поиском объектов с пустой табличной частью следует обрабатывать отдельно.

Вариант 1. Оптимальный запрос с проверкой по двум агрегатным функциям

Рассмотрим наиболее лаконичный и быстрый способ решения задачи. Он не требует декартовых произведений и объединений, а базируется на анализе всей табличной части объекта с помощью секции ИМЕЮЩИЕ.

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


ВЫБРАТЬ
    ТЧ.Ссылка КАК Ссылка
ИЗ
    Справочник.ГруппыЦБ.СписокЦБ КАК ТЧ
СГРУППИРОВАТЬ ПО
    ТЧ.Ссылка
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТЧ.ЦБ) = &КоличествоЭлементов
    И СУММА(ВЫБОР
            КОГДА ТЧ.ЦБ В (&Массив)
                ТОГДА 0
            ИНАЧЕ 1
        КОНЕЦ) = 0

Посмотрим на особенности этого решения:

  1. Мы не накладываем условие ГДЕ, поэтому агрегатные функции видят все строки каждой табличной части.
  2. Конструкция ВЫБОР КОГДА ... ТОГДА 0 ИНАЧЕ 1 КОНЕЦ маркирует каждую строку: если элемент есть в массиве — 0, если элемента в массиве нет (лишний) — 1.
  3. Условие СУММА(...) = 0 гарантирует, что у найденного объекта нет ни одной лишней строки.

Вариант 2. Использование реквизита общего количества в шапке

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


ВЫБРАТЬ
    ГруппыЦБСписокЦБ.Ссылка КАК ГруппаЦБ
ИЗ
    Справочник.ГруппыЦБ.СписокЦБ КАК ГруппыЦБСписокЦБ
ГДЕ
    ГруппыЦБСписокЦБ.ЦБ В(&Массив)
    И ГруппыЦБСписокЦБ.Ссылка.КоличествоЦБ = &КоличествоЭлементов
СГРУППИРОВАТЬ ПО
    ГруппыЦБСписокЦБ.Ссылка
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ГруппыЦБСписокЦБ.ЦБ) = &КоличествоЭлементов

В данном случае условие по шапке отсекает объекты, у которых общее число элементов отличается от эталона, а условие в ГДЕ и ИМЕЮЩИЕ подтверждает, что все совпавшие элементы действительно принадлежат переданному списку.

Вариант 3. Сравнение через полное соединение таблиц

Проанализируем классический реляционный подход к сравнению двух множеств без использования условий в ИМЕЮЩИЕ. Для этого сформируем временную таблицу с эталоном и выполним полное соединение с табличной частью.


ВЫБРАТЬ
    Эталон.ЭлементОтбора КАК ЭлементОтбора
ПОМЕСТИТЬ ВТ_Эталон
ИЗ
    &ТаблицаЭталона КАК Эталон
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ТЧ.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_НеподходящиеСсылки
ИЗ
    ВТ_Эталон КАК Эталон
        ПОЛНОЕ СОЕДИНЕНИЕ Справочник.Номенклатура.Состав КАК ТЧ
        ПО (Эталон.ЭлементОтбора = ТЧ.Материал)
ГДЕ
    Эталон.ЭлементОтбора ЕСТЬ NULL
    ИЛИ ТЧ.Материал ЕСТЬ NULL
СГРУППИРОВАТЬ ПО
    ТЧ.Ссылка
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
    ТЧ.Ссылка КАК Ссылка
ИЗ
    Справочник.Номенклатура.Состав КАК ТЧ
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_НеподходящиеСсылки КАК Исключения
        ПО (ТЧ.Ссылка = Исключения.Ссылка)
ГДЕ
    Исключения.Ссылка ЕСТЬ NULL

В этом варианте сначала находятся все ссылки, у которых обнаружены несовпадения (значения NULL с любой из сторон соединения), а затем эти ссылки исключаются из результирующей выборки.

Подготовка данных на встроенном языке 1С

Перед запуском запроса обязательно выполним предварительную подготовку параметров во встроенном языке, чтобы избежать передачи дублей или пустого массива:


// Удаляем дубликаты из исходного массива с помощью объекта Соответствие
СоответствиеЭлементов = Новый Соответствие;
Для Каждого Элемент Из ИсходныйМассив Цикл
    СоответствиеЭлементов.Вставить(Элемент, Истина);
КонецЦикла;

ОчищенныйМассив = Новый Массив;
Для Каждого КлючИЗначение Из СоответствиеЭлементов Цикл
    ОчищенныйМассив.Добавить(КлючИЗначение.Ключ);
КонецЦикла;

Если ОчищенныйМассив.Количество() = 0 Тогда
    // Обработка поиска объектов с пустой табличной частью
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Спр.Ссылка КАК Ссылка
        |ИЗ
        |   Справочник.ГруппыЦБ КАК Спр
        |   ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ГруппыЦБ.СписокЦБ КАК ТЧ
        |   ПО Спр.Ссылка = ТЧ.Ссылка
        |ГДЕ
        |   ТЧ.Ссылка ЕСТЬ NULL";
Иначе
    Запрос.УстановитьПараметр("Массив", ОчищенныйМассив);
    Запрос.УстановитьПараметр("КоличествоЭлементов", ОчищенныйМассив.Количество());
КонецЕсли;

Архитектурная альтернатива: Хеширование состава

Если поиск по точному составу табличной части выполняется регулярно на больших объемах данных, постоянный перебор строк табличной части через группировки может замедлить работу системы. В таких случаях применяют расчет сигнатуры (хеша):

  1. В справочник добавляют строковый реквизит ХешСостава с установленным свойством «Индексировать».
  2. В событии ПередЗаписью объекта строки табличной части сортируются по GUID/коду, объединяются в единую строку и вычисляется хеш с помощью объекта ХешированиеДанных (например, MD5 или SHA-256).
  3. При поиске достаточно рассчитать хеш для искомого массива и выполнить быстрый точечный запрос: ГДЕ Спр.ХешСостава = &ИскомыйХеш.
← На главную