При разработке и кастомизации конфигураций на платформе 1С:Предприятие 8 на базе обычных форм (толстый клиент, например, в таких классических решениях, как Управление торговлей 10.3, Управление производственным предприятием 1.3 или Комплексная автоматизация 1.1) программисты часто сталкиваются с аппаратными и программными ограничениями интерфейса. Одной из классических задач является необходимость наложить сложный фильтр на список регистра сведений, задействуя логическую связь ИЛИ между двумя или более измерениями.
Стандартный объект РегистрСведенийСписок, связанный с табличным полем формы, предоставляет встроенную коллекцию Отбор. Главная сложность заключается в том, что платформа накладывает условия на элементы этой коллекции исключительно по логическому «И». Если нам нужно отобрать записи, где текущий пользователь является либо отправителем (КтоЗадалВопрос), либо получателем (КомуВопрос), стандартный механизм отбора заставит нас выбирать только одно из полей или объединять их по «И», что в корне ломает бизнес-логику чата или системы уведомлений. Выясним, как обойти это ограничение платформы, проанализируем различные подходы к решению и разберем конкретные примеры реализации.
Рассмотрим самый очевидный и часто применяемый способ. Мы можем программно выполнить запрос к регистру сведений, отобрать все подходящие записи по условию ИЛИ, выгрузить уникальные идентификаторы (ссылки на документы-основания или ключи записей) во временный список значений, а затем наложить стандартный отбор с видом сравнения ВидСравнения.ВСписке на ключевое поле списка.
Разберем по шагам программный код, который можно разместить, например, в обработчике события ПриОткрытии или при изменении параметров фильтрации на форме:
// Сбросим текущие отборы по ключевому полю
РегистрСведенийСписок.Отбор.ДокументСсылка.Сбросить();
// Формируем запрос для получения списка документов, подходящих под условие "ИЛИ"
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Чат.ДокументСсылка КАК ДокументСсылка
|ИЗ
| РегистрСведений.ИсторияЧата КАК Чат
|ГДЕ
| (Чат.КомуВопрос = &ТекущийПользователь
| ИЛИ Чат.КтоЗадалВопрос = &ТекущийПользователь)";
Запрос.УстановитьПараметр("ТекущийПользователь", ПараметрыСеанса.ТекущийПользователь);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
// Выгружаем результат в список значений
СписокДокументов = Новый СписокЗначений;
СписокДокументов.ЗагрузитьЗначения(РезультатЗапроса.Выгрузить().ВыгрузитьКолонку("ДокументСсылка"));
// Накладываем фильтр на динамический список формы
РегистрСведенийСписок.Отбор.ДокументСсылка.ВидСравнения = ВидСравнения.ВСписке;
РегистрСведенийСписок.Отбор.ДокументСсылка.Значение = СписокДокументов;
РегистрСведенийСписок.Отбор.ДокументСсылка.Использование = Истина;
Иначе
// Если ничего не найдено, передаем пустую ссылку, чтобы список на форме остался пустым
РегистрСведенийСписок.Отбор.ДокументСсылка.ВидСравнения = ВидСравнения.Равно;
РегистрСведенийСписок.Отбор.ДокументСсылка.Значение = Документы.ВопросПользователя.ПустаяСсылка();
РегистрСведенийСписок.Отбор.ДокументСсылка.Использование = Истина;
КонецЕсли;
Важное предупреждение о производительности: Как справедливо отмечают опытные разработчики, данный метод таит в себе скрытую угрозу. Если база данных содержит сотни тысяч записей, а результат запроса возвращает более 5–10 тысяч уникальных ссылок, СУБД начнет испытывать колоссальные нагрузки при обработке условия IN (...). Кроме того, наложение механизма RLS (ограничение прав на уровне записей) поверх такого фильтра может превратить открытие формы в бесконечный процесс. Чтобы этого не произошло, обязательно накладывайте жесткие ограничения по периоду (например, выбирать сообщения только за последние 7 дней) и используйте индексацию полей в конфигураторе.
Для обхода ограничения виртуального списка обычных форм мы можем использовать мощный инструмент — ПостроительЗапроса. Вместо прямой связи табличного поля с объектом РегистрСведенийСписок, мы можем динамически загружать данные в таблицу значений и выводить ее на форму. Построитель запроса позволяет сохранить интерактивность сортировок и наложение пользовательских фильтров, выполняя сложную логику на стороне СУБД. Для реализации гибкой фильтрации произвольных таблиц и запросов можно использовать универсальную форму отбора и сортировки средствами СКД.
Посмотрим на пример программной инициализации источника данных:
Построитель = Новый ПостроительЗапроса;
Построитель.Текст =
"ВЫБРАТЬ
| Чат.Период КАК Период,
| Чат.КомуВопрос КАК КомуВопрос,
| Чат.КтоЗадалВопрос КАК КтоЗадалВопрос,
| Чат.ТекстВопроса КАК ТекстВопроса,
| Чат.Ознакомлен КАК Ознакомлен
|ИЗ
| РегистрСведений.ИсторияЧата КАК Чат
|ГДЕ
| (Чат.КомуВопрос = &ТекущийПользователь
| ИЛИ Чат.КтоЗадалВопрос = &ТекущийПользователь)";
Построитель.Параметры.Вставить("ТекущийПользователь", ПараметрыСеанса.ТекущийПользователь);
Построитель.Выполнить();
// Выгружаем результат построителя в ТаблицуЗначений
ТаблицаОтобранныхСообщений = Построитель.Результат.Выгрузить();
// Связываем полученную таблицу с элементом управления на форме
ЭлементыФормы.ТабличноеПолеЧата.Значение = ТаблицаОтобранныхСообщений;
ЭлементыФормы.ТабличноеПолеЧата.СоздатьКолонки();
Основным недостатком этого метода является потеря динамической выборки данных (так называемого "виртуального списка"). Программа будет вынуждена считывать весь массив строк в оперативную память клиента, что допустимо только при сравнительно небольших объемах данных (до нескольких десятков тысяч записей).
Проанализируем ситуацию с точки зрения проектирования структуры метаданных. Зачастую необходимость наложения сложных отборов «ИЛИ» вызвана неоптимальной архитектурой хранения данных. Если вы проектируете функционал чата или внутренних задач с нуля, вместо хранения получателя и отправителя в одной строке регистра (в виде колонок), мы можем разнести их на отдельные строки во вспомогательный подчиненный регистр.
Рассмотрим эту схему подробнее:
УчастникиВопросов с измерениями ВопросСсылка (тип: ДокументСсылка.Вопрос или УникальныйИдентификатор) и Пользователь (тип: Справочник.Пользователи).УчастникиВопросов: одну строку для автора, вторую — для адресата.Пользователь РАВНО ТекущийПользователь. Этот отбор мгновенно обрабатывается СУБД, так как поле проиндексировано.Такой подход полностью решает проблему производительности и избавляет от необходимости писать тяжелые и нестабильные обходные алгоритмы.
Мало кто помнит, но начиная с версии платформы 1С 8.2 разработчикам доступен гибридный режим работы конфигурации. Даже если вся ваша система (например, УТ 10.3) работает исключительно на обычных формах, для конкретного нового объекта или списка вы можете разработать и использовать управляемую форму.
В управляемой форме в качестве источника данных выступает полноценный ДинамическийСписок, поддерживающий произвольные запросы и сложные группировки условий компоновщика настроек. Разберем по шагам, как настроить группу отборов программно при создании управляемой формы:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Получаем коллекцию отборов динамического списка
ГруппаИЛИ = Список.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
ГруппаИЛИ.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИли;
ГруппаИЛИ.Использование = Истина;
// Добавляем первое условие: КомуВопрос = ТекущийПользователь
ЭлементКому = ГруппаИЛИ.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементКому.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("КомуВопрос");
ЭлементКому.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементКому.ПравоеЗначение = ПараметрыСеанса.ТекущийПользователь;
ЭлементКому.Использование = Истина;
// Добавляем второе условие: КтоЗадалВопрос = ТекущийПользователь
ЭлементКто = ГруппаИЛИ.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементКто.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("КтоЗадалВопрос");
ЭлементКто.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементКто.ПравоеЗначение = ПараметрыСеанса.ТекущийПользователь;
ЭлементКто.Использование = Истина;
КонецПроцедуры
При вызове такой управляемой формы из обычного интерфейса платформа автоматически откроет ее в отдельном окне, задействуя тонкий клиент под капотом толстого. Это современное, надежное и наиболее масштабируемое решение проблемы. Для расширения возможностей таких интерфейсов можно применять практические методы доработки динамических списков.
Подводя итог нашему анализу, мы можем выделить следующие рекомендации для практикующих разработчиков: