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