Как реализовать отбор в запросе 1С с учетом незаполненных полей на форме?

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

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

Разберем типичные ошибки при реализации такой логики и рассмотрим правильные подходы к построению запросов с динамическими условиями.

Анализ типичных ошибок в коде

Проанализируем, почему попытка модификации запроса через текстовую замену часто приводит к ошибкам или некорректным результатам:

  1. Несоответствие переменной текста запроса: Если текст присвоен напрямую свойству Запрос.Текст, а последующие вызовы СтрЗаменить() производятся над отдельной переменной ТекстЗапроса, итоговый запрос не меняется и выполняется с исходными жесткими условиями.
  2. Неправильная проверка дат: В платформе 1С незаполненная дата имеет значение пустой даты ('00010101000000'), а не Неопределено. Проверка вида Объект.ДатаНачала = Неопределено всегда возвращает Ложь. Для проверки любых типов реквизитов следует использовать функцию ЗначениеЗаполнено().
  3. Учет времени при отборе по дате окончания: Если реквизит содержит дату без времени (00:00:00), то условие Дата <= &ДатаОкончания исключит документы, созданные в течение указанного дня позже полуночи. Необходимо приводить границу к концу дня через КонецДня().
  4. Очистка таблицы-приемника: Перед заполнением табличной части новыми данными обязательно нужно вызывать метод Очистить(), иначе строки будут дублироваться при повторном поиске.

Вариант 1. Программная динамическая сборка условий (Рекомендуемый)

Наиболее надежный и производительный способ — собирать блок ГДЕ только из тех условий, значения которых фактически заданы пользователем на форме. Для этого удобно использовать массив условий.

Посмотрим на пример реализации серверной процедуры:


&НаСервере
Процедура НайтиНаСервере()
    
    Объект.АктСверки.Очистить();
    
    Запрос = Новый Запрос;
    
    МассивУсловий = Новый Массив;
    
    Если ЗначениеЗаполнено(Объект.ДатаНачала) Тогда
        МассивУсловий.Добавить("АктСверки.Дата >= &ДатаНачала");
        Запрос.УстановитьПараметр("ДатаНачала", Объект.ДатаНачала);
    КонецЕсли;
    
    Если ЗначениеЗаполнено(Объект.ДатаОкончания) Тогда
        МассивУсловий.Добавить("АктСверки.Дата <= &ДатаОкончания");
        Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(Объект.ДатаОкончания));
    КонецЕсли;
    
    Если ЗначениеЗаполнено(Объект.Контрагент) Тогда
        МассивУсловий.Добавить("АктСверки.Контрагент = &Контрагент");
        Запрос.УстановитьПараметр("Контрагент", Объект.Контрагент);
    КонецЕсли;
    
    Если ЗначениеЗаполнено(Объект.Организация) Тогда
        МассивУсловий.Добавить("АктСверки.Организация = &Организация");
        Запрос.УстановитьПараметр("Организация", Объект.Организация);
    КонецЕсли;
    
    ТекстГДЕ = "";
    Если МассивУсловий.Количество() > 0 Тогда
        ТекстГДЕ = "ГДЕ " + СтрСоединить(МассивУсловий, " И ");
    КонецЕсли;
    
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    АктСверки.Ссылка КАК АктСверки,
        |    АктСверки.Номер КАК Номер,
        |    АктСверки.Дата КАК Дата,
        |    АктСверки.Организация КАК Организация,
        |    АктСверки.Контрагент КАК Контрагент
        |ИЗ
        |    Документ.АктСверкиВзаиморасчетов КАК АктСверки
        |" + ТекстГДЕ;
    
    РезультатЗапроса = Запрос.Выполнить();
    ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
    
    Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
        НоваяСтрока = Объект.АктСверки.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, ВыборкаДетальныеЗаписи);
    КонецЦикла;
    
КонецПроцедуры

Вариант 2. Проверка пустых значений внутри самого запроса

Рассмотрим альтернативный способ, когда текст запроса остается статичным, а логика отключения фильтра переносится в условия через оператор ИЛИ. В этом случае параметры передаются в запрос всегда, независимо от заполнения.


&НаСервере
Процедура НайтиНаСервере()
    
    Объект.АктСверки.Очистить();
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    АктСверки.Ссылка КАК АктСверки,
        |    АктСверки.Номер КАК Номер,
        |    АктСверки.Дата КАК Дата,
        |    АктСверки.Организация КАК Организация,
        |    АктСверки.Контрагент КАК Контрагент
        |ИЗ
        |    Документ.АктСверкиВзаиморасчетов КАК АктСверки
        |ГДЕ
        |    (АктСверки.Дата >= &ДатаНачала ИЛИ ГОД(&ДатаНачала) = 1)
        |    И (АктСверки.Дата <= &ДатаОкончания ИЛИ ГОД(&ДатаОкончания) = 1)
        |    И (АктСверки.Контрагент = &Контрагент ИЛИ &Контрагент = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка))
        |    И (АктСверки.Организация = &Организация ИЛИ &Организация = ЗНАЧЕНИЕ(Справочник.Организации.ПустаяСсылка))";
    
    Запрос.УстановитьПараметр("ДатаНачала", Объект.ДатаНачала);
    
    ДатаОкончания = ?(ЗначениеЗаполнено(Объект.ДатаОкончания), КонецДня(Объект.ДатаОкончания), '00010101');
    Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);
    
    Запрос.УстановитьПараметр("Контрагент", Объект.Контрагент);
    Запрос.УстановитьПараметр("Организация", Объект.Организация);
    
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    Пока Выборка.Следующий() Цикл
        НоваяСтрока = Объект.АктСверки.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
    КонецЦикла;
    
КонецПроцедуры

Важное замечание по производительности: Использование конструкций вида (Поле = &Параметр ИЛИ &Параметр = ...) может приводить к неоптимальным планам выполнения запросов в СУБД (сканирование таблиц вместо поиска по индексу). На больших объемах данных рекомендуется использовать Вариант 1 с динамической сборкой условий.

Вариант 3. Использование объекта ПостроительЗапроса

Разберем механизм, предоставляемый объектом ПостроительЗапроса. Он автоматически управляет включением и отключением условий в зависимости от активности отборов.

Посмотрим на пример использования:


&НаСервере
Процедура НайтиНаСервере()
    
    Объект.АктСверки.Очистить();
    
    Построитель = Новый ПостроительЗапроса;
    Построитель.Текст = 
        "ВЫБРАТЬ
        |    АктСверки.Ссылка КАК АктСверки,
        |    АктСверки.Номер КАК Номер,
        |    АктСверки.Дата КАК Дата,
        |    АктСверки.Организация КАК Организация,
        |    АктСверки.Контрагент КАК Контрагент
        |ИЗ
        |    Документ.АктСверкиВзаиморасчетов КАК АктСверки
        |{ГДЕ
        |    АктСверки.Дата >= &ДатаНачала,
        |    АктСверки.Дата <= &ДатаОкончания,
        |    АктСверки.Контрагент.* КАК Контрагент,
        |    АктСверки.Организация.* КАК Организация}";
    
    // Заполняем доступные параметры
    Если ЗначениеЗаполнено(Объект.ДатаНачала) Тогда
        Отбор = Построитель.Отбор.Добавить("Дата");
        Отбор.ВидСравнения = ВидСравнения.БольшеИлиРавно;
        Отбор.Значение = Объект.ДатаНачала;
        Отбор.Использование = Истина;
    КонецЕсли;
    
    Если ЗначениеЗаполнено(Объект.ДатаОкончания) Тогда
        Отбор = Построитель.Отбор.Добавить("Дата");
        Отбор.ВидСравнения = ВидСравнения.МеньшеИлиРавно;
        Отбор.Значение = КонецДня(Объект.ДатаОкончания);
        Отбор.Использование = Истина;
    КонецЕсли;
    
    Если ЗначениеЗаполнено(Объект.Контрагент) Тогда
        Отбор = Построитель.Отбор.Добавить("Контрагент");
        Отбор.ВидСравнения = ВидСравнения.Равно;
        Отбор.Значение = Объект.Контрагент;
        Отбор.Использование = Истина;
    КонецЕсли;
    
    Если ЗначениеЗаполнено(Объект.Организация) Тогда
        Отбор = Построитель.Отбор.Добавить("Организация");
        Отбор.ВидСравнения = ВидСравнения.Равно;
        Отбор.Значение = Объект.Организация;
        Отбор.Использование = Истина;
    КонецЕсли;
    
    Построитель.Выполнить();
    РезультатЗапроса = Построитель.Результат;
    Выборка = РезультатЗапроса.Выбрать();
    
    Пока Выборка.Следующий() Цикл
        НоваяСтрока = Объект.АктСверки.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
    КонецЦикла;
    
КонецПроцедуры
← На главную