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