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

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

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

При попытке написать собственную обработку для упорядочивания кодов разработчики часто сталкиваются с логическими ошибками или зацикливанием выборки. Разберем типичные ошибки, проанализируем причины сбоев и рассмотрим надежные варианты перенумерации справочника.

Анализ типичной логической ошибки при использовании Таблицы Значений

Рассмотрим распространенный подход: разработчик считывает текущие данные справочника в объект ТаблицаЗначений, чтобы зафиксировать список элементов и их текущие коды, а затем запускает цикл для поиска свободных номеров.

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


Процедура ОшибочнаяПеренумерация()
    Спр = СоздатьОбъект("Справочник.Номенклатура");
    ТЗ  = СоздатьОбъект("ТаблицаЗначений");
    ТЗ.НоваяКолонка("Код", "Число");
    ТЗ.НоваяКолонка("Эл");
    
    Спр.ПорядокКодов();
    Спр.ВыбратьЭлементы(0);
    Пока Спр.ПолучитьЭлемент() = 1 Цикл
        ТЗ.НоваяСтрока();
        ТЗ.Код = Число(Спр.Код);
        ТЗ.Эл  = Спр.ТекущийЭлемент();
    КонецЦикла;
    
    ТЗ.Сортировать("Код");
    
    Для Сч = 1 По ТЗ.КоличествоСтрок() Цикл
        Стр = "";
        // Ошибка: поиск идет по статичной ТЗ, которая рассинхронизируется с базой!
        Если ТЗ.НайтиЗначение(Сч, Стр, "Код") = 0 Тогда
            ТекЭл = ТЗ.ПолучитьЗначение(Сч, "Эл");
            Если Спр.НайтиЭлемент(ТекЭл) = 1 Тогда
                Спр.УстановитьАтрибут("Код", Сч);
                Спр.Записать();
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Выясним причину, почему такой алгоритм сбивается и обрабатывает лишь часть элементов:

  1. Таблица значений формируется один раз в начале процедуры и в процессе работы цикла остается неизменной.
  2. Допустим, в базе отсутствует код 10 (свободный номер), а следующему элементу с кодом 11 обработка присваивает освободившийся номер 10 и сохраняет объект методом Записать().
  3. На следующем шаге цикла ищется код 11. В базе данных элемента с кодом 11 уже нет (он стал 10-м), но в памяти в ТаблицаЗначений этот код по-прежнему присутствует в колонке Код.
  4. В итоге поиск ТЗ.НайтиЗначение(Сч, ...) находит старую запись, решает, что код 11 занят, и пропускает шаг. Возникает эффект «потери выборки» и частичного смещения данных.

Особенности модификации данных и выборки в 1С 7.7

При реализации любых механизмов перенумерации важно учитывать базовые правила платформы 1С:Предприятие 7.7:

  1. Смещение курсора индексов: Метод ВыбратьЭлементы() перемещается по индексу B-Tree (например, по порядку кодов). Если внутри цикла Пока Спр.ПолучитьЭлемент() = 1 Цикл менять код текущего или другого элемента, индекс перестраивается «на лету», что приводит к пропуску элементов или бесконечному циклу. Любая модификация кодов должна производиться либо по заранее сохраненному списку ссылок, либо прямым поиском.
  2. Контроль уникальности: При включенном контроле уникальности в метаданных попытка записать элемент с уже существующим номером приведет к ошибке транзакции. В проверке уникальности участвуют в том числе помеченные на удаление элементы.
  3. Оптимизация скорости транзакциями: Поэлементная запись без явных транзакций тратит много времени на обновление файлов БД на диске. Использование НачатьТранзакцию() и ЗафиксироватьТранзакцию() блоками по 200–500 элементов ускоряет обработку в десятки раз.

Способ 1. Уплотнение кодов с оперативной проверкой занятости через НайтиПоКоду

Если стоит задача «сжать» справочник, устранив дыры и заполнив пропущенные коды по порядку, наиболее надежно выполнять проверку доступности номера непосредственно в базе данных через Спр.НайтиПоКоду().

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


Процедура Сформировать()
    Перем Спр, СпрПоиск, ТЗ;
    
    Спр = СоздатьОбъект("Справочник.Номенклатура");
    СпрПоиск = СоздатьОбъект("Справочник.Номенклатура");
    ТЗ  = СоздатьОбъект("ТаблицаЗначений");
    
    ТЗ.НоваяКолонка("Эл");
    
    // Выгружаем фиксированный список элементов в порядке существующих кодов
    Спр.ПорядокКодов();
    Спр.ВыбратьЭлементы(0);
    Пока Спр.ПолучитьЭлемент() = 1 Цикл
        ТЗ.НоваяСтрока();
        ТЗ.Эл = Спр.ТекущийЭлемент();
    КонецЦикла;
    
    НачатьТранзакцию();
    Сч = 0;
    ТЗ.ВыбратьСтроки();
    
    Пока ТЗ.ПолучитьСтроку() = 1 Цикл
        Сч = Сч + 1;
        // Проверяем реальное наличие кода в базе данных, а не в статичной ТЗ
        Если СпрПоиск.НайтиПоКоду(Сч) = 0 Тогда
            ТекЭлемент = ТЗ.Эл;
            Если Спр.НайтиЭлемент(ТекЭлемент) = 1 Тогда
                Спр.УстановитьАтрибут("Код", Сч);
                Спр.Записать();
                
                Если Сч % 500 = 0 Тогда
                    Состояние("Обработано: " + Сч);
                    ЗафиксироватьТранзакцию();
                    НачатьТранзакцию();
                КонецЕсли;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    
    ЗафиксироватьТранзакцию();
    Сообщить("Обработка завершена. Всего элементов: " + Сч);
КонецПроцедуры

Способ 2. Универсальный двухпроходный алгоритм сквозной перенумерации

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

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

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

Посмотрим на пример реализации двухпроходного метода:


Процедура ПолнаяСквознаяПеренумерация()
    Перем Спр, ТЗ, ВсегоЭл, Сдвиг, Индекс, ТекЭл;
    
    Спр = СоздатьОбъект("Справочник.Номенклатура");
    ТЗ  = СоздатьОбъект("ТаблицаЗначений");
    ТЗ.НоваяКолонка("Эл");
    
    Спр.ПорядокКодов();
    Спр.ВыбратьЭлементы(0);
    Пока Спр.ПолучитьЭлемент() = 1 Цикл
        ТЗ.НоваяСтрока();
        ТЗ.Эл = Спр.ТекущийЭлемент();
    КонецЦикла;
    
    ВсегоЭл = ТЗ.КоличествоСтрок();
    Если ВсегоЭл = 0 Тогда
        Возврат;
    КонецЕсли;
    
    Сдвиг = 1000000; // Заведомо свободное смещение
    
    // Шаг 1: Перенос во временный диапазон
    НачатьТранзакцию();
    Для Индекс = 1 По ВсегоЭл Цикл
        ТекЭл = ТЗ.ПолучитьЗначение(Индекс, "Эл");
        Если Спр.НайтиЭлемент(ТекЭл) = 1 Тогда
            Спр.УстановитьАтрибут("Код", Сдвиг + Индекс);
            Спр.Записать();
        КонецЕсли;
    КонецЦикла;
    ЗафиксироватьТранзакцию();
    
    // Шаг 2: Присвоение правильных номеров по порядку от 1
    НачатьТранзакцию();
    Для Индекс = 1 По ВсегоЭл Цикл
        ТекЭл = ТЗ.ПолучитьЗначение(Индекс, "Эл");
        Если Спр.НайтиЭлемент(ТекЭл) = 1 Тогда
            Спр.УстановитьАтрибут("Код", Индекс);
            Спр.Записать();
        КонецЕсли;
    КонецЦикла;
    ЗафиксироватьТранзакцию();
    
    Сообщить("Справочник успешно перенумерован!");
КонецПроцедуры

Рекомендации по выполнению процедуры

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

  1. Создание резервной копии: Всегда делайте выгрузку информационной базы перед массовой модификацией ключевых реквизитов.
  2. Монопольный режим: Перенумерацию следует выполнять в монопольном режиме, чтобы исключить создание новых элементов другими пользователями во время работы алгоритма.
  3. Подчинение и владельцы: Если справочник иерархический или подчиненный, убедитесь, что нумерация ведется «Во всем справочнике». Если установлена серия кодов «В пределах подчинения», перенумерацию необходимо выполнять отдельно в рамках каждой группы/владельца.
  4. Очистка удаленных объектов: Перед нормализацией кодов рекомендуется выполнить удаление помеченных объектов, чтобы старые неиспользуемые записи не резервировали номера.
← На главную