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

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

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

В этой статье мы подробно разберем, почему возникает популярная ошибка "Значение не является значением объектного типа" при поиске, почему стандартный перебор работает медленно и как правильно оптимизировать этот процесс несколькими способами.

Выясняем причину ошибки при использовании метода Найти()

Рассмотрим исходный фрагмент кода, который вызывал ошибку у автора темы:


Табл1 = Док.Взаиморасчеты.Найти(Строка("№ ") + Строка(НомерЛСчета), "ЛицевойСчет");
Если Табл1 <> Неопределено Тогда
   Сообщить("Не найдено");
Иначе
   Табл1.СуммаПриход = 200;
КонецЕсли;

Здесь допущена классическая логическая ошибка в условии Если...Тогда. Метод Найти() для табличной части возвращает строку таблицы, если значение найдено, и Неопределено, если значение отсутствует.

В коде автора условие написано наоборот: если строка найдена (Табл1 <> Неопределено), выводится сообщение "Не найдено". Если же строка не найдена, управление переходит в ветку Иначе, где программа пытается обратиться к свойству СуммаПриход у значения Неопределено (которое содержится в переменной Табл1). Это и вызывает ошибку "Значение не является значением объектного типа (СуммаПриход)".

Правильный вариант проверки должен выглядеть следующим образом:


Если Табл1 = Неопределено Тогда
   Сообщить("Строка с таким лицевым счетом не найдена!");
Иначе
   Табл1.СуммаПриход = 200;
КонецЕсли;

Проблема поиска по части строки в табличной части

Вторая проблема, с которой столкнулся автор — метод Найти() ищет только по точному совпадению значения. Если в колонке "ЛицевойСчет" хранится длинная строка (например, "Лицевой счет № 46600 от 27.08.2016 г."), а мы пытаемся найти ее по подстроке "№ 46600", метод Найти() всегда будет возвращать Неопределено.

Также важно помнить о производительности. Если в колонке "ЛицевойСчет" хранится ссылка на справочник, а в цикле выполняется конструкция Строка(Строка.ЛицевойСчет), платформа для каждой строки табличной части будет делать отдельный неявный запрос к базе данных, чтобы получить текстовое представление ссылки. При работе со 100 500 строками это приведет к катастрофическому падению скорости работы.

Ниже мы рассмотрим три надежных и быстрых способа решения этой задачи.

Способ 1. Предварительный поиск ссылки (Рекомендуемый базовый способ)

Если колонка табличной части имеет тип ссылки на справочник (например, "Лицевые счета"), наиболее правильным решением будет сначала найти саму ссылку в справочнике по подстроке (наименованию), а затем выполнить точный поиск по этой ссылке в табличной части.

Разберем этот алгоритм по шагам:


// 1. Ищем элемент справочника по наименованию (части строки)
СсылкаЛС = Справочники.ЛицевыеСчета.FindByName("№ " + НомерЛСчета); // НайтиПоНаименованию

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

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

Способ 2. Использование запроса для поиска по индексу строки

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

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


// Находим ссылку на лицевой счет
СсылкаЛС = Справочники.ЛицевыеСчета.НайтиПоНаименованию("№ " + НомерЛСчета);

Если ЗначениеЗаполнено(СсылкаЛС) Тогда
   
   Запрос = Новый Запрос;
   Запрос.Текст = 
       "ВЫБРАТЬ
       |   Взаиморасчеты.НомерСтроки КАК НомерСтроки
       |ИЗ
       |   Документ.НужныйДокумент.Взаиморасчеты КАК Взаиморасчеты
       |ГДЕ
       |   Взаиморасчеты.Ссылка = &СсылкаДокумента
       |   И Взаиморасчеты.ЛицевойСчет = &ЛицевойСчет";
   
   Запрос.УстановитьПараметр("СсылкаДокумента", Док.Ссылка);
   Запрос.УстановитьПараметр("ЛицевойСчет", СсылкаЛС);
   
   РезультатЗапроса = Запрос.Выполнить();
   Выборка = РезультатЗапроса.Выбрать();
   
   Пока Выборка.Следующий() Цикл
       // Получаем строку напрямую по индексу. 
       // НомерСтроки в 1С начинается с 1, а индекс коллекции с 0.
       СтрокаТЧ = Док.Взаиморасчеты[Выборка.НомерСтроки - 1];
       СтрокаТЧ.СуммаПриход = Число(ОстатокТело);
   КонецЦикла;
   
КонецЕсли;

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

Способ 3. Выгрузка в Таблицу значений с индексацией колонок

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

Выполним следующие шаги:

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

Проанализируем пример кода:


// Выгружаем данные в ТЗ
ТаблицаВзаиморасчетов = Док.Взаиморасчеты.Выгрузить();

// Добавляем индекс для моментального поиска
ТаблицаВзаиморасчетов.Индексы.Добавить("ЛицевойСчет");

СсылкаЛС = Справочники.ЛицевыеСчета.НайтиПоНаименованию("№ " + НомерЛСчета);

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

Использование индексации колонок в таблицах значений сокращает время поиска до минимума при многократных обращениях к коллекции.

← На главную