Как выбрать максимальную цену номенклатуры из табличной части документа

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

В практике разработки на платформе 1С часто возникает задача автоматического создания документов на основании других объектов. Типичный пример — создание документа УстановкаЦенНоменклатуры при проведении документа ПриобретениеТоваровУслуг. Однако специфика табличных частей такова, что одна и та же Номенклатура может встретиться в документе несколько раз с разной ценой (например, из-за разных партий или условий поставки). Поскольку регистры сведений, хранящие цены, не допускают записи дублирующихся ключей (номенклатура + регистратор), нам необходимо программно отобрать только одну строку для каждой позиции — ту, где цена является максимальной.

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

Способ 1: Использование объекта Соответствие (работа в памяти)

Проанализируем ситуацию: при проведении документа все данные его табличной части уже загружены в память сервера приложений и находятся в объекте ЭтотОбъект. Обращение к базе данных через запрос в данном случае может быть избыточным, так как оно создает дополнительную нагрузку на СУБД и требует ресурсов на сериализацию данных. Рассмотрим наиболее элегантный и производительный способ решения «в памяти» с использованием объекта Соответствие.

Алгоритм действий:

  1. Создаем пустое соответствие, где ключом будет выступать Номенклатура, а значением — Цена.
  2. Обходим табличную часть документа циклом.
  3. Для каждой строки проверяем, есть ли уже такая номенклатура в соответствии.
  4. Если номенклатуры нет или текущая цена выше той, что уже сохранена, перезаписываем значение в соответствии.

Рассмотрим пример кода, реализующий эту логику:


МаксЦены = Новый Соответствие;

Для Каждого СтрокаТЧ Из ЭтотОбъект.Товары Цикл
    // Проверяем наличие номенклатуры в нашем хранилище
    ТекущаяМаксЦена = МаксЦены.Получить(СтрокаТЧ.Номенклатура);
    
    // Если записи еще нет или текущая цена в строке больше найденной ранее
    Если ТекущаяМаксЦена = Неопределено Или СтрокаТЧ.Цена > ТекущаяМаксЦена Тогда
        МаксЦены.Вставить(СтрокаТЧ.Номенклатура, СтрокаТЧ.Цена);
    КонецЕсли;
КонецЦикла;

// Теперь в соответствии МаксЦены находятся только уникальные позиции с максимальными ценами
Для Каждого Элемент Из МаксЦены Цикл
    НоваяСтрока = ДокументУстановкиЦен.Товары.Добавить();
    НоваяСтрока.Номенклатура = Элемент.Ключ;
    НоваяСтрока.Цена = Элемент.Значение;
КонецЦикла;

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

Способ 2: Использование запроса к табличной части

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

Важно помнить: если мы просто добавим в группировку поля Количество или Сумма, то СУБД вернет разные строки, если количество в них отличается, даже если номенклатура одинаковая. Поэтому сначала нужно получить чистый список «Номенклатура — Максимальная цена».

Выясним причину, по которой запрос автора темы в Сообщении №9 не работал корректно: он включил в СГРУППИРОВАТЬ ПО реквизиты, уникальные для каждой строки ТЧ. Правильный подход заключается в использовании временных таблиц.

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ТЧ_Товары.Номенклатура КАК Номенклатура,
    |    МАКСИМУМ(ТЧ_Товары.Цена) КАК Цена
    |ПОМЕСТИТЬ ВТ_МаксЦены
    |ИЗ
    |    Документ.ПриобретениеТоваровУслуг.Товары КАК ТЧ_Товары
    |ГДЕ
    |    ТЧ_Товары.Ссылка = &Ссылка
    |ГРУППИРОВАТЬ ПО
    |    ТЧ_Товары.Номенклатура
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    ВТ_МаксЦены.Номенклатура,
    |    ВТ_МаксЦены.Цена
    |ИЗ
    |    ВТ_МаксЦены КАК ВТ_МаксЦены";

Запрос.УстановитьПараметр("Ссылка", ЭтотОбъект.Ссылка);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

Пока Выборка.Следующий() Цикл
    // Обработка уникальных записей с максимальной ценой
КонецЦикла;

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

Способ 3: Сортировка таблицы значений

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

Разберем этот процесс по шагам:

  1. Выгружаем нужные колонки в ТаблицуЗначений методом Выгрузить().
  2. Сортируем таблицу: сначала по Номенклатура (по возрастанию), затем по Цена (по убыванию).
  3. При такой сортировке в каждой группе одинаковых товаров первой всегда будет идти строка с максимальной ценой.
  4. Используем метод Свернуть(), но он не подходит для выбора конкретной (первой) строки. Поэтому мы будем использовать обход с конца или вспомогательный алгоритм удаления.

Посмотрим на пример реализации:


ТЗ = Товары.Выгрузить(,"Номенклатура, Цена");
// Сортируем так, чтобы макс. цена была вверху для каждой номенклатуры
ТЗ.Сортировать("Номенклатура Возр, Цена Убыв");

КоллекцияДляУдаления = Новый Массив;
ТекущаяНоменклатура = Неопределено;

Для Каждого СтрокаТЗ Из ТЗ Цикл
    Если СтрокаТЗ.Номенклатура = ТекущаяНоменклатура Тогда
        // Эта строка имеет ту же номенклатуру, но меньшую (или равную) цену
        КоллекцияДляУдаления.Добавить(СтрокаТЗ);
    Иначе
        // Это первая строка для новой номенклатуры, она же максимальная
        ТекущаяНоменклатура = СтрокаТЗ.Номенклатура;
    КонецЕсли;
КонецЦикла;

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

Сравнение методов и рекомендации

Проанализируем ситуации, в которых стоит выбирать тот или иной метод:

  1. Производительность: Метод с использованием Соответствие (Способ 1) обычно выигрывает, так как требует всего одного прохода по коллекции и не задействует механизмы СУБД. Это критично при проведении документов в высоконагруженных системах.
  2. Читаемость кода: Код с Соответствием короче и понятнее для других разработчиков. Запросы (Способ 2) часто перегружены синтаксисом временных таблиц.
  3. Сложная логика: Если помимо максимальной цены вам нужно учитывать Характеристику, Серию или Упаковку, не забудьте добавить эти поля в ключи поиска. В Соответствии ключом может быть Структура, а в запросе — дополнительные поля в ГРУППИРОВАТЬ ПО.

Важный совет: При сравнении цен всегда обращайте внимание на единицы измерения. Если в одной строке цена указана за «штуку», а в другой — за «коробку», простое сравнение чисел будет некорректным. Перед поиском максимума необходимо привести все цены к базовой единице измерения номенклатуры, используя коэффициент упаковки.

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

← На главную