Как программно создать Таблицу значений в колонке другой Таблицы значений в 1С

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

При разработке сложных интеграционных механизмов, например, при обмене данными с интернет-магазинами, парсинге многоуровневых XML/JSON-пакетов или выполнении тяжелых расчетов в регламентных заданиях, разработчики часто сталкиваются с необходимостью хранения иерархических структур в памяти. Одним из первых решений, приходящих на ум, является использование вложенных коллекций — когда одна ТаблицаЗначений хранится внутри ячейки другой ТаблицыЗначений.

Однако при попытке реализовать эту схему программно (в режиме выполнения / runtime) без использования визуального конструктора форм многие разработчики совершают классическую ошибку, путая описание типов колонок с инициализацией самих объектов. В этой статье мы подробно разберем, почему возникает популярная ошибка "Нет поля Колонки", как правильно создавать вложенные таблицы значений, с какими критическими проблемами производительности вы можете столкнуться и какие альтернативные подходы существуют в платформе 1С.

Выясняем причину: почему возникает ошибка "Нет поля Колонки"

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


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

// Пытаемся добавить колонку для вложенной таблицы
ПозицииЗаказа = ЗаказыИМ.Колонки.Добавить("ПозицииЗаказа", ТипТЗ);

// На следующей строке платформа выдаст ошибку: "Поле объекта не обнаружено (Колонки)"
ПозицииЗаказа.Колонки.Добавить("ИдентификаторИМ", ТипЧисло);

Проанализируем ситуацию и разберемся, почему этот код принципиально неработоспособен. Метод Колонки.Добавить() возвращает объект метаданных колонки — КолонкаТаблицыЗначений, а не сам экземпляр таблицы значений, который будет храниться в ячейках. У объекта КолонкаТаблицыЗначений есть лишь несколько свойств: Имя, ТипЗначения, Заголовок и Ширина. Естественно, у него нет и не может быть свойства Колонки.

Кроме того, важно понимать ключевую особенность платформы: в 1С тип ТаблицаЗначений не может выступать в роли жестко типизированного объекта на уровне метаданных колонки. Когда вы пишите Новый ОписаниеТипов("ТаблицаЗначений"), платформа технически воспринимает это как тип Произвольный. Родительская колонка не хранит и не может хранить схему колонок вложенной таблицы. Схема колонок должна создаваться динамически для каждого конкретного экземпляра таблицы в конкретной строке.

Правильный способ программного создания вложенных Таблиц Значений

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

Разберем по шагам корректный алгоритм реализации:

  1. Создаем родительскую таблицу значений и добавляем в нее колонку с типом ТаблицаЗначений (или вообще без указания типа, по умолчанию тип будет Произвольный).
  2. При добавлении новой строки в родительскую таблицу мы инициализируем новый независимый объект ТаблицаЗначений.
  3. Настраиваем структуру колонок для этого нового объекта.
  4. Помещаем созданную вложенную таблицу в соответствующую ячейку новой строки родительской таблицы.

Посмотрим на практический пример реализации этого алгоритма в коде:


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

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

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

Критический подводный камень: Поверхностное копирование (Shallow Copy)

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

Допустим, у вас есть заполненная родительская таблица с вложенными таблицами, и вы решаете сделать её копию с помощью стандартного метода Скопировать():

КопияЗаказов = ЗаказыИМ.Скопировать();

Внимание! Платформа 1С при вызове метода Скопировать() для таблиц значений выполняет поверхностное копирование. Это означает следующее:

Если вы измените количество товара во вложенной таблице внутри КопияЗаказов, эти изменения автоматически отразятся и в исходной таблице ЗаказыИМ, так как оба родительских объекта ссылаются на один и тот же физический экземпляр вложенной таблицы в памяти компьютера.

Чтобы избежать этой проблемы и получить полную (глубокую) копию структуры, необходимо вручную обойти скопированную таблицу в цикле и принудительно пересоздать каждую вложенную таблицу:


КопияЗаказов = ЗаказыИМ.Скопировать();

Для Каждого СтрокаЗаказа Из КопияЗаказов Цикл
    // Проверяем на заполненность и принудительно копируем вложенный объект
    Если ЗначениеЗаполнено(СтрокаЗаказа.ПозицииЗаказа) Тогда
        СтрокаЗаказа.ПозицииЗаказа = СтрокаЗаказа.ПозицииЗаказа.Скопировать();
    КонецЕсли;
КонецЦикла;

Проблемы производительности и потребления оперативной памяти

Вложенные таблицы значений крайне неэффективны с точки зрения расхода памяти при больших объемах данных. Каждая созданная ТаблицаЗначений — это "тяжелый" системный объект со своей служебной информацией (метаданные колонок, внутренние индексы, менеджеры памяти).

Если вы обрабатываете большой объем данных (например, загружаете 10 000 заказов за сессию, в каждом из которых в среднем по 5-10 позиций), платформа создаст 10 000 независимых объектов Таблиц Значений в оперативной памяти сервера 1С. Это неизбежно приведет к следующим негативным последствиям:

Именно поэтому использование вложенных таблиц значений считается в среде профессиональных разработчиков 1С не самым лучшим архитектурным решением (или, как выразился автор темы, "дурно пахнущим").

Оптимальная альтернатива №1: Связь плоских таблиц по ключу

Наиболее производительным и соответствующим стандартам разработки 1С (включая стандарты БСП) подходом является использование двух независимых "плоских" таблиц значений, связанных между собой уникальным ключом связи. Этот метод работает на порядок быстрее и потребляет в разы меньше оперативной памяти.

Рассмотрим пример реализации такой архитектуры:


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

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

// КРИТИЧЕСКИ ВАЖНО: Добавляем индекс по ключу связи для мгновенного поиска
ТаблицаПозиций.Индексы.Добавить("КлючСвязи");

// Заполняем данными
Для Каждого ТекущийЗаказ Из МассивВходящихДанных Цикл
    
    // Генерируем уникальный ключ для связи
    НовыйКлюч = Новый УникальныйИдентификатор;
    
    // Добавляем заказ
    НоваяСтрокаЗаказа = ТаблицаЗаказов.Добавить();
    НоваяСтрокаЗаказа.КлючСвязи = НовыйКлюч;
    НоваяСтрокаЗаказа.НомерЗаказа = ТекущийЗаказ.Номер;
    
    // Добавляем позиции этого заказа в общую плоскую таблицу
    Для Каждого Товар Из ТекущийЗаказ.Товары Цикл
        НоваяСтрокаТовара = ТаблицаПозиций.Добавить();
        НоваяСтрокаТовара.КлючСвязи = НовыйКлюч;
        НоваяСтрокаТовара.Товар = Товар.Ссылка;
        НоваяСтрокаТовара.Количество = Товар.Количество;
    КонецЦикла;
    
КонецЦикла;

Для того чтобы быстро получить позиции конкретного заказа из такой структуры, мы используем высокопроизводительный метод НайтиСтроки(), который благодаря предварительно созданному индексу отработает практически мгновенно:


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

Для Каждого СтрокаТовара Из НайденныеПозиции Цикл
    // Обрабатываем позиции заказа
КонецЦикла;

В данном сценарии в памяти сервера создаются всего два объекта ТаблицаЗначений. Это обеспечивает колоссальный выигрыш в скорости работы и экономии системных ресурсов.

Оптимальная альтернатива №2: Массивы и Структуры для JSON/XML (XDTO)

Если задача заключается исключительно в парсинге данных из внешнего источника (например, получение данных по HTTP в формате JSON или XML) и последующей обработке в фоновом задании без вывода на форму, то использование таблиц значений вообще не имеет практического смысла.

Таблицы значений — это тяжелые интерфейсные объекты. Для чистой обработки данных гораздо эффективнее использовать связку легких коллекций: Массивы и Структуры. Платформа 1С идеально адаптирована под сериализацию таких структур.

Разберем пример обработки структуры "Заказ - Позиции" через легкие коллекции:


// Создаем общий массив для хранения заказов
ПакетЗаказов = Новый Массив;

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

Такой подход обладает рядом неоспоримых преимуществ:

Подводим итоги

Мы подробно разобрали особенности работы с вложенными таблицами значений в 1С. Подводя итог, можно сделать следующие выводы:

  1. Программное создание вложенной таблицы в колонке родительской таблицы возможно только путем динамической инициализации нового объекта ТаблицаЗначений для каждой строки отдельно. Настроить структуру вложенных колонок через метод родительской таблицы Колонки.Добавить() нельзя.
  2. Использование вложенных ТЗ влечет за собой проблемы с поверхностным копированием методом Скопировать(), требуя глубокого ручного копирования вложенных объектов в цикле.
  3. Для высокопроизводительных систем, регламентных заданий и обработки больших массивов данных настоятельно рекомендуется отказаться от вложенных ТЗ в пользу двух плоских таблиц, связанных уникальным ключом (индексированным), либо использовать легкие структуры данных — массивы и структуры.
← На главную