Правка ячеек ТаблицыЗначений: по индексу, через поиск, отбором и пакетом
Содержание
Значение в таблице значений 1С меняют присваиванием полю строки: Строка.Цена = 150;. Саму строку получают одним из пяти способов: по индексу ТЗ[0], методом Найти(), методом НайтиСтроки() со структурой отбора, обходом Для Каждого или сразу для всей колонки через ЗаполнитьЗначения() и ЗагрузитьКолонку().
Строка таблицы — это ссылка на данные внутри таблицы, а не копия. Поэтому изменить значение можно в любой переменной, где лежит строка: в элементе массива, который вернул НайтиСтроки(), в переменной цикла, в результате Найти(). Таблица изменится сразу, «сохранять» её отдельно не нужно.
Всё ниже относится к платформе 8.3 и работает одинаково в любой конфигурации: ТаблицаЗначений — объект платформы, а не БП или УТ.
Как устроена строка и чем менять значение в ней
У строки таблицы значений (СтрокаТаблицыЗначений) три способа обратиться к ячейке:
- 🔹 через точку по имени колонки:
Строка.Количество = 5; - 🔹 через квадратные скобки по имени:
Строка["Количество"] = 5; - 🔹 через квадратные скобки по номеру колонки:
Строка[2] = 5;(нумерация с нуля)
Точка удобна, когда имя колонки известно заранее. Скобки с именем нужны, когда имя приходит в переменной — например, вы обходите ТЗ.Колонки и правите каждую колонку по очереди.
// Обнулить все числовые колонки в строке
Для Каждого Колонка Из ТЗ.Колонки Цикл
Если Колонка.ТипЗначения.СодержитТип(Тип("Число")) Тогда
Строка[Колонка.Имя] = 0;
КонецЕсли;
КонецЦикла;
Если колонки с таким именем нет, на этой строке кода будет ошибка выполнения Поле объекта не обнаружено. Синтаксический контроль её не ловит: опечатку в имени колонки вы увидите только при запуске.
Номер колонки в скобках использовать можно, но опасно. Стоит кому-то вставить колонку в начало через ТЗ.Колонки.Вставить(), и Строка[2] начнёт писать в чужое поле. Имя надёжнее.
Способ 1. Изменение по индексу строки
Если известна позиция строки, её берут по индексу. Нумерация строк, как и колонок, начинается с нуля.
ТЗ[0].Цена = 120; // первая строка
ТЗ[ТЗ.Количество() - 1].Цена = 0; // последняя строка
ТЗ.Получить(3).Цена = 99; // то же, что ТЗ[3]
Запись ТЗ[3] и вызов ТЗ.Получить(3) равнозначны. На практике скобки встречаются чаще, Получить() удобнее читается в длинных выражениях.
Когда это уместно:
- ✅ таблица только что заполнена в известном порядке, и вы правите первую или последнюю строку;
- ✅ у вас уже есть индекс, полученный через
ТЗ.Индекс(Строка); - ✅ обход идёт классическим счётчиком
Для Сч = 0 По ТЗ.Количество() - 1 Цикл.
Если индекс выходит за пределы, получите ошибку Индекс находится за границами диапазона. Типичная причина — забыли вычесть единицу из Количество() или обращаетесь к ТЗ[0] пустой таблицы. Перед обращением проверяйте ТЗ.Количество() > 0.
Индекс — не постоянный адрес строки. После Сортировать(), Удалить(), Вставить() или Сдвинуть() по индексу 3 лежит уже другая строка. Если строку надо найти ещё раз позже, сохраните в переменную саму строку, а не её номер.
Способ 2. Найти строку методом Найти и поменять значение
Найти(Значение, Колонки) возвращает первую строку, в которой встретилось значение, или Неопределено. Второй параметр — имена колонок через запятую. Если его не указать, поиск идёт по всем колонкам, а это и медленнее, и рискованнее: число 5 может найтись не в той колонке.
Строка = ТЗ.Найти(Номенклатура, "Номенклатура");
Если Строка <> Неопределено Тогда
Строка.Количество = Строка.Количество + 1;
Иначе
Строка = ТЗ.Добавить();
Строка.Номенклатура = Номенклатура;
Строка.Количество = 1;
КонецЕсли;
Это классический приём «найти или добавить». Без проверки на Неопределено код упадёт, как только значения в таблице не окажется.
Ограничения Найти():
- 🔸 ищет только одно значение — по двум полям сразу (товар и склад) не найдёт;
- 🔸 возвращает только первое совпадение — дубли останутся нетронутыми;
- 🔸 сравнивает на точное равенство — строки «Иванов» и «иванов» разные, у чисел учитываются все знаки.
Если нужны несколько условий или все совпадения, переходите к способу 4.
Способ 3. Массовое изменение значений таблицы циклом и пакетом
Самый частый случай — поменять значение во всех строках или в строках, которые подходят под условие. Для этого обходят таблицу циклом Для Каждого.
Для Каждого Строка Из ТЗ Цикл
Если Строка.Количество = 0 Тогда
Продолжить;
КонецЕсли;
Строка.Сумма = Строка.Цена * Строка.Количество;
КонецЦикла;
Менять значения внутри Для Каждого безопасно. Опасно другое — удалять строки в этом же цикле: обход собьётся и часть строк будет пропущена. Для удаления используйте обратный цикл с заранее зафиксированным количеством:
Всего = ТЗ.Количество();
Для Сч = 1 По Всего Цикл
Строка = ТЗ[Всего - Сч];
Если Строка.Количество = 0 Тогда
ТЗ.Удалить(Строка);
КонецЕсли;
КонецЦикла;
Удаление строки сдвигает только строки после неё, а их цикл уже прошёл. Поэтому ни одна строка не пропускается.
Одно значение во всю колонку: ЗаполнитьЗначения
Если всем строкам нужно поставить одно и то же значение, цикл не нужен:
ТЗ.ЗаполнитьЗначения(Ложь, "Отмечено");
ТЗ.ЗаполнитьЗначения(0, "Скидка, СуммаСкидки");
Можно перечислить несколько колонок через запятую. Без второго параметра значение запишется во все колонки — так делать почти никогда не нужно.
Колонка целиком из массива: ЗагрузитьКолонку
Когда новые значения уже лежат в массиве в том же порядке, что и строки, используйте ЗагрузитьКолонку(). Обратная операция — ВыгрузитьКолонку().
Цены = ТЗ.ВыгрузитьКолонку("Цена");
Для Индекс = 0 По Цены.ВГраница() Цикл
Цены[Индекс] = Окр(Цены[Индекс] * 1.1, 2);
КонецЦикла;
ТЗ.ЗагрузитьКолонку(Цены, "Цена");
Пример поднимает цены на 10%: 100 превращается в 110, 45,50 — в 50,05. Значения раскладываются по строкам строго по порядку, поэтому перед загрузкой сверяйте Цены.Количество() с ТЗ.Количество() и не сортируйте таблицу между выгрузкой и загрузкой.
Способ 4. НайтиСтроки и структура отбора для сложных условий
НайтиСтроки(Отбор) принимает структуру, где ключ — имя колонки, а значение — что ищем. Условия соединяются через «И». Возвращается массив строк, пустой, если ничего не нашлось.
Отбор = Новый Структура;
Отбор.Вставить("Склад", СкладОсновной);
Отбор.Вставить("ЕдиницаИзмерения", ЕдиницаШтука);
НайденныеСтроки = ТЗ.НайтиСтроки(Отбор);
Для Каждого Строка Из НайденныеСтроки Цикл
Строка.Склад = СкладРезервный;
КонецЦикла;
Здесь хорошо видно, что массив хранит ссылки на строки. Меняем Строка.Склад в массиве — меняется таблица.
Короткая запись того же отбора — через конструктор структуры:
НайденныеСтроки = ТЗ.НайтиСтроки(Новый Структура("Склад, ЕдиницаИзмерения", СкладОсновной, ЕдиницаШтука));
Что НайтиСтроки() не умеет: сравнение «больше», «меньше», «содержит», условие «ИЛИ». Только точное равенство по каждому полю. Для таких условий комбинируют: отбирают по равенству, а остальное проверяют внутри цикла.
Для Каждого Строка Из ТЗ.НайтиСтроки(Новый Структура("Склад", СкладОсновной)) Цикл
Если Строка.Остаток < 10 ИЛИ Строка.Цена = 0 Тогда
Строка.ТребуетПроверки = Истина;
КонецЕсли;
КонецЦикла;
Массив из НайтиСтроки() удобен и для удаления: он не зависит от индексов, поэтому ТЗ.Удалить(Строка) для каждого его элемента ничего не сбивает.
Сравнение способов поиска строки
| Способ | Когда брать | Что вернёт | Подводный камень |
|---|---|---|---|
ТЗ[Индекс] | позиция известна | одну строку | индекс меняется после сортировки и удаления |
Найти() | одно значение, нужна первая строка | строку или Неопределено | без проверки на Неопределено — ошибка |
НайтиСтроки() | несколько полей, все совпадения | массив строк | только равенство, без «больше» и «ИЛИ» |
Для Каждого | условие любой сложности | каждую строку | медленно на больших таблицах без индекса |
ЗаполнитьЗначения() | одно значение во все строки | ничего, правит сразу | без указания колонок затрёт всю таблицу |
Типы колонок и формат значений: что запишется на самом деле
При создании колонки можно указать её тип. От этого зависит, что окажется в ячейке после присваивания.
ТЗ.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
ТЗ.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(10)));
ТЗ.Колонки.Добавить("Комментарий"); // без типа — любое значение
Если колонка типизирована, платформа приводит присваиваемое значение к её описанию типов:
- 🔢 число с лишними знаками округляется до точности колонки: в колонку
Число(15, 2)значение10.456запишется как10.46; - ✂️ строка длиннее квалификатора обрезается:
"АРТ-0001234"(11 символов) в колонке длиной 10 станет"АРТ-000123"; - 🚫 значение недопустимого типа превращается в пустое значение типа колонки: строка
"abc"в числовой колонке даёт0.
Ошибки при этом не возникает. Данные молча становятся другими, поэтому типизация колонок полезна и одновременно опасна: она защищает от мусора, но скрывает ошибки в расчётах.
Колонка без типа принимает что угодно. Зато такую таблицу нельзя передать в запрос как параметр ИЗ &ТЗ — запрос требует колонок с явными типами.
Числа и строки для вывода
Частая ошибка — хранить в таблице уже отформатированное число строкой, например "1 250,00 руб.". Потом по этой колонке нельзя сложить итог, отсортировать по возрастанию или сравнить с другим числом.
Правило простое: в колонке храните исходное значение, а форматируйте при выводе.
Строка.Представление = СтрШаблон("%1 шт. по %2",
Формат(Строка.Количество, "ЧГ=0"),
Формат(Строка.Цена, "ЧДЦ=2"));
Здесь ЧГ=0 убирает разделитель групп разрядов: без него 1250 выведется как «1 250». ЧДЦ=2 оставляет два знака после запятой. Склеивать число со строкой через + без Формат() не стоит: на результат влияют региональные настройки, и в тексте появятся неразрывные пробелы.
Пустые значения и ссылки
Для проверки «значение не заполнено» используйте ЗначениеЗаполнено(Строка.Контрагент), а не сравнение с Неопределено. В типизированной колонке ссылочного типа пустая ячейка содержит пустую ссылку, а не Неопределено, и прямое сравнение даст ложный результат.
Таблица значений на управляемой форме
На форме таблица значений становится коллекцией данных формы (ДанныеФормыКоллекция). Сам объект ТаблицаЗначений в тонком клиенте недоступен, поэтому правка зависит от того, где выполняется код.
На клиенте меняют текущую строку элемента формы:
ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
Если ТекущиеДанные <> Неопределено Тогда
ТекущиеДанные.Цена = 100;
КонецЕсли;
У коллекции формы тоже есть НайтиСтроки() и обход Для Каждого, так что простые правки можно сделать и на клиенте, без сервера.
На сервере для сложной обработки форму превращают в настоящую таблицу значений и обратно:
&НаСервере
Процедура ПересчитатьНаСервере()
ТЗ = РеквизитФормыВЗначение("Товары");
ТЗ.ЗаполнитьЗначения(0, "Скидка");
// ... любые методы ТаблицыЗначений
ЗначениеВРеквизитФормы(ТЗ, "Товары");
КонецПроцедуры
Если забыть ЗначениеВРеквизитФормы(), изменения останутся в локальной переменной, а на экране ничего не поменяется. Это самая частая причина вопроса «значение меняю, а в таблице на форме по-старому».
Программное присваивание ТекущиеДанные.Цена = 100 не вызывает обработчик ПриИзменении поля. Если от цены зависит сумма, пересчитайте её в том же коде.
Изменение значений в больших таблицах: как не потерять время
На таблице в 100 строк любой способ работает мгновенно. На 100 тысячах разница между подходами — секунды против минут.
Индексы для поиска
Без индекса Найти() и НайтиСтроки() перебирают всю таблицу. Если поиск идёт внутри цикла, получается перебор «каждая строка на каждую строку». Индекс по нужным колонкам решает это:
ТЗ.Индексы.Добавить("Номенклатура, Склад");
Для Каждого СтрокаДвижения Из Движения Цикл
Отбор = Новый Структура("Номенклатура, Склад",
СтрокаДвижения.Номенклатура, СтрокаДвижения.Склад);
Найденные = ТЗ.НайтиСтроки(Отбор);
Для Каждого Строка Из Найденные Цикл
Строка.Количество = Строка.Количество + СтрокаДвижения.Количество;
КонецЦикла;
КонецЦикла;
Индекс срабатывает, когда набор полей в отборе совпадает с колонками индекса. Добавляйте индекс один раз, после заполнения таблицы и перед серией поисков.
Индекс замедляет изменение тех колонок, по которым построен: платформа перестраивает его при каждой записи. Если вы массово меняете колонку Склад, а индекс построен по ней же, удалите индекс на время правки (ТЗ.Индексы.Очистить()) и создайте заново после.
Соответствие вместо повторного поиска
Когда новые значения приходят из другой таблицы, удобно сначала собрать их в Соответствие. Поиск по ключу в соответствии быстрый, а сам код проще.
НовыеЦены = Новый Соответствие;
Для Каждого СтрокаЦены Из ТаблицаЦен Цикл
НовыеЦены.Вставить(СтрокаЦены.Номенклатура, СтрокаЦены.Цена);
КонецЦикла;
Для Каждого Строка Из ТЗ Цикл
Цена = НовыеЦены.Получить(Строка.Номенклатура);
Если Цена <> Неопределено Тогда
Строка.Цена = Цена;
КонецЕсли;
КонецЦикла;
Когда правку лучше отдать запросу
Если нужно соединить таблицу с данными базы или посчитать группировки, быстрее передать её в запрос и получить новую таблицу:
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТЗ", ТЗ);
Запрос.Текст =
"ВЫБРАТЬ
| Т.Номенклатура КАК Номенклатура,
| Т.Количество КАК Количество,
| Т.Количество * 1.2 КАК КоличествоСЗапасом
|ИЗ
| &ТЗ КАК Т";
ТЗ = Запрос.Выполнить().Выгрузить();
Колонки исходной таблицы для этого должны быть типизированы. Ещё два приёма ускоряют массовую правку: Свернуть("Номенклатура", "Количество, Сумма") вместо ручного суммирования дублей и ЗаполнитьЗначения() вместо цикла с одним присваиванием.
Частые вопросы
Можно ли изменить значение, если таблица передана в процедуру как параметр?
ТЗ.Скопировать().Чем отличается изменение табличной части документа от таблицы значений?
Строка.Поле = Значение. Но изменения табличной части попадут в базу только после записи документа, а таблица значений живёт лишь в памяти.Как переименовать колонку или сменить её тип, не теряя данные?
ТЗ.Колонки.Найти("Цена").Имя = "ЦенаПродажи". Тип у существующей колонки не меняется: добавьте новую колонку с нужным типом, перенесите значения через ВыгрузитьКолонку() и ЗагрузитьКолонку(), затем удалите старую.Как узнать, в какой таблице находится строка?
Строка.Владелец() вернёт таблицу значений, а Владелец().Индекс(Строка) — текущий номер строки в ней.