При работе в конфигурации «1С:Управление торговлей 10.3» (и любых других решениях на платформе «1С:Предприятие 8») разработчики часто сталкиваются с задачей поиска данных по строковому представлению. Типичный пример — регистр сведений ЗначенияСвойствОбъектов, где в качестве значения может храниться практически любой тип данных: число, дата, строка или ссылка на различные справочники (номенклатура, контрагенты, договоры и т.д.).
Попытка написать простой запрос с условием Представление(ЗначенияСвойствОбъектов.Значение) ПОДОБНО &СтрокаПоиска приводит к ошибке еще на этапе компиляции или работы в Конструкторе запросов. В этой статье мы подробно разберем, почему возникает эта проблема, как работает платформа «1С» на уровне СУБД, и рассмотрим четыре профессиональных способа решения этой задачи с примерами кода.
Для того чтобы понять, почему обычный запрос выдает ошибку, нам необходимо заглянуть «под капот» платформы 1С и посмотреть, как она взаимодействует с системами управления базами данных (MS SQL, PostgreSQL и др.).
Функция ПРЕДСТАВЛЕНИЕ() в языке запросов 1С является виртуальной. В самой базе данных SQL ссылки хранятся в виде уникальных идентификаторов (GUID), а числа и даты — в своих системных двоичных форматах. СУБД ничего не знает о том, как красиво отобразить пользователю конкретную ссылку (например, получить наименование номенклатуры с артикулом или фамилию физического лица).
Текстовое представление формируется силами самого сервера приложений 1С уже после того, как СУБД вернула сырые данные из таблиц. Более того, для формирования представления могут вызываться сложные алгоритмы, прописанные разработчиком в модуле менеджера объекта в событии ОбработкаПолученияПредставления.
Поскольку СУБД физически не имеет доступа к текстовой строке представления в момент выполнения SQL-запроса, она не может наложить на него оператор фильтрации LIKE (который в 1С представлен как ПОДОБНО). Именно поэтому платформа блокирует подобные конструкции на уровне синтаксического анализа.
Также в языке запросов 1С отсутствует универсальное приведение типов (Cast) в строку неограниченной длины. Функция ВЫРАЗИТЬ() позволяет изменять тип данных, но она не умеет превращать числа, даты или ссылочные типы в обычные строки для последующего текстового поиска.
Рассмотрим основные способы обхода этого ограничения, начиная с самого простого и заканчивая наиболее производительными.
Самый быстрый в реализации способ, к которому в итоге пришел автор темы, — это использование Системы компоновки данных (СКД). СКД представляет собой не просто генератор отчетов, а мощный процессор обработки данных в памяти.
Когда мы накладываем отбор с видом сравнения «Содержит» на поле представления в СКД, система автоматически обходит ограничения СУБД одним из двух путей:
ПОДОБНО непосредственно на поле наименования или кода этого справочника.Важное предупреждение о производительности: Несмотря на то что СКД решает задачу «из коробки» без написания сложного кода, этот метод таит в себе опасность. Если в вашем регистре сведений хранятся сотни тысяч или миллионы записей, СКД попытается выгрузить их все в оперативную память сервера приложений для вычисления представлений. Это приведет к огромной нагрузке на процессор, резкому росту потребления RAM и потенциальной ошибке нехватки памяти (Out of memory).
Если нам необходимо обеспечить высокую скорость работы на больших объемах данных, лучшим решением будет разделение задачи на два последовательных этапа. Вместо того чтобы искать строку «на лету» внутри универсального регистра, мы сначала найдем все подходящие объекты в конкретных справочниках, а затем выберем записи регистра по найденным ссылкам.
Проанализируем пример реализации этого подхода на встроенном языке 1С:
// Этап 1: Ищем ссылки на объекты, чье наименование содержит строку поиска
СтрокаПоиска = "%" + СокращенЛП(ПараметрСтрокаПоиска) + "%";
ЗапросПоискаСсылок = Новый Запрос;
ЗапросПоискаСсылок.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Наименование ПОДОБНО &СтрокаПоиска
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| Контрагенты.Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.Наименование ПОДОБНО &СтрокаПоиска";
ЗапросПоискаСсылок.УстановитьПараметр("СтрокаПоиска", СтрокаПоиска);
МассивНайденого = ЗапросПоискаСсылок.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка");
// Дополнительно обработаем случай, если ищут число
ЧислоПоиска = Неопределено;
Попытка
ЧислоПоиска = Число(ПараметрСтрокаПоиска);
Исключение
// Введенная строка не является числом, игнорируем
КонецПопытки;
// Этап 2: Основной запрос к регистру сведений
ЗапросРегистра = Новый Запрос;
ЗапросРегистра.Текст =
"ВЫБРАТЬ
| Регистр.Объект КАК Объект,
| Регистр.Значение КАК Значение
|ИЗ
| РегистрСведений.ЗначенияСвойствОбъектов КАК Регистр
|ГДЕ
| (Регистр.Значение В (&МассивНайденого)
| ИЛИ Регистр.Значение = &ЧислоПоиска)";
ЗапросРегистра.УстановитьПараметр("МассивНайденого", МассивНайденого);
ЗапросРегистра.УстановитьПараметр("ЧислоПоиска", ЧислоПоиска);
Результат = ЗапросРегистра.Выполнить();
Этот метод работает чрезвычайно быстро, поскольку СУБД использует существующие индексы по полям наименований справочников, а во второй запрос передается уже готовый отфильтрованный массив уникальных идентификаторов.
Если набор возможных типов значений в регистре свойств заранее известен и ограничен несколькими справочниками, мы можем построить запрос динамически, задействовав проверку типов через ТИПЗНАЧЕНИЯ и выполнив левые соединения с таблицами этих справочников.
Давайте посмотрим на структуру такого запроса:
ВЫБРАТЬ
Регистр.Объект КАК Объект,
Регистр.Значение КАК Значение
ИЗ
РегистрСведений.ЗначенияСвойствОбъектов КАК Регистр
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СпрНоменклатура
ПО ТИПЗНАЧЕНИЯ(Регистр.Значение) = ТИП(Справочник.Номенклатура)
И Регистр.Значение = СпрНоменклатура.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК СпрКонтрагенты
ПО ТИПЗНАЧЕНИЯ(Регистр.Значение) = ТИП(Справочник.Контрагенты)
И Регистр.Значение = СпрКонтрагенты.Ссылка
ГДЕ
СпрНоменклатура.Наименование ПОДОБНО &СтрокаПоиска
ИЛИ СпрКонтрагенты.Наименование ПОДОБНО &СтрокаПоиска
Преимущества данного подхода:
ПОДОБНО (при условии, что строка поиска не начинается со знака процента %, иначе индекс не задействуется).Для крупных информационных баз с миллионами записей стандартные SQL-операции поиска подстроки могут выполняться недопустимо долго. В таких сценариях целесообразно использовать встроенный механизм Полнотекстового Поиска (ПТП) платформы «1С:Предприятие».
Полнотекстовый поиск работает на основе предварительно проиндексированных данных и позволяет находить нужные объекты за миллисекунды. Алгоритм реализации выглядит следующим образом:
ПолнотекстовыйПоиск.Пример программного кода для работы с ПТП:
СписокПоиска = ПолнотекстовыйПоиск.СоздатьСписок(СтрокаПоиска, 50);
СписокПоиска.ПерваяЧасть();
МассивСсылок = Новый Массив;
Для Каждого СтрокаРезультата Из СписокПоиска Цикл
Если Справочники.ТипВсеСсылки().СодержитТип(ТипЗнч(СтрокаРезультата.Значение)) Тогда
МассивСсылок.Добавить(СтрокаРезультата.Значение);
КонецЕсли;
КонецЦикла;
Этот способ незаменим, когда требуется организовать «умный» поиск (например, с учетом морфологии русского языка, опечаток или синонимов) по всей базе данных сразу.
Мы проанализировали проблему поиска по представлению значения в запросах 1С и разобрали различные варианты ее решения. При выборе конкретного метода рекомендуем придерживаться следующих правил: