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

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

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

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

Выясняем причину: почему появляются дубли?

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

  1. Загрузка данных при обмене: Если перенос данных в базу выполнялся с установленным признаком ОбменДанными.Загрузка = Истина, платформа 1С отключает большинство стандартных бизнес-проверок. Если при этом на уровне СУБД (MS SQL Server, PostgreSQL) по какой-то причине был поврежден или отсутствовал уникальный индекс по полям измерений, СУБД позволяет записать дублирующиеся строки.
  2. Сбои при реструктуризации базы данных: При обновлении конфигурации или аварийном завершении работы сервера в процессе реструктуризации таблицы СУБД могут потерять свои индексы целостности.
  3. Прямая запись в таблицы СУБД: Выполнение сторонних SQL-скриптов в обход объектной модели 1С.

Когда мы пытаемся прочитать такой регистр стандартными методами платформы (например, через менеджер записи), система обращается к СУБД и ожидает получить ровно одну строку. Обнаружив дубли, внутренние механизмы платформы выдают ошибку и транзакция отменяется. Именно поэтому код автора темы, представленный ниже, возвращал ошибку при попытке выполнить метод Записать():


НаборЗаписей = РегистрыСведений.НастройкиУчетаВремени.СоздатьНаборЗаписей(); 
НаборЗаписей.Загрузить(ОставляемыеЗаписи);  
НаборЗаписей.Записать();

Для решения этой проблемы нам необходимо обойти стандартные триггеры платформы и очистить некорректные данные. Рассмотрим рабочие варианты решения по шагам.

Способ 1. Программное удаление лишней записи с отключением обработчиков

Рассмотрим наиболее правильный с точки зрения архитектуры 1С способ. Чтобы платформа позволила нам записать измененный набор данных без вызова внутренних проверок и ошибок менеджера записи, мы воспользуемся свойством ОбменДанными.Загрузка. Установка этого свойства в значение Истина временно отключает выполнение подписок на события и встроенных обработчиков в модуле регистра, которые обычно и вызывают ошибку при записи дублирующихся строк.

Разберем по шагам код обработки для удаления дублирующей записи:


// 1. Создаем набор записей для нашего проблемного регистра
НаборЗаписей = РегистрыСведений.НастройкиУчетаВремени.СоздатьНаборЗаписей();

// 2. Считываем все существующие записи (включая дубли) из базы данных в оперативную память
НаборЗаписей.Прочитать();

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

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

Способ 2. Полная программная очистка регистра и повторная запись данных

Если по какой-то причине метод НаборЗаписей.Прочитать() вызывает неустранимую ошибку на уровне платформы из-за присутствия дублей, мы можем пойти другим путем. Мы можем предварительно прочитать данные через безопасный механизм запросов (запросы работают напрямую к СУБД и игнорируют ограничения уникальности ключей 1С), затем полностью очистить регистр сведений, записав в него пустой набор, а после этого вернуть обратно одну корректную строку.

Рассмотрим подробнее реализацию этого алгоритма:


// Шаг 1. Получаем ровно одну (любую из двух одинаковых) запись с помощью запроса
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1
    |   *
    |ИЗ
    |   РегистрСведений.НастройкиУчетаВремени КАК НастройкиУчетаВремени";

ТаблицаДанных = Запрос.Выполнить().Выгрузить();

Если ТаблицаДанных.Количество() > 0 Тогда
    
    // Шаг 2. Полностью очищаем таблицу регистра в базе данных
    // Для этого создаем пустой набор записей и записываем его с замещением
    НаборОчистки = РегистрыСведений.НастройкиУчетаВремени.СоздатьНаборЗаписей();
    НаборОчистки.ОбменДанными.Загрузка = Истина; // Отключаем контроли
    НаборОчистки.Записать(Истина); // Запись пустого набора полностью удаляет все строки в таблице БД
    
    // Шаг 3. Восстанавливаем сохраненную единственную запись
    НаборЗаписи = РегистрыСведений.НастройкиУчетаВремени.СоздатьНаборЗаписей();
    НаборЗаписи.Загрузить(ТаблицаДанных);
    НаборЗаписи.ОбменДанными.Загрузка = Истина;
    
    Попытка
        НаборЗаписи.Записать(Истина);
        Сообщить("Регистр успешно очищен и заполнен одной корректной строкой.");
    Исключение
        Сообщить("Ошибка при восстановлении записи: " + ОписаниеОшибки());
    КонецПопытки;
    
КонецЕсли;

Этот метод является абсолютно надежным, так как операция записи пустого набора (Шаг 2) гарантированно очищает физическую таблицу на уровне СУБД, снимая любые блокировки и ошибки уникальности индексов.

Способ 3. Прямое удаление дублей на уровне СУБД (для SQL-баз)

Если ваша база данных работает под управлением Microsoft SQL Server или PostgreSQL, а запуск конфигуратора или выполнение внешних обработок в режиме «1С:Предприятие» по какой-то причине заблокированы, проблему можно быстро устранить с помощью SQL-скрипта в консоли СУБД.

Прежде всего, нам необходимо узнать физическое имя таблицы нашего регистра в базе SQL. Для этого в платформе 1С можно выполнить функцию ПолучитьСтруктуруХраненияБазыДанных() или использовать специализированные обработки получения структуры БД. Физическое имя таблицы регистра сведений будет иметь формат вида _InfoRegXXXX (где XXXX — это числовой идентификатор таблицы).

После определения имени таблицы мы можем запустить СУБД-клиент (например, SQL Server Management Studio) и выполнить следующий запрос (пример для MS SQL Server с использованием обобщенного табличного выражения CTE):


WITH CTE_Duplicates AS (
    SELECT 
        *, 
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as RowNumber
    FROM 
        _InfoRegXXXX -- Замените на реальное имя таблицы вашего регистра
)
DELETE FROM CTE_Duplicates WHERE RowNumber > 1;

Важно: Функция ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) присваивает порядковый номер каждой строке в нашей таблице. Условие WHERE RowNumber > 1 указывает СУБД удалить все строки, кроме самой первой. Это мгновенно решает проблему дублирования данных без вреда для логической структуры 1С.

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

TRUNCATE TABLE _InfoRegXXXX; -- Замените на реальное имя таблицы вашего регистра

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

Мы проанализировали причины возникновения дубликатов в независимых регистрах сведений без измерений и рассмотрели три эффективных метода решения проблемы: от деликатного программного удаления лишних строк с помощью свойства ОбменДанными.Загрузка до радикальной очистки физических таблиц на уровне СУБД. Выбор конкретного метода зависит от архитектуры вашей базы данных (файловая или клиент-серверная) и доступных прав администрирования.

← На главную