Как правильно заполнять вложенную таблицу значений в 1С и избежать дублирования данных

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

Работа со сложными структурами данных, такими как вложенные ТаблицыЗначений, часто вызывает затруднения у начинающих разработчиков 1С. Типичная ситуация: вы пытаетесь записать разные данные во вложенные таблицы разных строк основной таблицы, но в итоге обнаруживаете, что во всех строках основной таблицы содержимое вложенных таблиц стало одинаковым. В этой статье мы подробно разберем, почему так происходит, и как правильно реализовать заполнение таких структур, особенно при работе в циклах.

Анализ проблемы: почему данные заполняются неправильно?

Рассмотрим ситуацию, с которой столкнулся автор темы. В коде создается одна основная таблица и одна вспомогательная (вложенная). Автор добавляет данные во вложенную таблицу, затем присваивает эту таблицу колонке первой строки основной таблицы. После этого он очищает (или просто продолжает заполнять) ту же самую переменную вложенной таблицы и присваивает ее второй строке. В результате во всех строках основной таблицы оказывается последний вариант данных.

Выясним причину: в системе 1С:Предприятие объект типа ТаблицаЗначений является ссылочным типом. Когда вы пишете СтрокаТаблицы.Колонка2 = ВложеннаяТаблица, вы не копируете данные из одной таблицы в другую. Вы просто записываете ссылку на один и тот же объект в памяти в разные ячейки основной таблицы. Таким образом, все строки основной таблицы начинают «смотреть» на один и тот же экземпляр вложенной таблицы. Любое изменение этого объекта (добавление строк, очистка) мгновенно отражается во всех строках основной таблицы, где он задействован.

Решение №1: Создание нового экземпляра объекта для каждой строки

Чтобы данные в строках были независимыми, для каждой новой строки основной таблицы необходимо инициализировать новый объект ТаблицаЗначений. Рассмотрим исправленный код, который решает задачу автора:


// Создаем основную таблицу
ИсходнаяТаблица = Новый ТаблицаЗначений;
ИсходнаяТаблица.Колонки.Добавить("Колонка1", Новый ОписаниеТипов("Строка"));
ИсходнаяТаблица.Колонки.Добавить("Колонка2", Новый ОписаниеТипов("ТаблицаЗначений"));

// Формируем ПЕРВУЮ строку основной таблицы
ВложеннаяТаблица1 = Новый ТаблицаЗначений;
ВложеннаяТаблица1.Колонки.Добавить("Колонка1", Новый ОписаниеТипов("Строка"));

СтрокаВложТаб = ВложеннаяТаблица1.Добавить();
СтрокаВложТаб.Колонка1 = "ЭтоТест";

СтрокаОсновная1 = ИсходнаяТаблица.Добавить();
СтрокаОсновная1.Колонка1 = "Тест";
СтрокаОсновная1.Колонка2 = ВложеннаяТаблица1; // Теперь здесь хранится ссылка на первый объект

// Формируем ВТОРУЮ строку основной таблицы
// ВАЖНО: Создаем совершенно новый объект в памяти
ВложеннаяТаблица2 = Новый ТаблицаЗначений;
ВложеннаяТаблица2.Колонки.Добавить("Колонка1", Новый ОписаниеТипов("Строка"));

СтрокаВложТаб = ВложеннаяТаблица2.Добавить();
СтрокаВложТаб.Колонка1 = "ЭтоТест5555";
СтрокаВложТаб = ВложеннаяТаблица2.Добавить();
СтрокаВложТаб.Колонка1 = "ЭтоТест99999";

СтрокаОсновная2 = ИсходнаяТаблица.Добавить();
СтрокаОсновная2.Колонка1 = "Тест3";
СтрокаОсновная2.Колонка2 = ВложеннаяТаблица2; // Здесь ссылка на второй, независимый объект

Решение №2: Правильное заполнение в цикле

На практике создание переменных вручную (как ВложеннаяТаблица1, ВложеннаяТаблица2) неудобно. Разберем, как сделать это «красиво» в цикле, используя те же принципы. Основное правило — оператор Новый должен находиться внутри цикла.

Рассмотрим пример, где мы заполняем данные из некоторого массива или выборки:


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

Оптимизация: Использование шаблона структуры

Если вложенная таблица имеет сложную структуру с множеством колонок, постоянно описывать их вручную через Колонки.Добавить() внутри цикла непроизводительно. Проанализируем более элегантный способ — использование метода СкопироватьКолонки().

  1. Сначала создадим «эталонную» пустую таблицу со всеми нужными колонками вне цикла.
  2. Внутри цикла будем создавать копию структуры этой таблицы.

// 1. Подготовка шаблона
ШаблонТЗ = Новый ТаблицаЗначений;
ШаблонТЗ.Колонки.Добавить("Код", Новый ОписаниеТипов("Число"));
ШаблонТЗ.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка"));
ШаблонТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));

// 2. Основной цикл
Для Счетчик = 1 По 10 Цикл
    НоваяСтрока = ИсходнаяТаблица.Добавить();
    
    // Метод СкопироватьКолонки создает НОВЫЙ объект ТаблицаЗначений 
    // с такой же структурой, но без данных.
    ВложеннаяТЗ = ШаблонТЗ.СкопироватьКолонки();
    
    // Заполнение данными...
    СтрокаДанных = ВложеннаяТЗ.Добавить();
    СтрокаДанных.Код = Счетчик;
    
    НоваяСтрока.Колонка2 = ВложеннаяТЗ;
КонецЦикла;

Альтернативные подходы и полезные советы

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

1. Метод Скопировать() вместо пересоздания. Если вам нужно заполнить вложенную таблицу на основе уже существующей и при этом разорвать связь между ними, используйте ВложеннаяТаблица.Скопировать(). Этот метод создает полный клон объекта с данными. После копирования изменения в оригинале не затронут копию.

2. Дерево значений. Если вы строите иерархию данных (например, «Группа — Элементы»), объект ДеревоЗначений подходит гораздо лучше. Он изначально предназначен для хранения подчиненных строк, его легче выводить на форму и обходить рекурсивно.

3. Плоская таблица с идентификаторами. Если данных очень много (десятки тысяч вложенных таблиц), это может существенно нагрузить память. В таких случаях эффективнее использовать одну «плоскую» таблицу со всеми данными и колонкой-ключом (например, ID_Родителя). Для получения данных по конкретной строке достаточно использовать метод НайтиСтроки() с отбором по этому ключу.

4. Визуализация. Помните, что стандартная таблица на управляемой форме не может отображать колонку с типом ТаблицаЗначений «из коробки». Обычно для отображения таких данных создают две таблицы на форме. При выборе (активизации) строки в первой таблице, программа должна программно считывать вложенную таблицу и загружать ее данные во вторую таблицу (реквизит формы типа ТаблицаЗначений) с помощью метода Загрузить().

Резюме: Главный секрет успеха при работе с вложенными таблицами — понимание того, что ТаблицаЗначений — это самостоятельный объект. Чтобы строки основной таблицы не влияли друг на друга, каждая из них должна содержать ссылку на свой собственный, уникальный экземпляр объекта.

← На главную