При разработке систем закупок или контроля номенклатуры в конфигурациях уровня 1С:ERP мы часто сталкиваемся с задачей фильтрации товаров по классификаторам, таким как ОКПД2. Классификатор ОКПД2 имеет иерархическую структуру, где код верхнего уровня (например, "22.29") включает в себя все вложенные коды (например, "22.29.29.190").
Рассмотрим типичную задачу: у нас есть регистр сведений с "запрещенными" кодами. В этом регистре могут храниться как полные коды конкретных товаров, так и коды групп (частичные коды). Нам необходимо проверить, попадает ли конкретная номенклатура с полным кодом под блокировку, если в "черном списке" находится этот код или любой его родитель.
Давайте разберем несколько подходов к реализации этой задачи, проанализируем их производительность и выберем наиболее эффективный алгоритм.
Предположим, у нас есть следующие вводные данные:
22.29.29.190.22.29 (блокируется вся группа).Нам нужно, чтобы система поняла: код 22.29.29.190 начинается с 22.29, следовательно, закупка запрещена.
Рассмотрим, какие ошибки часто допускаются при реализации такой проверки в запросе.
Первая мысль, которая приходит в голову разработчику — использовать оператор ПОДОБНО (аналог LIKE в SQL). Логика кажется простой: мы проверяем, похож ли код номенклатуры на код из регистра с добавлением символа процента.
ВЫБРАТЬ
Номенклатура.Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЗапрещенныеКодыОКПД2 КАК Запрет
ПО Номенклатура.КодОКПД2 ПОДОБНО Запрет.Код + "%"
Почему этот метод плох?
Давайте проанализируем, как это работает на уровне СУБД (MS SQL или PostgreSQL). Условие соединения ПОДОБНО Запрет.Код + "%" является динамическим. Шаблон поиска формируется "на лету" из данных таблицы регистра.
Это приводит к следующим последствиям:
Код в регистре сведений, так как он не ищет конкретное значение.Этот метод категорически не рекомендуется использовать на больших объемах данных.
Чтобы проверка работала мгновенно, нам нужно использовать возможности индексирования. Для этого мы должны искать в регистре точное совпадение, а не подобие.
Идея метода заключается в инверсии логики. Вместо того чтобы искать в регистре строку, которая является "началом" нашего кода, мы заранее разложим наш код номенклатуры на всех возможных "родителей" и проверим их наличие в регистре.
Разберем алгоритм по шагам:
Шаг 1. Подготовка данных на сервере 1С
Перед выполнением запроса мы берем код ОКПД2 проверяемой номенклатуры (например, 22.29.29.190) и программно генерируем массив всех его иерархических уровней.
Используем функцию СтрРазделить для быстрого парсинга.
// Пример функции для получения массива родителей
Функция ПолучитьВариантыКодаОКПД2(Знач ПолныйКод)
МассивЧастей = СтрРазделить(ПолныйКод, ".", Ложь);
МассивВариантов = Новый Массив;
ТекущаяСтрока = "";
Для Каждого Часть Из МассивЧастей Цикл
Если Не ПустаяСтрока(ТекущаяСтрока) Тогда
ТекущаяСтрока = ТекущаяСтрока + ".";
КонецЕсли;
ТекущаяСтрока = ТекущаяСтрока + Часть;
// Добавляем вариант (например "22", затем "22.29", затем "22.29.29")
МассивВариантов.Добавить(ТекущаяСтрока);
КонецЦикла;
Возврат МассивВариантов;
КонецФункции
В результате для кода 22.29.29.190 мы получим массив:
2222.2922.29.2922.29.29.190Шаг 2. Выполнение запроса
Теперь мы можем передать этот массив в запрос и использовать оператор В (IN).
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Запрет.Код КАК ЗапрещенныйКод
|ИЗ
| РегистрСведений.ЗапрещенныеКодыОКПД2 КАК Запрет
|ГДЕ
| Запрет.Код В (&МассивВариантов)";
Запрос.УстановитьПараметр("МассивВариантов", МассивВариантов);
Результат = Запрос.Выполнить();
Преимущества этого метода:
В со списком конкретных значений позволяет SQL-серверу точечно обращаться к индексу регистра. Это работает экстремально быстро, даже если в регистре миллионы записей.В обсуждении (сообщения 8 и 9) был предложен интересный вариант изменения структуры хранения данных. Суть его заключается в том, чтобы хранить части кода ОКПД2 в отдельных измерениях регистра сведений.
Давайте посмотрим, как это может выглядеть. Вместо одной строки Код, мы создаем измерения:
Уровень1 (например, "22")Уровень2 (например, "29")Уровень3 (например, "29")При записи в регистр код "22.29" запишется как: Уровень1="22", Уровень2="29", остальные пустые.
Анализ решения:
Хотя этот метод позволяет структурировать данные, он делает запрос сложнее и менее гибким. Вам придется писать громоздкие условия ИЛИ, проверяя совпадение по разному количеству заполненных уровней. В сравнении с методом №2 (поиск по списку родителей), этот метод требует серьезной переработки метаданных конфигурации, но не дает существенного выигрыша в скорости чтения.
Если ваша система работает под высокой нагрузкой (например, высокочастотная торговля или тысячи строк в заказе), имеет смысл рассмотреть вариант денормализации данных.
Суть метода: перенести вычисления с момента чтения (проверки документа) на момент записи.
Номенклатура реквизит булево типа ЗапрещенПоОКПД2.ЗапрещенПоОКПД2 = Истина.В итоге, в момент оформления заказа, нам не нужно делать никаких сложных сравнений строк или соединений таблиц. Мы просто проверяем значение реквизита:
Если Номенклатура.ЗапрещенПоОКПД2 Тогда
Отказ = Истина;
Сообщить("Товар запрещен к закупке");
КонецЕсли;
Это самое быстрое решение для оперативной работы, но оно требует аккуратной реализации механизмов обновления этого признака.
Подведем итоги. Для реализации поиска по части кода ОКПД2 наиболее эффективными являются следующие решения:
В к регистру запретов. Это гарантирует использование индексов СУБД.ПОДОБНО Регистр.Код + "%" в соединениях таблиц, так как это приведет к полному сканированию таблиц и падению производительности.