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