Как реализовать единый отбор по двум полям с условием ИЛИ в динамическом списке 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке интерфейсов в управляемых формах 1С часто возникает классическая задача: предоставить пользователю простой и понятный инструмент для фильтрации документов по конкретному значению, которое может находиться в одном из двух разных реквизитов. Яркий пример — документ перемещения товаров, содержащий поля СкладОтправитель и СкладПолучатель. Нам необходимо спроектировать один простой отбор по складу на форме, который будет находить документы, где выбранный склад является либо отправителем, либо получателем.

В обычных формах подобные задачи элегантно решались с помощью критериев отбора. В управляемых формах работа с динамическими списками строится на технологиях Системы компоновки данных (СКД), что открывает перед нами несколько альтернативных вариантов реализации. Рассмотрим подробно каждый из подходов — от простых решений до высокопроизводительных методов для нагруженных баз данных.

Метод 1. Использование необязательных параметров СКД («тихие» параметры)

Этот способ является наиболее простым и декларативным. Мы переносим логику условия «ИЛИ» непосредственно в текст запроса динамического списка. При этом мы оборачиваем наше условие в фигурные скобки. Это позволяет платформе автоматически исключать данное условие из итогового SQL-запроса, если пользователь не выбрал значение фильтра.

Выполним настройку по шагам:

  1. Откроем свойства динамического списка и установим флаг Произвольный запрос.
  2. Перейдем в конструктор запроса и добавим в секцию ГДЕ наше условие в фигурных скобках:

ВЫБРАТЬ
    Перемещение.Ссылка КАК Ссылка,
    Перемещение.Дата КАК Дата,
    Перемещение.Номер КАК Номер,
    Перемещение.СкладОтправитель КАК СкладОтправитель,
    Перемещение.СкладПолучатель КАК СкладПолучатель
ИЗ
    Документ.ПеремещениеТоваров КАК Перемещение
{ГДЕ
    (Перемещение.СкладОтправитель = &СкладОтбор 
     ИЛИ Перемещение.СкладПолучатель = &СкладОтбор)}

Обратите внимание на использование фигурных скобок {ГДЕ ...}. Если параметр &СкладОтбор не будет установлен в коде, платформа 1С проигнорирует эту секцию, и запрос выполнится без лишних условий. Это избавляет от ошибок вида «Не задано значение параметра».

Теперь добавим на форму управляемый реквизит, например, ОтборСклад с типом ссылки на справочник складов, и вынесем его на форму в качестве поля ввода. В обработчике события ПриИзменении этого поля напишем следующий код:


&НаСервере
Процедура ОтборСкладПриИзмененииНаСервере()
    
    // Получаем параметры динамического списка
    ПараметрыСписка = Список.Параметры;
    
    // Если склад выбран, устанавливаем параметр, иначе сбрасываем его использование
    Если ЗначениеЗаполнено(ОтборСклад) Тогда
        ПараметрыСписка.УстановитьЗначениеПараметра("СкладОтбор", ОтборСклад);
    Иначе
        // Находим параметр и отключаем его использование
        ПараметрСКД = ПараметрыСписка.Найти("СкладОтбор");
        Если ПараметрСКД <> Неопределено Тогда
            ПараметрСКД.Использование = Ложь;
        КонецЕсли;
    EndЕсли;
    
КонецПроцедуры

&НаКлиенте
Процедура ОтборСкладПриИзменении(Элемент)
    ОтборСкладПриИзмененииНаСервере();
КонецПроцедуры

Метод 2. Оптимизированный запрос для высоконагруженных систем

Предыдущий метод отлично подходит для небольших и средних баз данных. Однако, если в вашей таблице документов содержатся сотни тысяч или миллионы записей, использование оператора ИЛИ (OR) в запросах к СУБД приведет к существенному замедлению работы. СУБД в большинстве случаев откажется от использования индексов по полям складов и перейдет к полному сканированию таблицы (Table/Index Scan).

Чтобы избежать падения производительности, мы можем переписать запрос нашего динамического списка через конструкцию В (ВЫБРАТЬ ... ОБЪЕДИНИТЬ ВСЕ). СУБД сможет применить быстрое сканирование индекса (Index Seek) отдельно по каждому полю склада.

Рассмотрим структуру оптимизированного запроса динамического списка:


ВЫБРАТЬ
    Перемещение.Ссылка КАК Ссылка,
    Перемещение.Дата КАК Дата,
    Перемещение.Номер КАК Номер,
    Перемещение.СкладОтправитель КАК СкладОтправитель,
    Перемещение.СкладПолучатель КАК СкладПолучатель
ИЗ
    Документ.ПеремещениеТоваров КАК Перемещение
{ГДЕ
    Перемещение.Ссылка В
        (ВЫБРАТЬ
            Т.Ссылка
        ИЗ
            Документ.ПеремещениеТоваров КАК Т
        ГДЕ
            Т.СкладОтправитель = &СкладОтбор
        
        ОБЪЕДИНИТЬ ВСЕ
        
        ВЫБРАТЬ
            Т.Ссылка
        ИЗ
            Документ.ПеремещениеТоваров КАК Т
        ГДЕ
            Т.СкладПолучатель = &СкладОтбор)}

Преимущества данного подхода:

Метод 3. Программная настройка группы отбора «ИЛИ» компоновки данных

Если вы принципиально не хотите изменять текст запроса динамического списка, решить задачу можно исключительно программными средствами, манипулируя коллекцией отборов компоновки данных. Мы можем создать группу отборов с видом сравнения ГруппаИЛИ.

Проанализируем пример кода, который выполняет эту операцию на сервере. Данный метод можно вызывать при изменении значения нашего элемента отбора на форме:


&НаСервере
Процедура УстановитьОтборПоСкладуНаСервере(ВыбранныйСклад)
    
    ИмяГруппыОтбора = "ГруппаОтбораПоСкладам";
    
    // Сначала удалим старый отбор, если он существовал
    ЭлементыОтбора = Список.Отбор.Элементы;
    Для Каждого Элемент Из ЭлементыОтбора Цикл
        Если ТипЗв = Тип("ГруппаЭлементовОтбораКомпоновкиДанных") 
            И Элемент.Представление = ИмяГруппыОтбора Тогда
            ЭлементыОтбора.Удалить(Элемент);
            Прервать;
        КонецЕсли;
    КонецЦикла;
    
    // Если склад не выбран, просто выходим (фильтр очищен)
    Если Не ЗначениеЗаполнено(ВыбранныйСклад) Тогда
        Возврат;
    КонецЕсли;
    
    // Создаем новую группу ИЛИ
    НоваяГруппа = ЭлементыОтбора.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
    НоваяГруппа.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИЛИ;
    НоваяГруппа.Представление = ИмяГруппыОтбора;
    НоваяГруппа.Использование = Истина;
    
    // Добавляем условие для Склада-Отправителя
    ЭлементОтправитель = НоваяГруппа.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
    ЭлементОтправитель.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("СкладОтправитель");
    ЭлементОтправитель.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
    ЭлементОтправитель.ПравоеЗначение = ВыбранныйСклад;
    ЭлементОтправитель.Использование = Истина;
    
    // Добавляем условие для Склада-Получателя
    ЭлементПолучатель = НоваяГруппа.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
    ЭлементПолучатель.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("СкладПолучатель");
    ЭлементПолучатель.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
    ЭлементПолучатель.ПравоеЗначение = ВыбранныйСклад;
    ЭлементПолучатель.Использование = Истина;
    
КонецПроцедуры

Если в вашей конфигурации используется Библиотека стандартных подсистем (БСП), то ручную работу с коллекциями можно упростить с помощью функций общего модуля ОбщегоНазначенияКлиентСервер. Это сделает ваш код более компактным и защищенным от ошибок:


// Пример использования функций БСП для установки отбора
ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(
    Список,
    "СкладОтправитель",
    ВыбранныйСклад,
    ВидСравненияКомпоновкиДанных.Равно,
    "СкладОтбор",
    ЗначениеЗаполнено(ВыбранныйСклад)
);

Метод 4. Виртуальное соединение со справочником (Метод «Критерия отбора»)

Интересный альтернативный вариант предложили участники обсуждения в сообщении 20. Если мы хотим, чтобы отбор вел себя максимально близко к логике классических критериев отбора платформы 1С, мы можем связать таблицу документов со справочником складов в самом запросе динамического списка.

Рассмотрим следующий шаблон запроса:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    Перемещение.Ссылка КАК Ссылка,
    Перемещение.Дата КАК Дата,
    Перемещение.Номер КАК Номер,
    Перемещение.СкладОтправитель КАК СкладОтправитель,
    Перемещение.СкладПолучатель КАК СкладПолучатель
ИЗ
    Документ.ПеремещениеТоваров КАК Перемещение
    {ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Склады КАК СкладыОтбор
    ПО Перемещение.СкладОтправитель = СкладыОтбор.Ссылка 
        ИЛИ Перемещение.СкладПолучатель = СкладыОтбор.Ссылка}
{ГДЕ
    СкладыОтбор.Ссылка.* КАК СкладФильтр}

При таком подходе в динамическом списке появится виртуальное поле отбора СкладФильтр. Пользователь сможет стандартным образом настраивать его через форму настроек списка, выбирать виды сравнения «В списке», «Равно» и другие. Однако помните о необходимости использования ключевого слова РАЗЛИЧНЫЕ, так как при отборе по списку складов (если один и тот же склад указан и отправителем, и получателем в одном документе) без этой инструкции строки списка могут задвоиться.

Улучшение юзабилити: Скрытие лишних полей отбора

Чтобы упростить интерфейс и не вводить пользователя в заблуждение, хорошей практикой является скрытие стандартных полей отбора (в нашем случае СкладОтправитель и СкладПолучатель) из списка доступных полей компоновки. Это исключит ситуацию, когда пользователь случайно наложит конфликтующие фильтры.

Начиная с версии платформы 8.3.10, мы можем использовать специальный метод УстановитьОграниченияИспользованияВОтборе() динамического списка. Разместим данный код в обработчике ПриСозданииНаСервере формы списка:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Запретим пользователю интерактивно использовать детальные поля складов в отборах
    МассивЗапрещенныхПолей = Новый Массив;
    МассивЗапрещенныхПолей.Добавить("СкладОтправитель");
    МассивЗапрещенныхПолей.Добавить("СкладПолучатель");
    
    Список.УстановитьОграниченияИспользованияВОтборе(МассивЗапрещенныхПолей);
    
КонецПроцедуры

Таким образом, мы рассмотрели различные способы решения задачи. Выбор конкретного метода зависит от объемов вашей информационной базы и требований к гибкости настроек для конечного пользователя. Для небольших баз отлично подойдет Метод 1 или Метод 3, тогда как для высоконагруженных систем настоятельно рекомендуется использовать оптимизированный запрос из Метода 2.

← На главную