Как восстановить пропавшие табличные части документов в 1С после тестирования и исправления

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

При работе с базами данных 1С на платформе 8.3 специалисты нередко сталкиваются с крайне неприятной ситуацией: после внезапного отключения питания, сбоя сети или аварийного завершения работы программы база отказывается запускаться. Пользователь, пытаясь оперативно решить проблему, запускает стандартную утилиту проверки физической целостности файла БД chdbfl.exe или штатный механизм Тестирование и исправление (ТИИ) в конфигураторе. Процедура завершается успешно, база снова открывается, однако при попытке зайти в документы (например, Поступление товаров и услуг) обнаруживается критическая проблема: шапка документа заполнена, итоговая сумма в общем списке отображается корректно, но сама табличная часть пуста. При попытке перепровести документ система выдает ошибку о том, что не заполнена спецификация или список товаров.

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

Почему исчезают табличные части: техническая суть сбоя

Для того чтобы понять, почему утилиты восстановления стирают строки документов, проанализируем внутреннюю архитектуру хранения данных в платформе 1С. Любая конфигурация хранит информацию о документах в нескольких связанных физических таблицах СУБД (независимо от того, файловая это база 1Cv8.1CD или клиент-серверная на MS SQL/PostgreSQL):

  1. Основная таблица документа (шапка). Она содержит реквизиты шапки (дата, номер, контрагент, договор, склад, общая сумма документа и т.д.). В СУБД эта таблица обычно имеет префикс вида _DocumentXX.
  2. Таблицы табличных частей. Каждая табличная часть (например, "Товары", "Услуги", "ВозвратнаяТара") хранится в отдельной физической таблице с префиксом вида _DocumentXX_VTyyyy. Связь между строками табличной части и шапкой документа осуществляется посредством уникального идентификатора ссылки (GUID) родительского документа.

В момент аварийного отключения СУБД или операционной системы нарушается целостность индексов и физических блоков файла БД. Это приводит к так называемому "рассинхрону" или физическому повреждению страниц данных в таблицах табличных частей _VT.

Когда мы запускаем процедуру ТИИ или утилиту chdbfl.exe, эти инструменты в первую очередь нацелены на восстановление работоспособности самой структуры файла базы данных. Если утилита обнаруживает битые сектора, некорректные указатели или разрушенные страницы в таблице _DocumentXX_VTyyyy, она не пытается восстановить поврежденные записи — она просто "обрезает" (удаляет) поврежденные страницы данных целиком, чтобы восстановить физическую структуру файла. В итоге таблица становится структурно целой, база без ошибок открывается в режиме "Предприятие", но данные строк документов оказываются безвозвратно удалены.

Дополнительным фактором может стать логическая очистка связей. Если во время выполнения ТИИ в настройках проверки ссылочной целостности были установлены параметры "При наличии битых ссылок: Очищать", то любые записи в табличной части, временно потерявшие корректный GUID-адрес родительского документа, будут полностью вычищены алгоритмом проверки.


Метод 1. Восстановление данных через СУБД (для клиент-серверных баз)

Если ваша база работает под управлением Microsoft SQL Server или PostgreSQL и у вас есть копия базы данных, сделанная до сбоя (даже если она "битая" и не запускается в 1С), мы можем восстановить утерянные строки документов прямым SQL-запросом.

Рассмотрим этот процесс по шагам:

  1. Развернем на сервере СУБД резервную копию базы данных под именем, например, base_backup. Наша рабочая база называется base_work.
  2. Выясним точные имена физических таблиц документов и их табличных частей. Для этого воспользуемся программным методом 1С ПолучитьСтруктуруХраненияБазыДанных() или специализированными обработками с порталов сообщества 1С. Допустим, поврежденная табличная часть "Товары" документа "Поступление" хранится в SQL-таблице _Document183_VT3456.
  3. Выполним резервное копирование рабочей базы перед проведением любых манипуляций.
  4. Запустим СУБД-менеджер (например, SQL Server Management Studio) и выполним скрипт переноса данных. Проанализируем пример такого запроса:

-- Шаг 1. Полностью очищаем пустую дефектную таблицу в рабочей базе данных
TRUNCATE TABLE base_work.dbo._Document183_VT3456;

-- Шаг 2. Копируем все записи табличной части из архивной копии в рабочую базу
INSERT INTO base_work.dbo._Document183_VT3456
SELECT * FROM base_backup.dbo._Document183_VT3456;

Поскольку идентификаторы документов (GUID) в рабочей базе и в архиве полностью совпадают, перенесенные строки мгновенно "привяжутся" к своим документам-родителям. После выполнения SQL-запроса в обязательном порядке необходимо запустить ТИИ в режиме реиндексации таблиц рабочей базы данных.


Метод 2. Перенос табличных частей из копии по GUID с помощью внешней обработки

Если база данных файловая или вы не имеете прямого низкоуровневого доступа к СУБД, отличным решением будет написание небольшой сервисной обработки в среде 1С. Этот метод основывается на том, что уникальные идентификаторы документов в вашей текущей базе и в ранее созданной резервной копии абсолютно идентичны.

Алгоритм действий выглядит следующим образом:

  1. Развернем архивную копию базы данных на локальном компьютере.
  2. Напишем простую выгрузку табличных частей документов из резервной копии во внешний файл (например, в формате XML или JSON).
  3. Запустим обработку в рабочей базе для загрузки и сопоставления данных.

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


// Код выполняется в архивной базе данных
Процедура ВыгрузитьТабличныеЧастиНаДиск() Экспорт
    ПутьКФайлу = "C:\Temp\recovered_vt.xml";
    ЗаписьXML = Новый ЗаписьXML;
    ЗаписьXML.ОткрытьФайл(ПутьКФайлу, "UTF-8");
    ЗаписьXML.ЗаписатьОбъявлениеXML();
    ЗаписьXML.ЗаписатьНачалоЭлемента("ДанныеДокументов");

    // Выбираем только нужные документы за определенный период сбоя
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Поступление.Ссылка КАК Ссылка,
        |	Поступление.Товары.(
        |		Номенклатура,
        |		Количество,
        |		Цена,
        |		Сумма,
        |		СтавкаНДС,
        |		СуммаНДС
        |	) КАК Товары
        |ИЗ
        |	Документ.ПоступлениеТоваровУслуг КАК Поступление
        |ГДЕ
        |	Поступление.Дата МЕЖДУ &НачалоПериода И &КонецПериода";
        
    Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ТекущаяДата() - 30)); // Например, за последний месяц
    Запрос.УстановитьПараметр("КонецПериода", КонецДня(ТекущаяДата()));
    
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        // Записываем GUID документа для однозначной идентификации в рабочей базе
        ИдентификаторДокумента = Строка(Выборка.Ссылка.УникальныйИдентификатор());
        
        ЗаписьXML.ЗаписатьНачалоЭлемента("Документ");
        ЗаписьXML.ЗаписатьАтрибут("ИД", ИдентификаторДокумента);
        
        Для Каждого СтрокаТЧ Из Выборка.Товары Цикл
            ЗаписьXML.ЗаписатьНачалоЭлемента("СтрокаТовары");
            // Выгружаем UUID номенклатуры для точного сопоставления ссылок
            ЗаписьXML.ЗаписатьАтрибут("НоменклатураИД", Строка(СтрокаТЧ.Номенклатура.УникальныйИдентификатор()));
            ЗаписьXML.ЗаписатьАтрибут("Количество", Строка(СтрокаТЧ.Количество));
            ЗаписьXML.ЗаписатьАтрибут("Цена", Строка(СтрокаТЧ.Цена));
            ЗаписьXML.ЗаписатьАтрибут("Сумма", Строка(СтрокаТЧ.Сумма));
            ЗаписьXML.ЗаписатьКонецЭлемента(); // СтрокаТовары
        КонецЦикла;
        
        ЗаписьXML.ЗаписатьКонецЭлемента(); // Документ
    КонецЦикла;
    
    ЗаписьXML.ЗаписатьКонецЭлемента(); // ДанныеДокументов
    ЗаписьXML.Закрыть();
    Предупреждение("Выгрузка успешно завершена!");
КонецПроцедуры

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


// Код выполняется в рабочей поврежденной базе данных
Процедура ЗагрузитьТабличныеЧасти() Экспорт
    ПутьКФайлу = "C:\Temp\recovered_vt.xml";
    ЧтениеXML = Новый ЧтениеXML;
    ЧтениеXML.ОткрытьФайл(ПутьКФайлу);
    
    ТекущийДокументОбъект = Неопределено;
    
    Пока ЧтениеXML.Прочитать() Цикл
        ИмяУзла = ЧтениеXML.Имя;
        ТипУзла = ЧтениеXML.ТипУзла;
        
        Если ТипУзла = ТипУзлаXML.НачалоЭлемента И ИмяУзла = "Документ" Тогда
            GUIDСтрока = ЧтениеXML.ПолучитьАтрибут("ИД");
            СсылкаДокумента = Документы.ПоступлениеТоваровУслуг.ПолучитьСсылку(Новый УникальныйИдентификатор(GUIDСтрока));
            
            // Получаем объект для редактирования
            ТекущийДокументОбъект = СсылкаДокумента.ПолучитьОбъект();
            Если ТекущийДокументОбъект <> Неопределено Тогда
                ТекущийДокументОбъект.Товары.Очистить(); // Удаляем остатки поврежденной структуры
            КонецЕсли;
            
        ИначеЕсли ТипУзла = ТипУзлаXML.НачалоЭлемента И ИмяУзла = "СтрокаТовары" И ТекущийДокументОбъект <> Неопределено Тогда
            НоменклатураGUID = ЧтениеXML.ПолучитьАтрибут("НоменклатураИД");
            СсылкаНоменклатуры = Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор(НоменклатураGUID));
            
            НоваяСтрока = ТекущийДокументОбъект.Товары.Добавить();
            НоваяСтрока.Номенклатура = СсылкаНоменклатуры;
            НоваяСтрока.Количество = Число(ЧтениеXML.ПолучитьАтрибут("Количество"));
            НоваяСтрока.Цена = Число(ЧтениеXML.ПолучитьАтрибут("Цена"));
            НоваяСтрока.Сумма = Число(ЧтениеXML.ПолучитьАтрибут("Сумма"));
            
        ИначеЕсли ТипУзла = ТипУзлаXML.КонецЭлемента И ИмяУзла = "Документ" Тогда
            Если ТекущийДокументОбъект <> Неопределено Тогда
                Попытка
                    // Записываем документ без проведения, чтобы не нарушать существующие проводки
                    ТекущийДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
                Исключение
                    ЗаписьЖурналаРегистрации("ВосстановлениеДанных", УровеньЖурналаРегистрации.Ошибка,,, 
                        "Ошибка записи документа: " + ТекущийДокументОбъект.Ссылка + " - " + ОписаниеОшибки());
                КонецПопытки;
                ТекущийДокументОбъект = Неопределено;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    
    ЧтениеXML.Закрыть();
    Предупреждение("Восстановление табличных частей успешно завершено!");
КонецПроцедуры

Метод 3. Восстановление табличных частей по движениям бухгалтерского учета

Этот метод является настоящим спасением в тех случаях, когда резервной копии базы вообще не существует. Поскольку шапка документа и его движения по проводкам (Регистр бухгалтерии) записываются в разные физические таблицы базы данных, при повреждении табличной части _VT проводки по счетам учета (например, Дебет 41.01 - Кредит 60.01) очень часто остаются абсолютно нетронутыми.

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

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


Процедура ВосстановитьТЧИзПроводок() Экспорт
    // Выбираем проведенные пустые документы
    ЗапросДокументов = Новый Запрос;
    ЗапросДокументов.Текст = 
        "ВЫБРАТЬ
        |	Поступление.Ссылка КАК Ссылка
        |ИЗ
        |	Документ.ПоступлениеТоваровУслуг КАК Поступление
        |ГДЕ
        |	Поступление.Проведен = ИСТИНА
        |	И НЕ Поступление.Товары.Ссылка В (
        |		ВЫБРАТЬ РАЗЛИЧНЫЕ
        |			Товары.Ссылка
        |		ИЗ
        |			Документ.ПоступлениеТоваровУслуг.Товары КАК Товары
        |	)";
        
    РезультатДокументов = ЗапросДокументов.Выделать().Выгрузить();
    
    Для Каждого СтрокаДок Из РезультатДокументов Цикл
        ДокументОбъект = СтрокаДок.Ссылка.ПолучитьОбъект();
        
        // Запрашиваем проводки данного документа по счету 41 (Товары)
        ЗапросПроводок = Новый Запрос;
        ЗапросПроводок.Текст = 
            "ВЫБРАТЬ
            |	Движения.СубконтоДт1 КАК Номенклатура,
            |	Движения.КоличествоДт КАК Количество,
            |	Движения.Сумма КАК Сумма
            |ИЗ
            |	РегистрБухгалтерии.Хозрасчетный.ДвиженияССубконто КАК Движения
            |ГДЕ
            |	Движения.Регистратор = &Регистратор
            |	И Движения.СчетДт.Код ПОДОБНО ""41%""";
            
        ЗапросПроводок.УстановитьПараметр("Регистратор", ДокументОбъект.Ссылка);
        ТаблицаПроводок = ЗапросПроводок.Выполнить().Выгрузить();
        
        Если ТаблицаПроводок.Количество() > 0 Тогда
            Для Каждого СтрокаПроводки Из ТаблицаПроводок Цикл
                НоваяСтрока = ДокументОбъект.Товары.Добавить();
                НоваяСтрока.Номенклатура = СтрокаПроводки.Номенклатура;
                НоваяСтрока.Количество = СтрокаПроводки.Количество;
                НоваяСтрока.Сумма = СтрокаПроводки.Сумма;
                
                // Рассчитаем цену программно
                Если СтрокаПроводки.Количество <> 0 Тогда
                    НоваяСтрока.Цена = СтрокаПроводки.Сумма / СтрокаПроводки.Количество;
                КонецЕсли;
            КонецЦикла;
            
            Попытка
                // Записываем документ в режиме Запись, чтобы сохранить проводки нетронутыми!
                ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
                ЗаписьЖурналаРегистрации("ВосстановлениеИзПроводок", УровеньЖурналаРегистрации.Информация,,,
                    "Успешно восстановлен документ: " + ДокументОбъект.Ссылка);
            Исключение
                ЗаписьЖурналаРегистрации("ВосстановлениеИзПроводок", УровеньЖурналаРегистрации.Ошибка,,,
                    "Не удалось записать документ: " + ДокументОбъект.Ссылка + " Ошибка: " + ОписаниеОшибки());
            КонецПопытки;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Метод 4. Извлечение таблиц из поврежденного файла *.1CD с помощью Tool_1CD

Если перед выполнением процедуры ТИИ вы скопировали битый файл 1Cv8.1CD (который не выгружался в резервную копию формата .dt из-за ошибок БД), ни в коем случае не удаляйте его. Мы можем извлечь данные напрямую из этого поврежденного файла в обход стандартных механизмов платформы 1С.

Для реализации этого сценария воспользуемся специализированной утилитой Tool_1CD, разработанной сообществом специалистов для прямого низкоуровневого чтения баз данных формата 1CD.

Разберем данный процесс по шагам:

  1. Запустим утилиту Tool_1CD и откроем в ней оригинальный поврежденный файл базы данных 1Cv8.1CD (сделанный ДО запуска тестирования и исправления).
  2. Найдем в списке структуру поврежденной таблицы (например, _Document183_VT3456). Даже если индексы или заголовок файла нарушены, утилита часто способна прочитать сырые записи страниц данных в обход структуры 1С.
  3. Экспортируем содержимое этой таблицы в формате XML, CSV или DBF на жесткий диск.
  4. В нашей рабочей исправленной базе напишем стандартную обработку чтения полученного файла обмена и загрузим данные обратно в документы, ориентируясь на GUID-идентификаторы документов и номенклатурных позиций.

Полезный совет: Если вы используете современную версию платформы (начиная с 8.3.8) с новым форматом баз данных, старые версии Tool_1CD могут не справиться с чтением структуры. В таком случае предварительно выполните конвертацию файла базы утилитой cnvdbfl.exe (которая находится в каталоге bin установленной платформы 1С) для приведения базы к более старому и "читаемому" формату.


Меры профилактики: как защитить базу от подобных сбоев в будущем

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

← На главную