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