При разработке интерфейсов в управляемых формах 1С часто возникает классическая задача: предоставить пользователю простой и понятный инструмент для фильтрации документов по конкретному значению, которое может находиться в одном из двух разных реквизитов. Яркий пример — документ перемещения товаров, содержащий поля СкладОтправитель и СкладПолучатель. Нам необходимо спроектировать один простой отбор по складу на форме, который будет находить документы, где выбранный склад является либо отправителем, либо получателем.
В обычных формах подобные задачи элегантно решались с помощью критериев отбора. В управляемых формах работа с динамическими списками строится на технологиях Системы компоновки данных (СКД), что открывает перед нами несколько альтернативных вариантов реализации. Рассмотрим подробно каждый из подходов — от простых решений до высокопроизводительных методов для нагруженных баз данных.
Этот способ является наиболее простым и декларативным. Мы переносим логику условия «ИЛИ» непосредственно в текст запроса динамического списка. При этом мы оборачиваем наше условие в фигурные скобки. Это позволяет платформе автоматически исключать данное условие из итогового SQL-запроса, если пользователь не выбрал значение фильтра.
Выполним настройку по шагам:
ГДЕ наше условие в фигурных скобках:
ВЫБРАТЬ
Перемещение.Ссылка КАК Ссылка,
Перемещение.Дата КАК Дата,
Перемещение.Номер КАК Номер,
Перемещение.СкладОтправитель КАК СкладОтправитель,
Перемещение.СкладПолучатель КАК СкладПолучатель
ИЗ
Документ.ПеремещениеТоваров КАК Перемещение
{ГДЕ
(Перемещение.СкладОтправитель = &СкладОтбор
ИЛИ Перемещение.СкладПолучатель = &СкладОтбор)}
Обратите внимание на использование фигурных скобок {ГДЕ ...}. Если параметр &СкладОтбор не будет установлен в коде, платформа 1С проигнорирует эту секцию, и запрос выполнится без лишних условий. Это избавляет от ошибок вида «Не задано значение параметра».
Теперь добавим на форму управляемый реквизит, например, ОтборСклад с типом ссылки на справочник складов, и вынесем его на форму в качестве поля ввода. В обработчике события ПриИзменении этого поля напишем следующий код:
&НаСервере
Процедура ОтборСкладПриИзмененииНаСервере()
// Получаем параметры динамического списка
ПараметрыСписка = Список.Параметры;
// Если склад выбран, устанавливаем параметр, иначе сбрасываем его использование
Если ЗначениеЗаполнено(ОтборСклад) Тогда
ПараметрыСписка.УстановитьЗначениеПараметра("СкладОтбор", ОтборСклад);
Иначе
// Находим параметр и отключаем его использование
ПараметрСКД = ПараметрыСписка.Найти("СкладОтбор");
Если ПараметрСКД <> Неопределено Тогда
ПараметрСКД.Использование = Ложь;
КонецЕсли;
EndЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ОтборСкладПриИзменении(Элемент)
ОтборСкладПриИзмененииНаСервере();
КонецПроцедуры
Предыдущий метод отлично подходит для небольших и средних баз данных. Однако, если в вашей таблице документов содержатся сотни тысяч или миллионы записей, использование оператора ИЛИ (OR) в запросах к СУБД приведет к существенному замедлению работы. СУБД в большинстве случаев откажется от использования индексов по полям складов и перейдет к полному сканированию таблицы (Table/Index Scan).
Чтобы избежать падения производительности, мы можем переписать запрос нашего динамического списка через конструкцию В (ВЫБРАТЬ ... ОБЪЕДИНИТЬ ВСЕ). СУБД сможет применить быстрое сканирование индекса (Index Seek) отдельно по каждому полю склада.
Рассмотрим структуру оптимизированного запроса динамического списка:
ВЫБРАТЬ
Перемещение.Ссылка КАК Ссылка,
Перемещение.Дата КАК Дата,
Перемещение.Номер КАК Номер,
Перемещение.СкладОтправитель КАК СкладОтправитель,
Перемещение.СкладПолучатель КАК СкладПолучатель
ИЗ
Документ.ПеремещениеТоваров КАК Перемещение
{ГДЕ
Перемещение.Ссылка В
(ВЫБРАТЬ
Т.Ссылка
ИЗ
Документ.ПеремещениеТоваров КАК Т
ГДЕ
Т.СкладОтправитель = &СкладОтбор
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Т.Ссылка
ИЗ
Документ.ПеремещениеТоваров КАК Т
ГДЕ
Т.СкладПолучатель = &СкладОтбор)}
Преимущества данного подхода:
В на уровне платформы автоматически уберет дубликаты записей, что избавляет нас от использования тяжелой для базы данных инструкции РАЗЛИЧНЫЕ (DISTINCT) в основном пакете.Если вы принципиально не хотите изменять текст запроса динамического списка, решить задачу можно исключительно программными средствами, манипулируя коллекцией отборов компоновки данных. Мы можем создать группу отборов с видом сравнения ГруппаИЛИ.
Проанализируем пример кода, который выполняет эту операцию на сервере. Данный метод можно вызывать при изменении значения нашего элемента отбора на форме:
&НаСервере
Процедура УстановитьОтборПоСкладуНаСервере(ВыбранныйСклад)
ИмяГруппыОтбора = "ГруппаОтбораПоСкладам";
// Сначала удалим старый отбор, если он существовал
ЭлементыОтбора = Список.Отбор.Элементы;
Для Каждого Элемент Из ЭлементыОтбора Цикл
Если ТипЗв = Тип("ГруппаЭлементовОтбораКомпоновкиДанных")
И Элемент.Представление = ИмяГруппыОтбора Тогда
ЭлементыОтбора.Удалить(Элемент);
Прервать;
КонецЕсли;
КонецЦикла;
// Если склад не выбран, просто выходим (фильтр очищен)
Если Не ЗначениеЗаполнено(ВыбранныйСклад) Тогда
Возврат;
КонецЕсли;
// Создаем новую группу ИЛИ
НоваяГруппа = ЭлементыОтбора.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
НоваяГруппа.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИЛИ;
НоваяГруппа.Представление = ИмяГруппыОтбора;
НоваяГруппа.Использование = Истина;
// Добавляем условие для Склада-Отправителя
ЭлементОтправитель = НоваяГруппа.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтправитель.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("СкладОтправитель");
ЭлементОтправитель.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементОтправитель.ПравоеЗначение = ВыбранныйСклад;
ЭлементОтправитель.Использование = Истина;
// Добавляем условие для Склада-Получателя
ЭлементПолучатель = НоваяГруппа.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементПолучатель.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("СкладПолучатель");
ЭлементПолучатель.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементПолучатель.ПравоеЗначение = ВыбранныйСклад;
ЭлементПолучатель.Использование = Истина;
КонецПроцедуры
Если в вашей конфигурации используется Библиотека стандартных подсистем (БСП), то ручную работу с коллекциями можно упростить с помощью функций общего модуля ОбщегоНазначенияКлиентСервер. Это сделает ваш код более компактным и защищенным от ошибок:
// Пример использования функций БСП для установки отбора
ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(
Список,
"СкладОтправитель",
ВыбранныйСклад,
ВидСравненияКомпоновкиДанных.Равно,
"СкладОтбор",
ЗначениеЗаполнено(ВыбранныйСклад)
);
Интересный альтернативный вариант предложили участники обсуждения в сообщении 20. Если мы хотим, чтобы отбор вел себя максимально близко к логике классических критериев отбора платформы 1С, мы можем связать таблицу документов со справочником складов в самом запросе динамического списка.
Рассмотрим следующий шаблон запроса:
ВЫБРАТЬ РАЗЛИЧНЫЕ
Перемещение.Ссылка КАК Ссылка,
Перемещение.Дата КАК Дата,
Перемещение.Номер КАК Номер,
Перемещение.СкладОтправитель КАК СкладОтправитель,
Перемещение.СкладПолучатель КАК СкладПолучатель
ИЗ
Документ.ПеремещениеТоваров КАК Перемещение
{ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Склады КАК СкладыОтбор
ПО Перемещение.СкладОтправитель = СкладыОтбор.Ссылка
ИЛИ Перемещение.СкладПолучатель = СкладыОтбор.Ссылка}
{ГДЕ
СкладыОтбор.Ссылка.* КАК СкладФильтр}
При таком подходе в динамическом списке появится виртуальное поле отбора СкладФильтр. Пользователь сможет стандартным образом настраивать его через форму настроек списка, выбирать виды сравнения «В списке», «Равно» и другие. Однако помните о необходимости использования ключевого слова РАЗЛИЧНЫЕ, так как при отборе по списку складов (если один и тот же склад указан и отправителем, и получателем в одном документе) без этой инструкции строки списка могут задвоиться.
Чтобы упростить интерфейс и не вводить пользователя в заблуждение, хорошей практикой является скрытие стандартных полей отбора (в нашем случае СкладОтправитель и СкладПолучатель) из списка доступных полей компоновки. Это исключит ситуацию, когда пользователь случайно наложит конфликтующие фильтры.
Начиная с версии платформы 8.3.10, мы можем использовать специальный метод УстановитьОграниченияИспользованияВОтборе() динамического списка. Разместим данный код в обработчике ПриСозданииНаСервере формы списка:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Запретим пользователю интерактивно использовать детальные поля складов в отборах
МассивЗапрещенныхПолей = Новый Массив;
МассивЗапрещенныхПолей.Добавить("СкладОтправитель");
МассивЗапрещенныхПолей.Добавить("СкладПолучатель");
Список.УстановитьОграниченияИспользованияВОтборе(МассивЗапрещенныхПолей);
КонецПроцедуры
Таким образом, мы рассмотрели различные способы решения задачи. Выбор конкретного метода зависит от объемов вашей информационной базы и требований к гибкости настроек для конечного пользователя. Для небольших баз отлично подойдет Метод 1 или Метод 3, тогда как для высоконагруженных систем настоятельно рекомендуется использовать оптимизированный запрос из Метода 2.