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