Каждый разработчик на платформе 1С:Предприятие рано или поздно сталкивается с классической проблемой реструктуризации базы данных. Типичный сценарий: вы решили удалить «лишнее» измерение из регистра сведений, сделать периодический регистр непериодическим или изменить его периодичность (например, с «В пределах секунды» на «В пределах дня»). Вы вносите изменения в Конфигураторе, запускаете обновление базы данных и получаете критическую ошибку: «Обнаружены неуникальные записи в регистре сведений». В результате база данных блокируется, обновление не применяется, а режим Предприятия становится недоступным.
В этой статье мы подробно проанализируем ситуацию, выясним причины возникновения этой ошибки и разберем по шагам несколько эффективных способов борьбы с дублями как на этапе проектирования (профилактика), так и в экстренных случаях, когда база данных уже отказывается обновляться.
Для понимания природы проблемы вспомним, как платформа 1С обеспечивает уникальность данных в регистре сведений. В отличие от справочников или документов, у записей регистра сведений нет уникального идентификатора (GUID). Уникальность записи определяется исключительно ключевыми полями. Для непериодического регистра ключом уникальности является комбинация значений всех его измерений. Для периодического регистра к этой комбинации добавляется еще и Период.
Проанализируем, что происходит при изменении структуры регистра:
Номенклатура и Склад. В регистре хранились две записи: «Лопата — Склад №1» и «Лопата — Склад №2». Если мы удаляем измерение Склад, то для платформы обе эти записи превращаются в одну и ту же ключевую комбинацию: «Лопата». Платформа не может решить, какую именно запись сохранить, а какую удалить (или как объединить их ресурсы), поэтому аварийно прерывает реструктуризацию.Рассмотрим подробнее методы решения этой проблемы, разделив их на превентивные (до изменения структуры) и аварийные (когда конфигуратор уже заблокирован).
Самый правильный и безопасный подход — выявить и устранить потенциальные дубликаты до того, как вы нажмете кнопку сохранения изменений в Конфигураторе. Посмотрим на пример простого запроса, который позволяет локализовать будущие конфликты.
Допустим, мы планируем удалить измерение УдаляемоеИзмерение из регистра сведений ЦеныНоменклатуры. Прежде чем удалять его в конфигураторе, запустим консоль запросов в режиме Предприятия и выполним следующий запрос:
ВЫБРАТЬ
Цены.Период КАК Период,
Цены.Номенклатура КАК Номенклатура,
Цены.ТипЦен КАК ТипЦен,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Цены.Актуальность) КАК КоличествоДублей
ИЗ
РегистрСведений.ЦеныНоменклатуры КАК Цены
СГРУППИРОВАТЬ ПО
Цены.Период,
Цены.Номенклатура,
Цены.ТипЦен
ИМЕЮЩИЕ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Цены.Актуальность) > 1
В данном запросе мы сгруппировали записи по тем полям, которые останутся в регистре после реструктуризации. Секция ИМЕЮЩИЕ отсекает все уникальные комбинации, оставляя только те группы, где количество записей больше одной. Получив этот список, мы можем написать простую обработку, которая свернет данные (например, удалит более старые записи или просуммирует ресурсы), и только после этого приступить к изменению метаданных в Конфигураторе.
Если вы не хотите писать запросы и обработчики вручную, воспользуемся специализированным инструментом. В составе известного программного комплекса «Инструменты разработчика» (ИР) есть мощная обработка — «Подготовка к изменению структуры БД».
Эта обработка работает следующим образом:
Рассмотрим ситуацию, когда изменения в Конфигураторе уже применены, кнопка «Принять» выдала ошибку неуникальности, и база данных теперь не запускается ни в каком режиме. Разберем по шагам алгоритм безопасного отката:
Если конфигурация изменена, реструктуризация «зависла» на этапе применения изменений, но платформа все же позволяет запустить базу в режиме Предприятия (или вы можете запустить ее в режиме отладки), мы можем применить временный программный код для полной очистки проблемного регистра.
Разберем этот процесс по шагам:
ПриНачалеРаботыСистемы и вставьте в самое начало код для очистки регистра сведений с помощью пустого набора записей:
Процедура ПриНачалеРаботыСистемы()
// Временный код для принудительной очистки проблемного регистра
НаборЗаписей = РегистрыСведений.ИмяВашегоРегистра.СоздатьНаборЗаписей();
НаборЗаписей.Записать(Истина); // Записываем пустой набор для очистки таблицы
КонецПроцедуры
Запустите систему в режиме Предприятия. При запуске код мгновенно очистит физическую таблицу регистра сведений. После этого закройте Предприятие, вернитесь в Конфигуратор, удалите этот временный код из модуля приложения и повторите процедуру обновления базы данных. Реструктуризация выполнится мгновенно, так как пустая таблица не содержит конфликтов уникальности.
Важно: Этот метод приводит к полной потере данных в очищаемом регистре. Используйте его только в том случае, если данные регистра не представляют ценности (например, это кэш или история версий) или если вы предварительно выгрузили данные в файл для последующего восстановления.
Для клиент-серверных баз данных под управлением MS SQL Server или PostgreSQL существует радикальный, но очень быстрый способ решения проблемы — прямая очистка таблицы на уровне СУБД. Этот метод незаменим на больших базах, когда стандартный откат изменений занимает слишком много времени.
Выполним следующие шаги:
ПолучитьСтруктуруХраненияБазыДанных(). Также можно найти таблицу по имени метаданных в таблице базы данных. Обычно она имеет вид _InfoRegXXXX (где XXXX — порядковый номер таблицы).TRUNCATE работает значительно быстрее, чем DELETE, так как не записывает каждую строку в журнал транзакций):
TRUNCATE TABLE _InfoReg1234; -- Вместо _InfoReg1234 укажите имя вашей таблицы
TRUNCATE сделайте резервную копию этой таблицы или выгрузите данные в промежуточный файл, после чего удалите дубли и загрузите обратно через стандартный объект платформы НаборЗаписей.Если вы боитесь повредить существующие важные данные в работающем регистре сведений, применим архитектурный обходной путь. Вместо изменения структуры старого регистра мы создадим новый.
Разберем этот алгоритм:
НовыеЦеныНоменклатуры) с требуемой новой структурой измерений и ресурсов.
Выборка = РегистрыСведений.СтарыйРегистр.Выбрать();
Пока Выборка.Следующий() Цикл
// Создаем запись в новом регистре с проверкой на дубли по новым измерениям
НоваяЗапись = РегистрыСведений.НовыйРегистр.СоздатьМенеджерЗаписи();
ЗаполнитьЗначенияСвойств(НоваяЗапись, Выборка);
Попытка
НоваяЗапись.Записать(Истина);
Исключение
// Обработка конфликта уникальности: логируем или объединяем ресурсы
ЗаписатьЛогОшибки("Дубль для номенклатуры: " + Выборка.Номенклатура);
КонецПопытки;
КонецЦикла;
Мы подробно изучили все возможные варианты борьбы с неуникальными записями в регистрах сведений 1С. Помните, что залог успешного обновления — это всегда предварительный анализ данных с помощью запросов и своевременное создание резервных копий перед любыми административными действиями.