При разработке интерфейсов на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с классической архитектурной проблемой: в динамическом списке присутствует колонка, данные в которой рассчитываются или собираются программно в обработчике ПриПолученииДанныхНаСервере. Когда пользователю требуется выполнить поиск или установить отбор по этой колонке, стандартные механизмы платформы (такие как поиск по нажатию Ctrl+F) либо не работают, либо вызывают критические ошибки. В этой статье мы подробно разберем, почему так происходит, и рассмотрим три практических способа решения этой задачи с примерами кода.
Для того чтобы выбрать оптимальный путь решения, нам необходимо детально проанализировать жизненный цикл выборки и отображения данных динамическим списком. Давайте посмотрим, в какой последовательности платформа выполняет операции:
ГДЕ (или WHERE на уровне СУБД) SQL-запроса, который отправляется к базе данных.ПриПолученииДанныхНаСервере, где разработчик программно заполняет виртуальные колонки.Важный вывод: поскольку СУБД выполняет фильтрацию до того, как управление передается в код обработчика, она ничего не знает о тех значениях, которые мы собираемся записать в колонку программно. Если колонка в тексте запроса инициализирована пустой строкой, СУБД попытается найти совпадение по пустой строке и вернет пустой результат. В итоге обработчик заполнения даже не запустится.
Также разберем причину ошибки «Не найдено поле отбора», с которой столкнулся автор темы при попытке программно добавить элемент отбора компоновки данных. Данная ошибка возникает из-за того, что поле отсутствует в схеме компоновки данных (СКД), на которой базируется динамический список. Чтобы поле стало доступным для разработчика в настройках компоновщика, в произвольном запросе динамического списка обязательно должна быть объявлена хотя бы «колонка-заглушка», например:
ВЫБРАТЬ
РегистрСведений.Измерение1 КАК Измерение1,
РегистрСведений.Ресурс1 КАК Ресурс1,
ВЫРАЗИТЬ("" КАК СТРОКА(1000)) КАК Контрагент
ИЗ
РегистрСведений.РегистрСведений КАК РегистрСведений
После этого для поля Контрагент в настройках списка необходимо взвести флаг «Использовать всегда». Однако помните: это лишь сделает поле видимым в настройках компоновщика, но саму проблему поиска по реальным программно заполненным данным не решит.
Ниже мы подробно разберем три профессиональных сценария реализации поиска, которые помогут обойти это ограничение платформы.
---
Это самый популярный способ быстро обойти ограничение без изменения структуры метаданных конфигурации. Суть метода заключается в том, что мы перехватываем ввод пользователя, выполняем отдельный точечный запрос к базе данных для поиска подходящих записей и накладываем на динамический список отбор по ключевому полю (например, по уникальной ссылке или измерению регистра). Похожий механизм сопоставления по произвольным реквизитам реализован в обработке, выполняющей перенос данных между похожими конфигурациями 1С при несовпадении ссылок.
Рассмотрим по шагам реализацию этого механизма:
Шаг 1. Создание элементов интерфейса. Добавим на форму строковый реквизит с именем СтрокаПоискаКонтрагента и вынесем его на форму в качестве поля ввода. Для этого элемента определим событие ПриИзменении.
Шаг 2. Написание обработчика на сервере. При изменении строки поиска мы должны запустить поисковый запрос. Обратите внимание на важный нюанс производительности: если поисковому запросу будет соответствовать слишком много записей (например, десятки тысяч документов), передача гигантского массива в условие В Списке приведет к падению производительности СУБД. Поэтому мы ограничим выборку разумным пределом (например, 500 записей) и предупредим пользователя.
Проанализируем программный код реализации:
&НаКлиенте
Процедура СтрокаПоискаКонтрагентаПриИзменении(Элемент)
ВыполнитьКосвенныйПоискНаСервере();
КонецПроцедуры
&НаСервере
Процедура ВыполнитьКосвенныйПоискНаСервере()
// Очищаем старый отбор по ключевому полю, если он был установлен ранее
ИмяПоляКлюча = "Документ"; // Ключевое поле нашего списка
ПолеКомпоновки = Новый ПолеКомпоновкиДанных(ИмяПоляКлюча);
ЭлементОтбораСписка = Неопределено;
Для Каждого ЭлементИзОтбора Из Список.Отбор.Элементы Цикл
Если ТипЗнч(ЭлементИзОтбора) = Тип("ЭлементОтбораКомпоновкиДанных")
И ЭлементИзОтбора.ЛевоеЗначение = ПолеКомпоновки Тогда
ЭлементОтбораСписка = ЭлементИзОтбора;
Прервать;
КонецЕсли;
КонецЦикла;
// Если поисковая строка пустая — просто отключаем или удаляем отбор
Если ПустаяСтрока(СтрокаПоискаКонтрагента) Тогда
Если ЭлементОтбораСписка <> Неопределено Тогда
ЭлементОтбораСписка.Использование = Ложь;
КонецЕсли;
Возврат;
КонецЕсли;
// Выполняем запрос для поиска подходящих ключей
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 501
| ДокументТабличнаяЧасть.Ссылка КАК Ссылка
|ИЗ
| Документ.РеализацияТоваровУслуг.Контрагенты КАК ДокументТабличнаяЧасть
|ГДЕ
| ДокументТабличнаяЧасть.Контрагент.Наименование ПОДОБНО &СтрокаПоиска";
Запрос.УстановитьПараметр("СтрокаПоиска", "%" + СтрокаПоискаКонтрагента + "%");
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
// Если ничего не найдено, передаем пустой массив, чтобы список очистился
МассивКлючей = Новый Массив;
Иначе
ТаблицаДанных = РезультатЗапроса.Выгрузить();
Если ТаблицаДанных.Количество() > 500 Тогда
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(
"Найдено слишком много совпадений. Пожалуйста, уточните поисковый запрос!");
// Удаляем лишнюю строку, чтобы не перегружать СУБД
ТаблицаДанных.Удалить(500);
КонецЕсли;
МассивКлючей = ТаблицаДанных.ВыгрузитьКолонку("Ссылка");
КонецЕсли;
// Добавляем или активируем отбор компоновки данных
Если ЭлементОтбораСписка = Неопределено Тогда
ЭлементОтбораСписка = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбораСписка.ЛевоеЗначение = ПолеКомпоновки;
КонецЕсли;
ЭлементОтбораСписка.ВидСравнения = ВидСравненияКомпоновкиДанных.ВСписке;
ЭлементОтбораСписка.ПравоеЗначение = МассивКлючей;
ЭлементОтбораСписка.Использование = Истина;
КонецПроцедуры
Этот метод хорош тем, что он не требует изменения структуры хранения данных в СУБД, однако при частых поисковых запросах он создает дополнительную точечную нагрузку на сервер СУБД. Для контроля корректности данных после подобных доработок используется автоматическая сверка бухгалтерских и налоговых проводок между базами 1С.
---
Очень часто разработчики используют обработчик ПриПолученииДанныхНаСервере необоснованно, опасаясь усложнять SQL-запрос динамического списка. Но если связующие данные реально физически присутствуют в базе данных (например, контрагент привязан к документу через табличную часть или подчиненный регистр), правильнее всего извлечь эти данные прямо в тексте динамического списка с помощью операции ЛЕВОЕ СОЕДИНЕНИЕ.
Давайте посмотрим на пример такого запроса. Предположим, у нас есть регистр сведений, и нам нужно отображать контрагента, который содержится в реквизитах документа-регистратора:
ВЫБРАТЬ
Регистр.Период КАК Период,
Регистр.ДокументРегистратор КАК ДокументРегистратор,
// Вытаскиваем контрагента напрямую через точку или левое соединение
ЕСТЬNULL(Регистр.ДокументРегистратор.Контрагент, ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)) КАК Контрагент
ИЗ
РегистрСведений.ИндексныеДанные КАК Регистр
При использовании такого подхода:
Важное ограничение: данный подход отлично работает, если связь между сущностями "один к одному". Если же у одного документа может быть множество контрагентов (например, табличная часть с перечнем участников сделки), то простое левое соединение приведет к дублированию строк в динамическом списке, что недопустимо. В таких случаях следует переходить к денормализации.
---
В крупных высоконагруженных конфигурациях (таких как ERP 2.5, Комплексная автоматизация или Управление торговлей 11) разработчики фирмы «1С» массово применяют концепцию денормализации. Для планирования миграции на новые версии систем подготовлен раздел с решениями для переноса данных в ERP.
Денормализация — это намеренное добавление избыточных данных в структуру БД при записи для того, чтобы существенно ускорить чтение и упростить последующие поисковые запросы. Вместо динамического соединения таблиц или программного перебора строк на лету, мы заранее формируем готовую строку поиска и физически сохраняем её в базе данных.
Давайте реализуем этот паттерн на практике:
Шаг 1. Модернизация метаданных. Добавим в наш объект метаданных (документ или регистр сведений) новый реквизит с именем СписокКонтрагентовСтрокой (тип: Строка, длина 1000 символов). Рекомендуется установить для этого поля свойство «Индексировать», чтобы СУБД могла моментально производить поиск по этой колонке.
Шаг 2. Заполнение поля при записи объекта. В модуле объекта (например, документа) в обработчике события ПередЗаписью напишем код, который будет обходить табличную часть, собирать имена контрагентов через запятую и записывать полученную строку в наш новый реквизит:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Сворачиваем табличную часть для получения уникальных контрагентов
ТаблицаКонтрагентов = КонтрагентыСделки.ВыгрузитьКолонку("Контрагент");
// Удаляем дубликаты
ОбщегоНазначенияКлиентСервер.СвернутьМассив(ТаблицаКонтрагентов);
СтроковыеПредставления = Новый Массив;
Для Каждого ТекущийКонтрагент Из ТаблицаКонтрагентов Цикл
Если ЗначениеЗаполнено(ТекущийКонтрагент) Тогда
СтроковыеПредставления.Добавить(Строка(ТекущийКонтрагент));
КонецЕсли;
КонецЦикла;
// Записываем результат денормализации
СписокКонтрагентовСтрокой = СтрСоединить(СтроковыеПредставления, ", ");
КонецПроцедуры
Шаг 3. Отображение в динамическом списке. Теперь в динамическом списке мы просто выводим поле СписокКонтрагентовСтрокой. Поскольку это реальное проиндексированное поле таблицы базы данных, стандартный полнотекстовый поиск платформы, отборы, группировки и сортировки будут работать идеально и с максимальной скоростью, не нагружая сервер приложений процедурами постобработки в ПриПолученииДанныхНаСервере.
---
Если специфика вашей задачи всё же заставляет вас использовать программное заполнение колонок через ПриПолученииДанныхНаСервере, то, согласно официальным стандартам разработки фирмы «1С», вы обязаны принудительно запретить пользователям выполнять интерактивный отбор и сортировку по этим программным полям. Это необходимо для того, чтобы исключить возникновение системных ошибок SQL или отображение некорректно отфильтрованных (пустых) списков.
Для реализации этого стандарта добавьте в процедуру формы ПриСозданииНаСервере следующий код:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Запрещаем использовать программную колонку "Контрагент" в интерактивных отборах
ПолеКонтрагент = Список.Поля.Найти("Контрагент");
Если ПолеКонтрагент <> Неопределено Тогда
ПолеКонтрагент.ОграничениеИспользования.Условие = Истина;
ПолеКонтрагент.ОграничениеИспользования.Порядок = Истина;
ПолеКонтрагент.ОграничениеИспользования.Группировка = Истина;
КонецЕсли;
КонецПроцедуры
Этот код надежно защитит форму от непредусмотренных действий пользователей, скрыв возможность настройки фильтрации по неиндексируемой программной колонке в стандартном меню «Настроить список».