Правка ячеек ТаблицыЗначений: по индексу, через поиск, отбором и пакетом

· 10 мин

Содержание

Значение в таблице значений 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 КАК КоличествоСЗапасом
|ИЗ
|    &ТЗ КАК Т";
ТЗ = Запрос.Выполнить().Выгрузить();

Колонки исходной таблицы для этого должны быть типизированы. Ещё два приёма ускоряют массовую правку: Свернуть("Номенклатура", "Количество, Сумма") вместо ручного суммирования дублей и ЗаполнитьЗначения() вместо цикла с одним присваиванием.

Частые вопросы

Можно ли изменить значение, если таблица передана в процедуру как параметр?
Да. Таблица передаётся по ссылке, и правка строк внутри процедуры видна вызывающему коду. Чтобы работать с независимой копией, вызовите ТЗ.Скопировать().
Чем отличается изменение табличной части документа от таблицы значений?
Синтаксис правки строки тот же: Строка.Поле = Значение. Но изменения табличной части попадут в базу только после записи документа, а таблица значений живёт лишь в памяти.
Как переименовать колонку или сменить её тип, не теряя данные?
Имя меняют через свойство колонки: ТЗ.Колонки.Найти("Цена").Имя = "ЦенаПродажи". Тип у существующей колонки не меняется: добавьте новую колонку с нужным типом, перенесите значения через ВыгрузитьКолонку() и ЗагрузитьКолонку(), затем удалите старую.
Как узнать, в какой таблице находится строка?
Метод Строка.Владелец() вернёт таблицу значений, а Владелец().Индекс(Строка) — текущий номер строки в ней.