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

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

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

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

Посмотрим на частую ошибку начинающих разработчиков: обход справочника Номенклатура в цикле, внутри которого запускается цикл по типам цен, а внутри него — каждый раз создается и выполняется объект Запрос. Такой подход создает критическую нагрузку на СУБД из-за сотен и тысяч отдельных обращений к базе данных.

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

Способ 1. Удаление строк через обратный цикл по индексу

Проанализируем стандартную проблему прямого цикла: если обходить строки от 0 до конца таблицы и удалять неподходящие строки методом Удалить(), индексы оставшихся элементов смещаются влево. В результате следующая после удаленной строка пропускается и не проверяется.

Чтобы избежать пропуска строк, разберем обход коллекции с конца к началу (обратный цикл). В этом случае удаление текущего элемента сдвигает только уже проверенные строки с большими индексами, не влияя на оставшиеся в начале таблицы элементы:


Индекс = ТаблицаЦен.Количество() - 1;
Пока Индекс >= 0 Цикл
    Если ТаблицаЦен[Индекс].ЗакупочнаяЦена < ПолеВвода2 Тогда
        ТаблицаЦен.Удалить(Индекс);
    КонецЕсли;
    Индекс = Индекс - 1;
КонецЦикла;

Способ 2. Накопление удаляемых строк в массив

Рассмотрим альтернативный способ, который часто применяется для повышения читаемости кода. Сначала выполняется обход коллекции конструкцией Для Каждого, в процессе которого ссылки на неподходящие строки собираются в объект Массив. Затем строки удаляются отдельным циклом:


УдаляемыеСтроки = Новый Массив;

Для Каждого Стр Из ТаблицаЦен Цикл
    Если Стр.ЗакупочнаяЦена < ПолеВвода2 Тогда
        УдаляемыеСтроки.Добавить(Стр);
    КонецЕсли;
КонецЦикла;

Для Каждого Стр Из УдаляемыеСтроки Цикл
    ТаблицаЦен.Удалить(Стр);
КонецЦикла;

Этот метод исключает путаницу с индексами, так как метод Удалить() коллекции ТаблицаЗначений умеет принимать в качестве параметра не только числовой индекс, но и прямую строку таблицы (СтрокаТаблицыЗначений).

Способ 3. Перенос подходящих строк в новую таблицу

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


ИтоговаяТЗ = ТаблицаЦен.СкопироватьКолонки();

Для Каждого Стр Из ТаблицаЦен Цикл
    Если Стр.ЗакупочнаяЦена >= ПолеВвода2 Тогда
        ЗаполнитьЗначенияСвойств(ИтоговаяТЗ.Добавить(), Стр);
    КонецЕсли;
КонецЦикла;

ТаблицаЦен = ИтоговаяТЗ;

Способ 4. Фильтрация существующей таблицы через временную таблицу в запросе

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


Запрос = Новый Запрос;
МВТ = Новый МенеджерВременныхТаблиц;
Запрос.МенеджерВременныхТаблиц = МВТ;

Запрос.Текст = 
"ВЫБРАТЬ * ПОМЕСТИТЬ ВремТЗ ИЗ &ИсходнаяТЗ КАК ИсходнаяТЗ;
|ВЫБРАТЬ * ИЗ ВремТЗ ГДЕ ЗакупочнаяЦена >= &ПорогЦены";

Запрос.УстановитьПараметр("ИсходнаяТЗ", ТаблицаЦен);
Запрос.УстановитьПараметр("ПорогЦены", ПолеВвода2);

ТаблицаЦен = Запрос.Выполнить().Выгрузить();

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

Архитектурно правильное решение: получение и фильтрация цен единым запросом

Выясним, как с самого начала избежать проблемы удаления строк. Вместо перебора объектов Справочники.Номенклатура.Выбрать() и выполнения запросов в циклах, актуальные цены необходимо получать одним пакетным запросом к виртуальной таблице РегистрСведений.ЦеныНоменклатуры.СрезПоследних.

Разберем по шагам, как сгруппировать разные типы цен в одну строку по номенклатуре и сразу отсечь позиции с ценой ниже порога:

  1. Обращаемся к виртуальной таблице СрезПоследних, передавая параметры периода и условий отбора в параметры виртуальной таблицы (а не в блок ГДЕ). Это гарантирует получение среза актуальных цен, а не всей истории записей регистра.
  2. Используем конструкцию ВЫБОР КОГДА совместно с агрегатной функцией МАКСИМУМ для разворачивания типов цен в отдельные колонки строки.
  3. Используем блок ИМЕЮЩИЕ для фильтрации итоговых значений, отсекая строки, где закупочная цена не достигает заданного значения.
  4. Связывание и поиск выполняем строго по полю Ссылка, исключая соединение по текстовому полю Наименование.

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


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

Запрос.УстановитьПараметр("Период", ТекущаяДата());
Запрос.УстановитьПараметр("ТипЦеныЗакупочная", Справочники.ТипыЦенНоменклатуры.НайтиПоНаименованию("Закупочная"));
Запрос.УстановитьПараметр("ТипЦеныРозничная", Справочники.ТипыЦенНоменклатуры.НайтиПоНаименованию("Розничная"));
Запрос.УстановитьПараметр("НижнийПредел", ПолеВвода2);

ТабличноеПоле1 = Запрос.Выполнить().Выгрузить();

Ограничения стандартных методов платформы

Обратите внимание на ограничения встроенных функций:

  1. Метод ТаблицаЗначений.НайтиСтроки() принимает структуру отбора, которая выполняет поиск только по строгому равенству значений. Выполнить отбор по условиям «больше», «меньше» или «не равно» с помощью этого метода невозможно.
  2. При работе в обычном приложении при массовой обработке строк элемента управления ТабличноеПоле рекомендуется выгружать данные в объект ТаблицаЗначений, выполнять обработку в оперативной памяти и загружать результат обратно, чтобы избежать постоянной перерисовки графического интерфейса.
← На главную