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