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

· 9 мин

Содержание

Ошибка Записи регистра сведений стали неуникальными означает, что после изменения структуры или проверки базы у двух и более записей регистра совпал ключ: период и все измерения. Платформа не может построить уникальный индекс и останавливает операцию. Лечится это в два хода: найти записи-дубли по новому ключу и удалить или объединить лишние, а затем повторить обновление или тестирование.

Частое заблуждение: «база сломалась, нужно поднимать бэкап». Чаще всего данные целы. Просто разработчик убрал измерение, сменил периодичность или укоротил строку, и старые записи, раньше разные, теперь выглядят одинаково. Если ошибка выскочила при обновлении конфигурации базы данных, изменения в базу ещё не попали, и спокойно разобраться время есть.

Откуда в регистре сведений берутся неуникальные записи

Ключ записи независимого регистра — это период (если регистр периодический) плюс все измерения. У подчинённого регистратору ключ другой: регистратор и номер строки. Ошибка возникает, когда этот набор полей перестаёт различать записи.

Основные сценарии:

  • 🔧 Удалили измерение. Было Склад + Номенклатура, стало только Номенклатура. Две записи по разным складам схлопываются в одну.
  • 📅 Укрупнили периодичность. Цены лежали по дням, регистр перевели на «В пределах месяца». Пять записей за март по одному товару дают пять одинаковых ключей.
  • ✂️ Уменьшили длину строки измерения. Строка 50 символов обрезана до 10. Значения Договор поставки 01 и Договор поставки 02 после обрезки совпадают.
  • 🔀 Сменили тип измерения. Был составной тип, стал один; значения другого типа превращаются в пустые и совпадают между собой.
  • 🔁 Сменили режим записи. Регистр из «Подчинение регистратору» сделали «Независимым». Ключ теперь строится по периоду и измерениям, а там повторов полно.
  • 🧹 Очистили битые ссылки. В тестировании и исправлении выбран вариант «Очищать ссылки». Две записи ссылались на разные удалённые элементы, после очистки обе ссылаются на пустую ссылку — ключ совпал.
  • 💥 Повреждён индекс. В файловой базе после аварийного отключения, в клиент-серверной после ручной работы с таблицами. Дубли физически лежат в таблице, и проверка их находит.
  • 🔤 Переезд между СУБД. При загрузке .dt в другую СУБД строки могут сравниваться иначе (регистр букв, хвостовые пробелы). Это вероятная причина, если ошибка появилась именно при загрузке выгрузки, а не при обновлении.

Где появилась ошибка, подсказывает и путь решения:

Когда вы увидели сообщение о неуникальных записях?

Как найти дубли записей регистра сведений через консоль запросов

Первым делом выясните, какой регистр виноват. Платформа обычно называет его в тексте ошибки или в протоколе тестирования. В клиент-серверной базе в сообщении бывает имя таблицы вида _InfoRg1234 — о том, как сопоставить его с регистром, ниже.

Дальше нужна внешняя обработка «Консоль запросов». Откройте её в пользовательском режиме через Главное меню → Файл → Открыть. Нужны права на открытие внешних обработок.

Главный приём: группируйте по новому ключу

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

Пример: регистр ЦеныПоставщика с измерениями Поставщик и Номенклатура переводят с периодичности «День» на «Месяц».

ВЫБРАТЬ
    НАЧАЛОПЕРИОДА(Ц.Период, МЕСЯЦ) КАК Месяц,
    Ц.Поставщик КАК Поставщик,
    Ц.Номенклатура КАК Номенклатура,
    КОЛИЧЕСТВО(*) КАК ЧислоЗаписей
ИЗ
    РегистрСведений.ЦеныПоставщика КАК Ц
СГРУППИРОВАТЬ ПО
    НАЧАЛОПЕРИОДА(Ц.Период, МЕСЯЦ),
    Ц.Поставщик,
    Ц.Номенклатура
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(*) > 1

Для других сценариев меняется только группировка:

Что менялиКак группировать
Удалили измерениеПо всем измерениям, кроме удалённого
Укрупнили периодичностьНАЧАЛОПЕРИОДА(Период, …) до новой периодичности
Укоротили строкуПОДСТРОКА(Измерение, 1, N), где N — новая длина
Сменили тип измеренияПо измерению, отбирая записи, где значение другого типа (ТИПЗНАЧЕНИЯ)
Сделали регистр независимымПо Период и всем измерениям

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

Поиск дублирующихся записей на уровне SQL

Если ошибка возникает при тестировании, загрузке .dt или падает построение индекса в СУБД, смотреть приходится прямо в таблицу. Типичные тексты ошибки от СУБД:

  • MS SQL: The CREATE UNIQUE INDEX statement terminated because a duplicate key was found for the object name 'dbo._InfoRg1234';
  • PostgreSQL: could not create unique index и строка Key (...)=(...) is duplicated.

Как узнать имя таблицы и полей

Имена таблиц в СУБД служебные. Соответствие даёт функция ПолучитьСтруктуруХраненияБазыДанных(). Выполните её на сервере, например во внешней обработке:

&НаСервере
Процедура ПоказатьСтруктуруНаСервере()
    Имена = Новый Массив;
    Имена.Добавить("РегистрСведений.ЦеныПоставщика");
    Структура = ПолучитьСтруктуруХраненияБазыДанных(Имена, Истина);
    Для Каждого СтрокаТаблицы Из Структура Цикл
        Сообщить(СтрокаТаблицы.ИмяТаблицы + " = " + СтрокаТаблицы.ИмяТаблицыХранения);
        Для Каждого Поле Из СтрокаТаблицы.Поля Цикл
            Сообщить("    " + Поле.ИмяПоля + " = " + Поле.ИмяПоляХранения);
        КонецЦикла;
    КонецЦикла;
КонецПроцедуры

Второй параметр Истина возвращает имена в том виде, как они записаны в СУБД. Основная таблица регистра будет вида _InfoRg1234, поле периода — _Period, ссылочное измерение — _Fld1235RRef.

Запрос на дубли

Номера таблиц и полей ниже условные — подставьте свои.

-- MS SQL и PostgreSQL
SELECT _Period, _Fld1235RRef, _Fld1236RRef, COUNT(*) AS cnt
FROM _InfoRg1234
GROUP BY _Period, _Fld1235RRef, _Fld1236RRef
HAVING COUNT(*) > 1;

В PostgreSQL 1С создаёт имена в нижнем регистре: _inforg1234, _fld1235rref. Запрос только на чтение, его можно выполнять при работающих пользователях.

Исправление средствами конфигуратора и встроенного языка

Это основной и безопасный путь. Данные меняются через платформу, все проверки и подписки срабатывают как положено.

Шаг 1. Откатите изменение структуры

Если ошибка возникла при Обновить конфигурацию базы данных, база осталась в старой структуре. Дальше два варианта:

  1. Временно вернуть основную конфигурацию к структуре базы: Конфигурация → Конфигурация базы данных → Вернуться к конфигурации БД. Доработки при этом пропадут из основной конфигурации, поэтому сначала сохраните её в файл .cf.
  2. Оставить доработку в основной конфигурации и чистить данные через внешнюю обработку: она работает по структуре базы данных, а не по основной конфигурации.

Второй вариант удобнее: доработка остаётся на месте, вы только подготавливаете данные.

Шаг 2. Удалите лишние записи обработкой

Продолжим пример с ценами. Оставляем последнюю запись месяца, остальные удаляем:

&НаСервере
Процедура УдалитьЛишниеНаСервере()
    Запрос = Новый Запрос;
    Запрос.Текст =
    "ВЫБРАТЬ
    |    Ц.Период КАК Период,
    |    Ц.Поставщик КАК Поставщик,
    |    Ц.Номенклатура КАК Номенклатура
    |ИЗ
    |    РегистрСведений.ЦеныПоставщика КАК Ц
    |        ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
    |            НАЧАЛОПЕРИОДА(Т.Период, МЕСЯЦ) КАК Месяц,
    |            Т.Поставщик КАК Поставщик,
    |            Т.Номенклатура КАК Номенклатура,
    |            МАКСИМУМ(Т.Период) КАК Последний
    |        ИЗ
    |            РегистрСведений.ЦеныПоставщика КАК Т
    |        СГРУППИРОВАТЬ ПО
    |            НАЧАЛОПЕРИОДА(Т.Период, МЕСЯЦ),
    |            Т.Поставщик,
    |            Т.Номенклатура) КАК Гр
    |        ПО (НАЧАЛОПЕРИОДА(Ц.Период, МЕСЯЦ) = Гр.Месяц)
    |            И Ц.Поставщик = Гр.Поставщик
    |            И Ц.Номенклатура = Гр.Номенклатура
    |            И Ц.Период < Гр.Последний";

    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        МЗ = РегистрыСведений.ЦеныПоставщика.СоздатьМенеджерЗаписи();
        МЗ.Период = Выборка.Период;
        МЗ.Поставщик = Выборка.Поставщик;
        МЗ.Номенклатура = Выборка.Номенклатура;
        МЗ.Удалить();
    КонецЦикла;
КонецПроцедуры

Менеджер записи удаляет запись по значениям ключевых полей, поэтому читать её заранее не нужно. Если удалять тысячи записей, разбейте работу на порции, чтобы не держать одну длинную транзакцию.

Если нужно не удалить, а сложить ресурсы (например, объёмы по двум складам при удалении измерения), логика другая: прочитайте группу, запишите сумму в оставшуюся запись, остальные удалите.

Шаг 3. Повторите обновление

Запустите запрос на дубли ещё раз. Пусто — выполняйте Конфигурация → Обновить конфигурацию базы данных (F7). Обновление структуры требует монопольного доступа: попросите пользователей выйти.

Особый случай: тестирование и исправление

Если дубли появились в ТИИ (Администрирование → Тестирование и исправление в конфигураторе), проверьте настройку для ссылок на несуществующие объекты. Вариант «Очищать ссылки» и создаёт совпадения. Вариант «Создавать объекты» оставит ключи различными: появятся элементы-заглушки, которые потом можно разобрать вручную.

В файловой базе при подозрении на повреждение запустите ТИИ с реиндексацией таблиц. До этого проверьте файл утилитой chdbfl.exe из каталога bin платформы. В клиент-серверной базе реиндексацию выполняет СУБД.

Радикальный путь: удаление дублей в СУБД и перестроение индексов

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

Перед любым UPDATE или DELETE сделайте полную резервную копию базы средствами СУБД. Работайте, когда в базе нет пользователей: заблокируйте начало сеансов в консоли кластера. Активные сеансы при этом нужно завершить заранее, их работа будет прервана.

MS SQL

WITH d AS (
    SELECT ROW_NUMBER() OVER (
        PARTITION BY _Period, _Fld1235RRef, _Fld1236RRef
        ORDER BY (SELECT NULL)) AS rn
    FROM _InfoRg1234
)
DELETE FROM d WHERE rn > 1;

ALTER INDEX ALL ON _InfoRg1234 REBUILD;

PostgreSQL

DELETE FROM _inforg1234
WHERE ctid IN (
    SELECT ctid FROM (
        SELECT ctid, ROW_NUMBER() OVER (
            PARTITION BY _period, _fld1235rref, _fld1236rref
            ORDER BY ctid) AS rn
        FROM _inforg1234
    ) t WHERE rn > 1
);

REINDEX TABLE _inforg1234;

Оба скрипта оставляют в каждой группе одну произвольную строку. Если важно, какую именно, задайте порядок в ORDER BY, например по значению ресурса. Сначала выполните скрипт внутри транзакции и проверьте число удалённых строк: оно должно совпасть с подсчётом из запроса на дубли (сумма cnt − 1 по группам).

Если у регистра включены итоги среза последних или первых, после правки в СУБД запустите тестирование и исправление с пересчётом итогов. Иначе срезы будут показывать удалённые значения.

Какой способ выбрать

СпособКогда подходитРиски
Обработка в 1СДубли по новому ключу перед обновлениемМинимальные, нужен доступ к внешним обработкам
Вернуться к конфигурации БДНужно срочно вернуть базу в работуТеряются доработки, если не сохранили .cf
ТИИ с другими опциямиОшибка в ходе тестирования и исправленияЭлементы-заглушки, которые надо разбирать
Скрипт в СУБДПовреждён индекс, не грузится .dtИзменения без контроля платформы, нужен бэкап

Как не допустить неуникальности записей регистра сведений снова

  • 🧪 Проверяйте данные до изменения ключа. Перед удалением измерения или сменой периодичности выполните группирующий запрос на копии рабочей базы.
  • 🧩 Меняйте структуру в два этапа. Создайте новый регистр с нужным ключом, перенесите в него данные обработчиком обновления, а старый удалите следующим релизом.
  • 🔌 Не пишите в таблицы мимо платформы. Загрузки и интеграции — через объекты 1С, а не через INSERT в СУБД.
  • 🔋 Защитите файловую базу от аварий. ИБП на сервере, регулярные копии, ТИИ по расписанию в нерабочее время.
  • 📋 Тестируйте обновление на копии. Особенно если в конфигурации есть доработки типовых регистров.
Перед обновлением доработанного регистра
Выполнено: 0 / 7

Частые вопросы

Пропадут ли данные, если обновление прервалось этой ошибкой?
Нет. Реструктуризация выполняется целиком или не выполняется совсем, база остаётся в прежней структуре.
Можно ли просто снять у регистра признак уникальности?
Нет. Ключ записи регистра сведений всегда уникален, такой настройки в свойствах регистра нет.
Почему дубли видны в SQL, но не видны в консоли запросов?
В консоли вы смотрите через текущую структуру и текущий индекс. Если индекс повреждён или дубли появятся только по новому ключу, нужно группировать по новому ключу или смотреть в таблицу СУБД.
Ошибка возникла на типовой конфигурации без доработок. Что это значит?
Скорее всего, в данных есть записи, которые обработчики нового релиза не ожидают, например с пустыми или битыми ссылками. Найдите их запросом по ключу нового релиза и разберите до повторного обновления.
Как понять, какая из двух записей правильная?
Сравните ресурсы и реквизиты: часто одна запись содержит актуальное значение, а вторая устаревшее. Если обе важны, решение принимает ответственный за данные, а не администратор.