Как правильно разложить структуры в список или массив в 1С без перезаписи данных

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

При разработке на платформе «1С:Предприятие 8» программисты часто сталкиваются с необходимостью выгрузки данных из запроса во вложенные коллекции. Типичный сценарий: получить список документов, где для каждого документа будет подготовлена структура с его шапкой и вложенной табличной частью. Однако на этапе реализации этой задачи начинающие разработчики совершают классическую ошибку, из-за которой во всех элементах итоговой коллекции оказываются абсолютно одинаковые данные — обычно из самой последней строки выборки.

В этой статье мы подробно разберем, почему возникает эта проблема, как устроена работа со ссылочными типами данных в 1С, как исправить ошибку несколькими способами и как сделать итоговый код максимально производительным и соответствующим стандартам разработки.

Анализ исходной проблемы и причины ее возникновения

Для начала давайте проанализируем ситуацию, с которой столкнулся автор темы. В его исходном коде инициализация структуры происходила до начала цикла обхода выборки запроса:


КолекцияДокументовРеализации = Новый СписокЗначений;
ДокументРеализации = Новый Структура("Дата, Склад, Покупатель, ТЧДокументаРеализации");

Пока Выборка.Следующий() Цикл
    ДокументРеализации.Дата = Выборка.Дата;
    ДокументРеализации.Склад = Выборка.Склад;
    ДокументРеализации.Покупатель = Выборка.Поставщик;
    ...
КонецЦикла;

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

На каждой новой итерации цикла программа обращается по той же самой ссылке и перезаписывает значения свойств: Дата, Склад, Покупатель. В результате к моменту окончания цикла все элементы нашей коллекции будут ссылаться на один единственный объект структуры, в котором останутся данные последней обработанной строки запроса. Это фундаментальное поведение платформы 1С при работе с объектами ссылочного типа (к которым относятся структуры, соответствия, массивы, таблицы значений и др.).

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

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

Рассмотрим исправленный алгоритм обхода выборки:


КоллекцияДокументовРеализации = Новый Массив; // Рекомендуется использовать Массив вместо СпискаЗначений

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

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

Использование системной функции ЗаполнитьЗначенияСвойств()

Для упрощения кода, избавления от рутинного сопоставления полей и минимизации ошибок ручного ввода крайне полезно использовать встроенную функцию платформы ЗаполнитьЗначенияСвойств(). Она копирует значения свойств из источника в приемник по совпадающим именам полей.

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


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

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

Архитектурный стандарт: Почему Массив предпочтительнее СпискаЗначений

В исходном коде автора для хранения документов использовался объект СписокЗначений. С точки зрения архитектуры платформы «1С:Предприятие 8», это неоптимальное решение.

Проанализируем ключевые различия между этими типами коллекций:

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

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

Альтернативный вариант: Однопроходный обход плоской таблицы

Использование вложенных выборок с обходом ПоГруппировкам (как в коде автора) является классическим решением, но имеет определенные недостатки при работе с большими объемами информации. Платформе приходится выполнять логические переходы по иерархическому дереву результатов.

Рассмотрим более производительный альтернативный метод: выгрузка результата запроса в плоскую таблицу значений и ее обработка в один проход. Для этого в запросе уберем секцию ИТОГИ ПО, оставив простую плоскую выборку строк, обязательно упорядоченную по ссылке документа.

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


// Выполняем запрос без итогов и выгружаем в Таблицу Значений
ТаблицаРезультата = Запрос.Выполнить().Выгрузить();

КоллекцияДокументов = Новый Массив;
ТекущийДокумент = Неопределено;
ТекущаяСсылка = Неопределено;

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

// Не забываем добавить самый последний документ после окончания цикла
Если ТекущийДокумент <> Неопределено Тогда
    КоллекцияДокументов.Добавить(ТекущийДокумент);
КонецЕсли;

Данный подход работает быстрее на больших выборках данных, так как платформа совершает линейный обход по непрерывному массиву строк таблицы в памяти без накладных расходов на создание объектов выборки группировок.

← На главную