Загрузка данных из внешних файлов, таких как Excel, является одной из самых частых задач для разработчика 1С. Однако при работе с периодическими регистрами сведений начинающие программисты часто сталкиваются с ситуацией, когда вместо создания множества записей в базе остается только одна или несколько строк. Рассмотрим, почему это происходит и как реализовать загрузку правильно, эффективно и надежно.
Проанализируем ситуацию, представленную в вопросе. Автор пытался загрузить остатки товаров из Excel, используя МенеджерЗаписи внутри цикла. Главная проблема заключалась в следующем фрагменте кода:
Для Стр = 1 По ПоследняяСтрока Цикл
Дата = '20130720000000';
Дата = Дата + 1;
// ... логика записи ...
КонецЦикла;
В чем здесь ошибка? Разберем по шагам. Переменная Дата инициализируется внутри цикла. Это означает, что на каждой итерации (для каждой строки Excel) дата сбрасывается к одному и тому же значению. Прибавление единицы (Дата + 1) в 1С добавляет к дате одну секунду. Таким образом, абсолютно все записи пытались записаться на одну и ту же секунду: 20.07.2013 00:00:01.
Поскольку регистр сведений является периодическим, его уникальность определяется комбинацией измерений и периода. Если мы записываем новую порцию данных с теми же измерениями (Номенклатура, Характеристика) и тем же периодом, система просто перезаписывает старую запись. Именно поэтому у автора «исчезали» строки — они накладывались друг на друга в одной точке времени.
Для исправления ситуации необходимо вынести инициализацию даты за пределы цикла. Посмотрим, как должен выглядеть исправленный алгоритм на стороне клиента:
&НаКлиенте
Процедура ЗагрузитьДанныеТабеля(Команда)
Excel = Новый COMОбъект("Excel.Application");
Книга = Excel.WorkBooks.Open("C:\1213.xlsx");
ЛистДанных = Книга.Sheets(1);
ПоследняяСтрока = ЛистДанных.UsedRange.Row + ЛистДанных.UsedRange.Rows.Count - 1;
// Инициализируем дату ОДИН РАЗ перед началом обработки файла
ТекущаяДатаЗаписи = '20130720000000';
Для Стр = 1 По ПоследняяСтрока Цикл
// Увеличиваем время для каждой новой строки, если нам нужна уникальность по периоду
ТекущаяДатаЗаписи = ТекущаяДатаЗаписи + 1;
Номенклатура = ЛистДанных.Cells(Стр, 1).Text;
Характеристика = ЛистДанных.Cells(Стр, 2).Text;
МаксОстаток = ЛистДанных.Cells(Стр, 3).Text;
ДобавитьЗаписьТабеля(Номенклатура, Характеристика, МаксОстаток, ТекущаяДатаЗаписи);
КонецЦикла;
Excel.Application.Quit();
Excel = Неопределено; // Обязательно освобождаем память
КонецПроцедуры
Использование РегистрыСведений.ИмяРегистра.СоздатьМенеджерЗаписи() внутри цикла — это крайне медленный способ. На каждой итерации программа обращается к базе данных, выполняет транзакцию записи и блокирует таблицу. Если в файле Excel 10 000 строк, процесс может затянуться надолго.
Проанализируем более эффективный подход — использование объекта НаборЗаписей. Это позволит записать все данные одним «пакетом». Рассмотрим, как это реализовать на сервере:
&НаСервере
Процедура ЗагрузитьНаборДанных(ТаблицаДанных)
// Создаем набор записей
Набор = РегистрыСведений.МаксОстатокПоТоварам.СоздатьНаборЗаписей();
// Если нужно добавить данные к существующим, фильтры не ставим,
// но вызываем Набор.Прочитать() и затем добавляем новые.
// Если нужно полностью заменить данные за период — используем отбор.
Для Каждого СтрокаИзExcel Из ТаблицаДанных Цикл
НоваяЗапись = Набор.Добавить();
НоваяЗапись.Период = СтрокаИзExcel.Дата;
НоваяЗапись.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию(СтрокаИзExcel.Номенклатура);
НоваяЗапись.Характеристика = Справочники.ХарактеристикиНоменклатуры.НайтиПоНаименованию(СтрокаИзExcel.Характеристика);
НоваяЗапись.МаксОстаток = СтрокаИзExcel.МаксОстаток;
КонецЦикла;
// Записываем всё одним махом. Параметр Ложь означает, что мы НЕ заменяем
// все записи в регистре, а только добавляем те, что в наборе.
Набор.Записать(Ложь);
КонецПроцедуры
В исходном примере используется метод НайтиПоНаименованию. Проанализируем риски этого метода:
НайтиПоНаименованию вернет пустую ссылку.Выясним, как сделать поиск надежнее. Рекомендуется использовать поиск по уникальным идентификаторам: Коду или Артикулу. Также обязательно добавим проверку на «пустое» значение перед записью:
СсылкаНоменклатура = Справочники.Номенклатура.НайтиПоКоду(КодИзExcel);
Если СсылкаНоменклатура.Пустая() Тогда
Сообщить("Товар с кодом " + КодИзExcel + " не найден в базе!");
Продолжить; // Пропускаем строку, чтобы не плодить битые данные
КонецЕсли;
Использование COMОбъект("Excel.Application") требует установленного Microsoft Office на компьютере пользователя и часто приводит к «зависанию» процессов в памяти. Проанализируем современный способ через ТабличныйДокумент, который работает быстрее и стабильнее:
Прочитать() для загрузки файла напрямую в объект 1С.
&НаКлиенте
Процедура ЗагрузитьФайлБезExcel(ПутьКФайлу)
ТабДок = Новый ТабличныйДокумент;
Попытка
ТабДок.Прочитать(ПутьКФайлу);
Исключение
Сообщить("Не удалось прочитать файл: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
// Вся дальнейшая логика обработки ТабДок на сервере...
КонецПроцедуры
Для успешной загрузки данных из Excel в регистры сведений 1С придерживаемся следующих правил:
НаборЗаписей.Excel.Quit() и обнуляйте переменную в блоке Попытка...Исключение.Разбирая подобные задачи, мы учимся не только писать код, но и проектировать надежные системы интеграции, которые не «падают» при изменении формата файла или росте объема данных.