Как исправить ошибку «Запись с такими ключевыми полями существует» при записи в регистр сведений?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

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

Анализ причины возникновения ошибки

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

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

  1. Обрезка строк по длине: Если измерение имеет строковый тип с ограниченной длиной (например, 20 символов), а записываемое значение длиннее, платформа отсекает лишние символы. Если две разные строки после обрезки становятся одинаковыми — возникает конфликт.
  2. Невидимые символы и пробелы: Пробелы в конце строки (trailing spaces) игнорируются СУБД при проверке уникальности в строковых полях. Строки «+79001112233» и «+79001112233 » для регистра будут идентичны.
  3. Дубли в исходной таблице: Перед записью данных из таблицы значений (ТЗ) необходимо убедиться, что сама ТЗ не содержит строк с одинаковыми измерениями.
  4. Регистр символов: В зависимости от настроек базы данных (Case Sensitivity), значения «Абонент» и «абонент» могут считаться как разными, так и одинаковыми.

Методика правильной подготовки данных к записи

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


// Предположим, у нас есть ТаблицаЗначений "СписокТелефонов" с колонками ЛицевойСчет и Телефон
// 1. Очистка и нормализация данных в цикле
Для Каждого Стр Из СписокТелефонов Цикл
    // Удаляем лишние пробелы по краям
    Стр.Телефон = СокрЛП(Стр.Телефон);
    
    // Если ЛицевойСчет - это ссылка, проверяем на пустое значение
    Если Стр.ЛицевойСчет.Пустая() Тогда
        // Обработка пустых значений, которые тоже могут создать дубли
    КонецЕсли;
КонецЦикла;

// 2. Сворачивание таблицы по ключевым полям (измерениям)
// Это гарантирует уникальность комбинации ЛицевойСчет + Телефон внутри самой таблицы
СписокТелефонов.Свернуть("ЛицевойСчет, Телефон", "СуммаРесурса"); 

Метод Свернуть() крайне важен. Если в вашем регистре есть ресурсы, их нужно указать во втором параметре метода, чтобы система просуммировала их (если это числа) или оставила первое встретившееся значение.

Эффективная запись данных через Набор Записей

Для массовой записи данных в независимый регистр сведений использование цикла с созданием менеджера записи на каждую строку является крайне непроизводительным. Правильнее использовать объект НаборЗаписей. Рассмотрим оптимальный способ записи:


Процедура ЗаполнитьРегистрТелефонов(ТЗДанных)
    
    // Создаем объект набора записей
    НаборЗаписей = РегистрыСведений.Модус_ТелефоныАбонентов.СоздатьНаборЗаписей();
    
    // Если нужно полностью перезаписать регистр, фильтры не устанавливаем.
    // Метод Загрузить() переносит данные из ТЗ в память набора
    НаборЗаписей.Загрузить(ТЗДанных);
    
    // Записываем набор в базу данных. 
    // Параметр Истина означает замещение существующих записей в рамках установленного фильтра
    Попытка
        НаборЗаписей.Записать(Истина);
        Сообщить("Данные успешно записаны.");
    Исключение
        Инфо = ИнформацияОбОшибке();
        Сообщить("Ошибка при записи: " + Инфо.Описание);
    КонецПопытки;
    
КонецПроцедуры

Важный момент, который часто упускают: метод НаборЗаписей.Загрузить(ТЗДанных) требует, чтобы имена колонок в таблице значений в точности совпадали с именами измерений и ресурсов регистра. Если в ТЗ есть лишние колонки, они будут проигнорированы, но все обязательные поля должны присутствовать.

Работа с большими объемами данных (свыше 1 млн записей)

В обсуждении упоминалось, что объем данных составляет около 1,5 миллиона записей. Попытка записать такой объем одним набором может привести к критическому потреблению оперативной памяти или переполнению лога транзакций SQL-сервера. Рассмотрим, как проанализировать ситуацию и применить порционную запись.

Разделим процесс на порции (транзакции) по 10 000 — 50 000 записей. Это позволит системе «дышать» и не блокировать таблицу регистра на длительное время.


// Пример порционной записи данных
РазмерПорции = 10000;
ВсегоСтрок = ТЗДанных.Количество();
ТекущаяСтрока = 0;

Пока ТекущаяСтрока < ВсегоСтрок Цикл
    
    Набор = РегистрыСведений.Модус_ТелефоныАбонентов.СоздатьНаборЗаписей();
    
    // Наполняем набор только частью данных
    Для Счётчик = 1 По РазмерПорции Цикл
        Если ТекущаяСтрока >= ВсегоСтрок Тогда
            Прервать;
        КонецЕсли;
        
        СтрокаТЗ = ТЗДанных[ТекущаяСтрока];
        НоваяЗапись = Набор.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяЗапись, СтрокаТЗ);
        
        ТекущаяСтрока = ТекущаяСтрока + 1;
    КонецЦикла;
    
    // Записываем порцию с параметром Ложь, чтобы не затирать весь регистр, 
    // а добавлять новые записи (если регистр был пуст или мы уверены в отсутствии дублей)
    Набор.Записать(Ложь); 
    
КонецЦикла;

Подводный камень: Длина измерения

Как выяснилось в ходе обсуждения, наиболее коварная причина ошибки «Запись с такими ключевыми полями существует» кроется в настройках метаданных. Если измерение Телефон имеет длину 20 символов, а вы пытаетесь записать туда список телефонов через запятую (например, «+79001112233, +79004445566»), 1С обрежет строку до 20 знаков.

Проанализируем пример:
Строка 1: «+79001112233, +79000000001» -> Обрезка до 20: «+79001112233, +79000»
Строка 2: «+79001112233, +79000000002» -> Обрезка до 20: «+79001112233, +79000»

В результате в базу данных пытаются записаться две абсолютно одинаковые строки с точки зрения ключа. Решение: всегда проверяйте соответствие максимальной длины входящих данных и длины, установленной в свойствах измерения регистра. Для хранения нормализованных телефонов достаточно 15-20 символов, но данные должны быть предварительно очищены от «хвостов» и перечислений.

Использование отладчика для локализации ошибки

Если ошибка сохраняется, не пренебрегайте использованием отладчика. В момент возникновения исключения перед вызовом Записать() можно проверить содержимое набора. Для этого в табло или в окне вычисления выражений введите НаборЗаписей.Выгрузить(). Полученную таблицу можно сохранить в файл и с помощью стандартных средств поиска дублей (например, в Excel) быстро найти проблемные строки, которые вызывают конфликт уникальности.

Подводя итог, можно сказать, что стабильная запись в регистры сведений требует строгого соблюдения уникальности ключевых полей, предварительной нормализации строковых данных (использование СокрЛП и контроля длины) и грамотного выбора способа записи (через объекты НаборЗаписей).

← На главную