При разработке отчетов и выгрузок остатков товаров разработчики часто сталкиваются с задачей передачи сотен тысяч строк в файл электронной таблицы. При этом возникают трудности: выгрузка зависает, завершается аварийно из-за нехватки оперативной памяти или сохраняет пустой документ. Рассмотрим основные причины таких сбоев и разберем наиболее надежные варианты решения этой задачи.
В исходном коде часто встречается принципиальная ошибка: одновременное создание объекта ТабличныйДокумент и вызов COM-объекта Excel.Application. Проанализируем, почему такой подход не работает:
ТаблицаСохранение.Вывести(ОбластьСтрока) формирует документ исключительно во внутренней памяти платформы 1С. В открытую книгу Excel через COM-соединение эти строки не передаются автоматически.Лист.Cells не заполняются данными.Возврат внутри цикла прерывает выполнение процедуры на первой же тысяче строк, не доходя до этапа фактического сохранения файла.Самый простой и быстрый способ, не требующий наличия установленного Microsoft Office на компьютере или сервере, — использование встроенных методов платформы 1С. Для версий файлов XLSX лимит составляет 1 048 576 строк, в то время как старый формат XLS ограничен 65 535 строками.
Сначала заполним табличный документ стандартным выводом областей макета, после чего сохраним его встроенным методом Записать с указанием типа файла ТипФайлаТабличногоДокумента.XLSX:
ТаблицаСохранение = Новый ТабличныйДокумент;
Макет = ЭтотОбъект.ПолучитьМакет("ТаблицаВыгрузки");
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
ТаблицаСохранение.Вывести(ОбластьШапка);
Для Каждого Стр Из ДанныеВыгрузки Цикл
ЗаполнитьЗначенияСвойств(ОбластьСтрока.Параметры, Стр);
ТаблицаСохранение.Вывести(ОбластьСтрока);
КонецЦикла;
ТаблицаСохранение.Записать(ПутьКФайлу, ТипФайлаТабличногоДокумента.XLSX);
Важно: Метод ТабличныйДокумент.Записать() выгружает данные только на один лист. Если вам требуется строго многостраничная книга Excel или объем данных превышает 1 миллион строк, потребуется использовать другие подходы.
Если необходимо распределить данные по нескольким листам одной книги Excel средствами COM-автоматизации, данные следует записывать непосредственно в объект листа. Чтобы избежать зависаний, обязательно отключаем обновление экрана и автопересчет формул.
Разберем пример создания листов и заполнения ячеек с разделением по страницам (например, по 500 000 строк на лист):
Попытка
Эксель = Новый COMОбъект("Excel.Application");
Исключение
Сообщить("Не удалось запустить Excel: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
Эксель.ScreenUpdating = Ложь;
Эксель.DisplayAlerts = Ложь;
Эксель.EnableEvents = Ложь;
Книга = Эксель.Workbooks.Add();
Лист = Книга.Worksheets(1);
Лист.Name = "Остатки_1";
НомерСтроки = 1;
ЛимитСтрокНаЛисте = 500000;
НомерЛиста = 1;
Для Каждого Стр Из ДанныеВыгрузки Цикл
Если НомерСтроки > ЛимитСтрокНаЛисте Тогда
НомерЛиста = НомерЛиста + 1;
Лист = Книга.Worksheets.Add(, Лист);
Лист.Name = "Остатки_" + Строка(НомерЛиста);
НомерСтроки = 1;
КонецЕсли;
Лист.Cells(НомерСтроки, 1).Value = Стр.Номенклатура;
Лист.Cells(НомерСтроки, 2).Value = Стр.Количество;
Лист.Cells(НомерСтроки, 3).Value = Стр.Склад;
НомерСтроки = НомерСтроки + 1;
КонецЦикла;
Попытка
Книга.SaveAs(ПутьКФайлу);
Исключение
Сообщить("Ошибка при сохранении: " + ОписаниеОшибки());
КонецПопытки;
Эксель.ScreenUpdating = Истина;
Эксель.DisplayAlerts = Истина;
Книга.Close(Ложь);
Эксель.Quit();
Лист = Неопределено;
Книга = Неопределено;
Эксель = Неопределено;
Обратите внимание на очистку переменных в конце кода: присвоение значения Неопределено гарантирует закрытие фонового процесса EXCEL.EXE в диспетчере задач.
Если объем выгрузки превышает сотни тысяч записей, генерация через COM или ТабличныйДокумент может вызвать ошибку Недостаточно памяти (особенно в 32-разрядных клиентах 1С). В таких ситуациях оптимально использовать потоковую запись в текстовый файл с разделителями (CSV) с помощью объекта ЗаписьТекста.
Файлы CSV без проблем открываются в Excel и не требуют промежуточного хранения всей таблицы в оперативной памяти:
Запись = Новый ЗаписьТекста(ПутьКФайлуCSV, КодировкаТекста.UTF8);
// Записываем заголовок
Запись.ЗаписатьСтроку("Номенклатура;Количество;Склад");
Для Каждого Стр Из ДанныеВыгрузки Цикл
СтрокаДанных = Стр.Номенклатура + ";" + Формат(Стр.Количество, "ЧГ=0") + ";" + Стр.Склад;
Запись.ЗаписатьСтроку(СтрокаДанных);
КонецЦикла;
Запись.Закрыть();
Такой подход обеспечивает максимальную скорость формирования и минимальную нагрузку на оперативную память сервера и клиентского приложения.