Изменение данных в таблице значений: от одной строки до тысяч

· 9 мин

Содержание

Чтобы обновить таблицу значений в 1С, найдите нужную строку методом Найти() или НайтиСтроки() и присвойте новое значение колонке: Строка.Цена = 150. Строка, которую вернул поиск, — это ссылка на строку самой таблицы, а не её копия, поэтому изменение сразу видно в исходной ТаблицаЗначений.

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

Как устроена таблица значений и что в ней можно менять

Таблица значений — коллекция в памяти, живёт только на сервере и в толстом клиенте. В тонком клиенте и веб-клиенте её нет: на управляемой форме вместо неё работает ДанныеФормыКоллекция.

Что можно изменить в таблице значений:

  • ✏️ значение ячейки — присваиванием Строка.Колонка = Значение или Строка["Колонка"] = Значение;
  • ➕ набор строк — Добавить(), Вставить(Индекс), Удалить(Строка), Очистить();
  • 🧱 набор колонок — Колонки.Добавить(), Колонки.Удалить();
  • 🔀 порядок строк — Сортировать("Колонка Убыв"), Сдвинуть();
  • 🧮 структуру данных целиком — Свернуть(), Скопировать(), ЗагрузитьКолонку().

Обращение к колонке по имени через точку быстрее читается, через квадратные скобки — удобнее, когда имя колонки хранится в переменной:

ИмяКолонки = "Количество";
Строка[ИмяКолонки] = Строка[ИмяКолонки] + 1;

Типы колонок решают половину проблем

Колонка, добавленная без типа, принимает что угодно. Это удобно, пока таблицу не нужно передать в запрос или сложить суммы. Лучше сразу описывать типы:

ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Количество",   Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
ТЗ.Колонки.Добавить("Цена",         Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
ТЗ.Колонки.Добавить("Сумма",        Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));

У типизированной числовой колонки новая строка сразу содержит 0, а не Неопределено. Значит, выражение Строка.Сумма + 100 не упадёт на пустой строке.

Если таблица получена из запроса через РезультатЗапроса.Выгрузить(), типы колонок уже заданы по полям запроса. Отдельно описывать их не нужно.

Поиск строки, которую нужно изменить

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

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

Метод Найти

Найти() сравнивает значение с ячейками перечисленных колонок. Если колонки не указать, поиск идёт по всем колонкам — это медленнее и иногда находит не то.

Строка = ТЗ.Найти(Номенклатура, "Номенклатура");
Если Строка <> Неопределено Тогда
    Строка.Цена  = НоваяЦена;
    Строка.Сумма = Строка.Количество * Строка.Цена;
КонецЕсли;

Проверка на Неопределено обязательна. Без неё при ненайденной строке код упадёт на первом же присваивании.

Метод НайтиСтроки

Отбор задаётся структурой: ключ — имя колонки, значение — искомое значение. Все условия объединяются через «И».

Отбор = Новый Структура("Номенклатура, Склад", Номенклатура, Склад);
НайденныеСтроки = ТЗ.НайтиСтроки(Отбор);

Для Каждого Строка Из НайденныеСтроки Цикл
    Строка.Количество = 0;
КонецЦикла;

Массив содержит ссылки на строки самой таблицы. Изменили элемент массива — изменили таблицу. Копировать ничего не нужно.

Найти или добавить

Частый сценарий — обновить строку, если она есть, и добавить, если нет:

Строка = ТЗ.Найти(Номенклатура, "Номенклатура");
Если Строка = Неопределено Тогда
    Строка = ТЗ.Добавить();
    Строка.Номенклатура = Номенклатура;
КонецЕсли;
Строка.Количество = Строка.Количество + Количество;

При типизированной колонке Количество новая строка стартует с нуля, и сложение работает без дополнительных проверок.

Индексы: как ускорить поиск в большой таблице

Без индекса НайтиСтроки() перебирает таблицу целиком. На 500 строках это незаметно. На 50 000 строк, когда поиск идёт в цикле по другой таблице такого же размера, получается 2,5 млрд сравнений — и обработка висит минутами.

Индекс добавляется одной строкой:

ТЗ.Индексы.Добавить("Номенклатура, Склад");

После этого НайтиСтроки() с отбором по Номенклатура и Склад находит строки почти мгновенно.

Правила, которые стоит держать в голове:

  1. 🎯 Набор полей в структуре отбора должен совпадать с колонками индекса. Индекс по двум колонкам — отбор по тем же двум.
  2. ⏱️ Индекс строится один раз и поддерживается автоматически при изменении строк. Это стоит времени, поэтому на таблицу из 20 строк индекс не нужен.
  3. 🧩 Изменение значения в индексированной колонке пересчитывает индекс. Если в цикле массово меняете именно ключевую колонку, быстрее удалить индекс, обновить данные и создать индекс заново.
  4. 🗂️ Индексов может быть несколько — под разные варианты отбора.
// Массовая смена ключевой колонки без постоянного перестроения индекса
ТЗ.Индексы.Очистить();
Для Каждого Строка Из ТЗ Цикл
    Строка.Склад = НовыйСклад;
КонецЦикла;
ТЗ.Индексы.Добавить("Номенклатура, Склад");

Соответствие вместо поиска

Когда ключ один и он уникален, ещё быстрее работает Соответствие: ключ — значение колонки, значение — строка таблицы. Построили один раз, дальше доступ по ключу без перебора.

КартаСтрок = Новый Соответствие;
Для Каждого Строка Из ТЗ Цикл
    КартаСтрок.Вставить(Строка.Номенклатура, Строка);
КонецЦикла;

Строка = КартаСтрок.Получить(Номенклатура);
Если Строка <> Неопределено Тогда
    Строка.Цена = НоваяЦена;
КонецЕсли;

Если ключ не уникален, в соответствии останется последняя строка с этим ключом. Для дублей используйте индекс и НайтиСтроки().

Массовое обновление строк таблицы значений в цикле

Одно значение во все строки

Для заполнения колонки одним значением цикл не нужен:

ТЗ.ЗаполнитьЗначения(0, "Скидка");
ТЗ.ЗаполнитьЗначения(Истина, "Пометка");

Можно передать несколько колонок через запятую: ТЗ.ЗаполнитьЗначения(0, "Скидка, СуммаСкидки").

Пересчёт по формуле

Если новое значение зависит от других колонок той же строки, нужен обычный обход:

Для Каждого Строка Из ТЗ Цикл
    Строка.Сумма = Окр(Строка.Количество * Строка.Цена, 2);
КонецЦикла;

Пример: 3 шт. × 41,67 руб. = 125,01 руб. Окр(..., 2) защищает от хвостов при дробных ценах.

Обновление одной таблицы данными из другой

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

ТЗ.Индексы.Добавить("Номенклатура");

Для Каждого СтрокаЦены Из ТЗЦены Цикл
    Отбор = Новый Структура("Номенклатура", СтрокаЦены.Номенклатура);
    Для Каждого Строка Из ТЗ.НайтиСтроки(Отбор) Цикл
        Строка.Цена  = СтрокаЦены.Цена;
        Строка.Сумма = Строка.Количество * Строка.Цена;
    КонецЦикла;
КонецЦикла;

Индекс здесь построен по той же колонке, по которой идёт отбор, поэтому поиск не перебирает таблицу.

Перенос нескольких колонок сразу

ЗаполнитьЗначенияСвойств(Приёмник, Источник, "Колонки") копирует значения одноимённых колонок или свойств. Удобно, когда колонок много:

ЗаполнитьЗначенияСвойств(Строка, СтрокаИсточника, "Цена, Скидка, СтавкаНДС");

Без третьего параметра переносятся все совпавшие по имени колонки. Если нужно всё, кроме ключа, используйте четвёртый параметр — список исключаемых свойств.

Схлопнуть дубли

После загрузки из нескольких источников одна позиция часто встречается несколько раз. Свернуть() группирует строки и суммирует числа:

ТЗ.Свернуть("Номенклатура, Склад", "Количество, Сумма");

Колонки, которых нет ни в первом, ни во втором параметре, будут удалены. Цену после свёртки пересчитывают заново: Сумма / Количество, с проверкой деления на ноль.

Когда данные нужны из базы

Не пишите запрос внутри цикла по строкам — 10 000 строк дадут 10 000 обращений к базе. Передайте таблицу в запрос параметром и получите всё одним запросом:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Т.Номенклатура КАК Номенклатура,
|    Т.Количество КАК Количество
|ПОМЕСТИТЬ ВТ
|ИЗ
|    &ТЗ КАК Т
|;
|ВЫБРАТЬ
|    ВТ.Номенклатура КАК Номенклатура,
|    ВТ.Количество КАК Количество,
|    ВТ.Номенклатура.Артикул КАК Артикул
|ИЗ
|    ВТ КАК ВТ";
Запрос.УстановитьПараметр("ТЗ", ТЗ);
ТЗ = Запрос.Выполнить().Выгрузить();

Для передачи в запрос колонки таблицы должны иметь заданный тип. С нетипизированной колонкой запрос вернёт ошибку.

Удаление строк при обновлении

Обновление часто включает чистку: убрать строки с нулевым количеством. Удалять строки внутри Для Каждого нельзя — после удаления индексы сдвигаются, и следующая строка пропускается.

Надёжный вариант — обход с конца:

Для Сч = 1 По ТЗ.Количество() Цикл
    Строка = ТЗ[ТЗ.Количество() - Сч];
    Если Строка.Количество = 0 Тогда
        ТЗ.Удалить(Строка);
    КонецЕсли;
КонецЦикла;

Граница цикла вычисляется один раз при входе. Счётчик Сч идёт от 1 вверх, а индекс строки ТЗ.Количество() - Сч указывает на последнюю ещё не проверенную строку. Удаление последней строки не сдвигает те, что выше.

Второй вариант — собрать строки в массив и удалить после обхода:

КУдалению = ТЗ.НайтиСтроки(Новый Структура("Количество", 0));
Для Каждого Строка Из КУдалению Цикл
    ТЗ.Удалить(Строка);
КонецЦикла;

Здесь обходим массив, а не саму таблицу, поэтому удаление ничего не ломает.

Как обновить таблицу значений на форме

На управляемой форме данные таблицы хранятся в реквизите формы. На клиенте это ДанныеФормыКоллекция: строки можно менять напрямую, методы поиска НайтиСтроки() у коллекции тоже есть. Индексов и Свернуть() на клиенте нет.

Если обработка тяжёлая, её переносят на сервер:

&НаСервере
Процедура ПересчитатьНаСервере()
    ТЗ = РеквизитФормыВЗначение("ТаблицаТоваров");
    ТЗ.Свернуть("Номенклатура", "Количество, Сумма");
    ЗначениеВРеквизитФормы(ТЗ, "ТаблицаТоваров");
КонецПроцедуры

Для табличной части объекта на форме документа на сервере работает пара Объект.Товары.Выгрузить() и Объект.Товары.Загрузить(ТЗ).

После возврата с сервера форма перерисует таблицу сама. Отдельная команда «обновить» для этого не нужна.

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

Сравнение методов обновления по скорости

Ориентиры для таблицы на 50 000 строк, когда обновляем по ключу данные из другой таблицы того же размера:

СпособСложностьКогда выбиратьРиски
Найти() в цикле без индексаперебор на каждую строкудо нескольких сотен строкна больших объёмах минуты ожидания
НайтиСтроки() + индекспочти мгновенный поисксоставной ключ, дублииндекс по колонке, которую меняете, тормозит
Соответствиедоступ по ключу без переборауникальный ключдубли теряются, остаётся последняя строка
ЗаполнитьЗначения()один вызоводно значение во все строкименяет все строки без условий
Свернуть()один вызовобъединить дублиудаляет колонки вне параметров
Таблица как параметр запросаодно обращение к базенужны данные из базыобязательны типы колонок

Правило простое: если внутри цикла есть поиск по таблице, нужен индекс или соответствие. Если внутри цикла есть запрос к базе — его нужно вынести наружу.

Как проверить, что обновление прошло верно

После массовой правки сверьте контрольные суммы. Метод Итог("Колонка") возвращает сумму по числовой колонке:

СуммаДо = ТЗ.Итог("Сумма");
ТЗ.Свернуть("Номенклатура, Склад", "Количество, Сумма");
СуммаПосле = ТЗ.Итог("Сумма");
Если СуммаДо <> СуммаПосле Тогда
    ВызватьИсключение "Свёртка изменила итог по колонке Сумма";
КонецЕсли;

Свёртка и перенос колонок не должны менять итоги. Пересчёт цен — должен, и разницу удобно вывести в сообщение для отладки. Количество строк до и после смотрят через ТЗ.Количество().

Ошибки при изменении таблицы значений и как их исправить

Диагностика: выберите симптом

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

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

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