В процессе эксплуатации баз данных 1С:Предприятия 7.7 нередко возникает ситуация, когда при ручном вводе или программном сбое пользователь вводит некорректный большой код (например, ошибочно сканирует штрихкод в поле кода). Поскольку механизм автонумерации платформы вычисляет следующий код как максимальный существующий плюс единица, все последующие новые элементы начинают получать завышенные номера. В результате образуются разрывы в кодах, а штатный диапазон нарушается.
При попытке написать собственную обработку для упорядочивания кодов разработчики часто сталкиваются с логическими ошибками или зацикливанием выборки. Разберем типичные ошибки, проанализируем причины сбоев и рассмотрим надежные варианты перенумерации справочника.
Рассмотрим распространенный подход: разработчик считывает текущие данные справочника в объект ТаблицаЗначений, чтобы зафиксировать список элементов и их текущие коды, а затем запускает цикл для поиска свободных номеров.
Посмотрим на ошибочный алгоритм, в котором данные изменяются в базе данных, но не обновляются в промежуточной таблице:
Процедура ОшибочнаяПеренумерация()
Спр = СоздатьОбъект("Справочник.Номенклатура");
ТЗ = СоздатьОбъект("ТаблицаЗначений");
ТЗ.НоваяКолонка("Код", "Число");
ТЗ.НоваяКолонка("Эл");
Спр.ПорядокКодов();
Спр.ВыбратьЭлементы(0);
Пока Спр.ПолучитьЭлемент() = 1 Цикл
ТЗ.НоваяСтрока();
ТЗ.Код = Число(Спр.Код);
ТЗ.Эл = Спр.ТекущийЭлемент();
КонецЦикла;
ТЗ.Сортировать("Код");
Для Сч = 1 По ТЗ.КоличествоСтрок() Цикл
Стр = "";
// Ошибка: поиск идет по статичной ТЗ, которая рассинхронизируется с базой!
Если ТЗ.НайтиЗначение(Сч, Стр, "Код") = 0 Тогда
ТекЭл = ТЗ.ПолучитьЗначение(Сч, "Эл");
Если Спр.НайтиЭлемент(ТекЭл) = 1 Тогда
Спр.УстановитьАтрибут("Код", Сч);
Спр.Записать();
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Выясним причину, почему такой алгоритм сбивается и обрабатывает лишь часть элементов:
Записать().ТаблицаЗначений этот код по-прежнему присутствует в колонке Код.ТЗ.НайтиЗначение(Сч, ...) находит старую запись, решает, что код 11 занят, и пропускает шаг. Возникает эффект «потери выборки» и частичного смещения данных.При реализации любых механизмов перенумерации важно учитывать базовые правила платформы 1С:Предприятие 7.7:
ВыбратьЭлементы() перемещается по индексу B-Tree (например, по порядку кодов). Если внутри цикла Пока Спр.ПолучитьЭлемент() = 1 Цикл менять код текущего или другого элемента, индекс перестраивается «на лету», что приводит к пропуску элементов или бесконечному циклу. Любая модификация кодов должна производиться либо по заранее сохраненному списку ссылок, либо прямым поиском.НачатьТранзакцию() и ЗафиксироватьТранзакцию() блоками по 200–500 элементов ускоряет обработку в десятки раз.Если стоит задача «сжать» справочник, устранив дыры и заполнив пропущенные коды по порядку, наиболее надежно выполнять проверку доступности номера непосредственно в базе данных через Спр.НайтиПоКоду().
Разберем готовый вариант решения задачи с порционной фиксацией транзакций:
Процедура Сформировать()
Перем Спр, СпрПоиск, ТЗ;
Спр = СоздатьОбъект("Справочник.Номенклатура");
СпрПоиск = СоздатьОбъект("Справочник.Номенклатура");
ТЗ = СоздатьОбъект("ТаблицаЗначений");
ТЗ.НоваяКолонка("Эл");
// Выгружаем фиксированный список элементов в порядке существующих кодов
Спр.ПорядокКодов();
Спр.ВыбратьЭлементы(0);
Пока Спр.ПолучитьЭлемент() = 1 Цикл
ТЗ.НоваяСтрока();
ТЗ.Эл = Спр.ТекущийЭлемент();
КонецЦикла;
НачатьТранзакцию();
Сч = 0;
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
Сч = Сч + 1;
// Проверяем реальное наличие кода в базе данных, а не в статичной ТЗ
Если СпрПоиск.НайтиПоКоду(Сч) = 0 Тогда
ТекЭлемент = ТЗ.Эл;
Если Спр.НайтиЭлемент(ТекЭлемент) = 1 Тогда
Спр.УстановитьАтрибут("Код", Сч);
Спр.Записать();
Если Сч % 500 = 0 Тогда
Состояние("Обработано: " + Сч);
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
ЗафиксироватьТранзакцию();
Сообщить("Обработка завершена. Всего элементов: " + Сч);
КонецПроцедуры
Если справочник сильно поврежден или необходимо полностью перенумеровать все элементы от 1 до N без риска коллизий с существующими номерами, применяется классический двухфазный алгоритм через буферный диапазон.
Рассмотрим логику работы в два прохода:
МаксимальныйКод + ПорядковыйНомер). Это полностью освобождает диапазон целевых кодов (1, 2, 3...) и исключает ошибки контроля уникальности.Посмотрим на пример реализации двухпроходного метода:
Процедура ПолнаяСквознаяПеренумерация()
Перем Спр, ТЗ, ВсегоЭл, Сдвиг, Индекс, ТекЭл;
Спр = СоздатьОбъект("Справочник.Номенклатура");
ТЗ = СоздатьОбъект("ТаблицаЗначений");
ТЗ.НоваяКолонка("Эл");
Спр.ПорядокКодов();
Спр.ВыбратьЭлементы(0);
Пока Спр.ПолучитьЭлемент() = 1 Цикл
ТЗ.НоваяСтрока();
ТЗ.Эл = Спр.ТекущийЭлемент();
КонецЦикла;
ВсегоЭл = ТЗ.КоличествоСтрок();
Если ВсегоЭл = 0 Тогда
Возврат;
КонецЕсли;
Сдвиг = 1000000; // Заведомо свободное смещение
// Шаг 1: Перенос во временный диапазон
НачатьТранзакцию();
Для Индекс = 1 По ВсегоЭл Цикл
ТекЭл = ТЗ.ПолучитьЗначение(Индекс, "Эл");
Если Спр.НайтиЭлемент(ТекЭл) = 1 Тогда
Спр.УстановитьАтрибут("Код", Сдвиг + Индекс);
Спр.Записать();
КонецЕсли;
КонецЦикла;
ЗафиксироватьТранзакцию();
// Шаг 2: Присвоение правильных номеров по порядку от 1
НачатьТранзакцию();
Для Индекс = 1 По ВсегоЭл Цикл
ТекЭл = ТЗ.ПолучитьЗначение(Индекс, "Эл");
Если Спр.НайтиЭлемент(ТекЭл) = 1 Тогда
Спр.УстановитьАтрибут("Код", Индекс);
Спр.Записать();
КонецЕсли;
КонецЦикла;
ЗафиксироватьТранзакцию();
Сообщить("Справочник успешно перенумерован!");
КонецПроцедуры
Для предотвращения сбоев и потери данных перед запуском обработки придерживайтесь следующих правил: