Замена данных в ТаблицеЗначений на встроенном языке: поиск строки, индексы, массовое обновление
Содержание
Чтобы заменить значение в таблице значений 1С, найдите нужную строку и присвойте новое значение её полю: Строка.Цена = 150. Строку получают по индексу (ТЗ[0]), методом Найти() (первая подходящая строка) или НайтиСтроки() (все строки по отбору из нескольких полей). Если нужно заменить значение сразу во всей колонке, хватит одного вызова ЗаполнитьЗначения().
Выбор способа зависит от задачи. Одна строка по известному номеру — обращение по индексу. Одно совпадение по одному полю — Найти(). Несколько полей или все совпадения — НайтиСтроки() плюс индекс, если таблица большая и поиск идёт в цикле. Код ниже работает в 8.3 на сервере; про формы и клиент — отдельный раздел.
Как устроена ТаблицаЗначений и что именно меняется
Таблица значений — коллекция строк СтрокаТаблицыЗначений с набором колонок. Строка — это ссылка на запись внутри таблицы. Отсюда главное правило:
- ✅ присвоили значение полю найденной строки — изменилась сама таблица, «записывать» ничего не надо;
- ✅ строка, полученная из
НайтиСтроки(), — та же строка таблицы, а не копия; - ❌ таблица, полученная через
Скопировать(), — уже другой объект, правки в ней на исходную не влияют.
Создадим таблицу, на которой будем показывать все примеры:
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(100)));
ТЗ.Колонки.Добавить("Склад", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(50)));
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
ТЗ.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.Номенклатура = "Бумага А4";
НоваяСтрока.Склад = "Основной";
НоваяСтрока.Количество = 10;
НоваяСтрока.Цена = 320;
Поле строки доступно двумя способами. Через точку — когда имя колонки известно при написании кода. Через квадратные скобки — когда имя приходит в переменной:
ТЗ[0].Цена = 350; // первая строка, индекс 0
ИмяКолонки = "Цена";
ТЗ[0][ИмяКолонки] = 360; // то же самое, имя в переменной
Строка = ТЗ.Получить(0); // явный метод вместо квадратных скобок
Строка.Количество = Строка.Количество + 5;
Индексы строк начинаются с нуля. Последняя строка — ТЗ[ТЗ.Количество() - 1]. Обращение к индексу за пределами таблицы вызывает ошибку, поэтому перед ТЗ[0] проверяйте ТЗ.Количество() > 0.
Типизированные колонки приводят значение
Если у колонки задано описание типов, присвоенное значение приводится к нему. В колонку с КвалификаторыСтроки(50) длинная строка ляжет обрезанной до 50 символов. В числовую колонку строка вида "abc" превратится в 0, без ошибки. Колонка без описания типов принимает что угодно.
Это удобно, но коварно: замена «молча» даёт не то значение, которое вы присвоили. Если результат замены выглядит странно — сначала проверьте ТЗ.Колонки.Цена.ТипЗначения.
Поиск строки для замены по ключевым полям
Найти: первое совпадение
Найти(Значение, Колонки) возвращает первую строку, где значение встретилось в одной из перечисленных колонок, или Неопределено. Второй параметр — строка с именами колонок через запятую. Без него поиск идёт по всем колонкам.
Строка = ТЗ.Найти("Бумага А4", "Номенклатура");
Если Строка <> Неопределено Тогда
Строка.Цена = 340;
КонецЕсли;
Ограничения метода:
- 🔸 ищет одно значение, а не сочетание полей;
Найти("Основной", "Номенклатура, Склад")найдёт строку, где «Основной» стоит хотя бы в одной из двух колонок; - 🔸 возвращает только первую строку, остальные дубли останутся со старым значением;
- 🔸 без проверки на
Неопределенокод упадёт, если строки нет.
Поиск по нескольким полям
Когда ключ составной — номенклатура и склад, договор и период — нужен отбор по структуре. Это работа НайтиСтроки(), о нём ниже. Обходить таблицу циклом с условием Если Строка.Номенклатура = ... И Строка.Склад = ... тоже допустимо, но на больших таблицах медленнее и длиннее в коде.
Сравнение идёт по значению и типу
Число 1 и строка "1" — разные значения, совпадения не будет. Та же история с пустыми значениями: Неопределено, пустая ссылка и пустая строка не равны друг другу. Если поиск «не находит» строку, которая точно есть, выведите тип: Сообщить(ТипЗнч(Строка.Склад)).
Как заменить значения в таблице значений через НайтиСтроки
НайтиСтроки(ПараметрыОтбора) принимает структуру «имя колонки — значение» и возвращает массив всех строк, где совпали все поля. Если ничего не нашлось — пустой массив, а не Неопределено.
Замена во всех найденных строках
Отбор = Новый Структура("Номенклатура, Склад", "Бумага А4", "Основной");
НайденныеСтроки = ТЗ.НайтиСтроки(Отбор);
Для Каждого Строка Из НайденныеСтроки Цикл
Строка.Цена = 340;
КонецЦикла;
Цикл идёт по массиву, а не по таблице. Поэтому внутри можно менять даже те поля, по которым искали, — массив от этого не изменится.
Замена или добавление (если строки нет)
Частая задача — «обновить строку, а если её нет, создать». Например, собрать остатки из нескольких источников:
Процедура ЗаписатьОстаток(ТЗ, Номенклатура, Склад, Количество)
Отбор = Новый Структура("Номенклатура, Склад", Номенклатура, Склад);
НайденныеСтроки = ТЗ.НайтиСтроки(Отбор);
Если НайденныеСтроки.Количество() = 0 Тогда
Строка = ТЗ.Добавить();
ЗаполнитьЗначенияСвойств(Строка, Отбор);
Иначе
Строка = НайденныеСтроки[0];
КонецЕсли;
Строка.Количество = Строка.Количество + Количество;
КонецПроцедуры
ЗаполнитьЗначенияСвойств(Приёмник, Источник) копирует одноимённые свойства. Здесь он переносит ключевые поля из структуры отбора в новую строку, без ручного присваивания каждого поля. Тем же методом удобно заменить сразу несколько полей одной строки: подготовьте структуру с новыми значениями и передайте её как источник.
НовыеДанные = Новый Структура("Цена, Количество", 355, 12);
ЗаполнитьЗначенияСвойств(Строка, НовыеДанные);
Третьим параметром можно ограничить список свойств, четвёртым — перечислить исключения.
Индексы: когда поиск становится узким местом
Без индекса НайтиСтроки() просматривает таблицу целиком. На сотне строк разницы нет. Проблема начинается, когда поиск стоит внутри цикла: на каждую из 20 000 строк документа ищется соответствие в таблице на 50 000 строк. Это уже порядка миллиарда сравнений, и обработка «висит» минутами.
Индекс добавляют один раз, до цикла:
ТЗ.Индексы.Добавить("Номенклатура, Склад");
Правила, которые стоит держать в голове:
- 📌 индекс строят по тем колонкам, которые входят в отбор
НайтиСтроки(); составной индекс указывают одной строкой через запятую; - 📌 индекс обслуживается платформой сам: после добавления строк и изменения значений его не надо пересоздавать вручную;
- 📌 у индекса есть цена — память и время на каждое изменение индексированных полей; на маленьких таблицах и при однократном поиске он не нужен;
- 📌 если в цикле массово меняется именно индексированная колонка, бывает быстрее удалить индекс (
ТЗ.Индексы.Очистить()), сделать замену и добавить его заново.
| Сценарий | Что использовать |
|---|---|
| Таблица до нескольких сотен строк, поиск разовый | Найти() или НайтиСтроки() без индекса |
| Поиск по одному-двум полям в цикле, таблица на тысячи строк | НайтиСтроки() + Индексы.Добавить() |
| Нужен только факт наличия ключа или одно значение по ключу | Соответствие вместо таблицы |
| Данные приходят из базы и их надо сопоставить с другими данными | запрос с временной таблицей |
Соответствие как быстрый словарь
Если по ключу нужно получить одно значение — цену, курс, ставку, — таблица не обязательна. Соответствие ищет по ключу без перебора:
Цены = Новый Соответствие;
Для Каждого Строка Из ТаблицаЦен Цикл
Цены.Вставить(Строка.Номенклатура, Строка.Цена);
КонецЦикла;
Для Каждого Строка Из ТЗ Цикл
НоваяЦена = Цены.Получить(Строка.Номенклатура);
Если НоваяЦена <> Неопределено Тогда
Строка.Цена = НоваяЦена;
КонецЕсли;
КонецЦикла;
Для составного ключа такой приём хуже: ключ приходится склеивать или строить вложенные соответствия. Там удобнее индекс в таблице.
Массовая замена без поиска строк
Одно значение во всю колонку
ЗаполнитьЗначения(Значение, Колонки) записывает значение во все строки. Например, обнулить количество перед пересчётом или выставить один склад:
ТЗ.ЗаполнитьЗначения(0, "Количество");
ТЗ.ЗаполнитьЗначения("Резервный", "Склад");
Метод проходит по всем строкам таблицы. Заменить так значения только в части строк нельзя — для этого отбор и цикл.
Заменить колонку целиком из массива
Если новые значения уже собраны в массив той же длины и в том же порядке, используйте ЗагрузитьКолонку(Массив, Колонка). Обратная операция — ВыгрузитьКолонку():
Цены = ТЗ.ВыгрузитьКолонку("Цена");
Для Индекс = 0 По Цены.ВГраница() Цикл
Цены[Индекс] = Окр(Цены[Индекс] * 1.1, 2);
КонецЦикла;
ТЗ.ЗагрузитьКолонку(Цены, "Цена");
Пример поднимает цены на 10%: строка с ценой 320 получит 352, с ценой 355 — 390,50.
Заменить строки через свёртку и копию
Иногда «заменить» означает «привести к итогам». Свернуть("Номенклатура, Склад", "Количество") склеивает дубли по ключу и суммирует количество. А Скопировать(Отбор, "Номенклатура, Цена") даёт новую таблицу только с нужными строками и колонками — изменения в ней на исходную не повлияют.
Замена на стороне запроса
Если таблицу вы получили запросом, проще не исправлять её после выгрузки, а сразу получить нужные значения. Таблицу значений можно передать в запрос параметром, поместить во временную таблицу и соединить с данными базы через ВЫБОР и ЕСТЬNULL. Для сопоставления десятков тысяч строк с данными регистров это обычно быстрее циклов на встроенном языке.
Формы, клиент и табличные части
Тип ТаблицаЗначений на тонком клиенте и в веб-клиенте недоступен. Реквизит формы такого типа на клиенте выглядит как ДанныеФормыКоллекция. У неё свой набор методов: НайтиСтроки() есть, Найти() и Индексы нет. Поле строки меняется так же, присваиванием:
&НаКлиенте
Процедура ОбновитьЦену(Команда)
Отбор = Новый Структура("Номенклатура", "Бумага А4");
Для Каждого Строка Из Товары.НайтиСтроки(Отбор) Цикл
Строка.Цена = 340;
КонецЦикла;
КонецПроцедуры
Для тяжёлой обработки переведите данные на сервер в настоящую таблицу значений, сделайте замену там и верните обратно:
&НаСервере
Процедура ПересчитатьНаСервере()
ТЗ = РеквизитФормыВЗначение("Товары");
ТЗ.Индексы.Добавить("Номенклатура");
// ... замена значений ...
ЗначениеВРеквизитФормы(ТЗ, "Товары");
КонецПроцедуры
Табличная часть документа или справочника — не таблица значений, но методы Найти() и НайтиСтроки() у неё тоже есть, и поле строки меняется присваиванием. Чтобы применить к ней всё описанное выше, её выгружают методом Выгрузить(), правят и загружают обратно через Загрузить(). После этого объект ещё нужно записать, иначе изменения останутся только в памяти.
Ошибки при замене значений и как их найти
Удаление строк во время обхода
Замена часто идёт вместе с удалением лишних строк. Удалять строки в цикле Для Каждого по самой таблице нельзя: после удаления текущей строки следующая сдвигается на её место и пропускается. Два надёжных способа.
Первый — удалить строки по массиву, полученному заранее. Массив хранит строки, а не номера, поэтому удаление его не ломает:
Для Каждого Строка Из ТЗ.НайтиСтроки(Новый Структура("Количество", 0)) Цикл
ТЗ.Удалить(Строка);
КонецЦикла;
Второй — обратный цикл по индексам с фиксированной границей:
Всего = ТЗ.Количество();
Для Сч = 1 По Всего Цикл
Индекс = Всего - Сч;
Если ТЗ[Индекс].Количество = 0 Тогда
ТЗ.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
Граница Всего посчитана до цикла. Строки удаляются с конца, поэтому индексы ещё не просмотренных строк не меняются.