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