Как исправить ошибку проверки дубликатов в 1С, которая мешает записывать и удалять объекты?

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

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

Анализ проблемы: почему код работает некорректно

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

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

Решение №1: Быстрое исправление текущего кода

Проанализируем самый простой способ заставить код работать — добавим исключение текущей ссылки. Чтобы система не находила «саму себя», нам нужно проверить, совпадает ли ссылка найденного в выборке объекта со ссылкой текущего объекта, который мы записываем. Разберем, как это реализовать в коде:


Функция Проверка()
    Выборка = Справочники.Партии.Выбрать();
    Пока Выборка.Следующий() Цикл 
        
        // ВАЖНО: Пропускаем текущий объект, чтобы не сравнивать его с самим собой
        Если Выборка.Ссылка = ЭтотОбъект.Ссылка Тогда
            Продолжить;
        КонецЕсли;

        // Также стоит пропустить объекты, уже помеченные на удаление
        Если Выборка.ПометкаУдаления Тогда
            Продолжить;
        КонецЕсли;

        // Далее идет логика сравнения ТЧ...
        // ...
    КонецЦикла;
КонецФункции

Посмотрим на процедуру ПередЗаписью. Чтобы разрешить пользователю удалять (помечать на удаление) объект, даже если он является дубликатом, добавим проверку свойства ПометкаУдаления:


Процедура ПередЗаписью(Отказ)
    // Если мы просто хотим пометить объект на удаление, проверку делать не нужно
    Если ЭтотОбъект.ПометкаУдаления Тогда
        Возврат;
    КонецЕсли;

    Если Проверка() = 1 Тогда
        Отказ = Истина;
        Сообщить("Нельзя сформировать комплект: такой состав уже существует в базе!");
    КонецЕсли;
КонецПроцедуры

Решение №2: Оптимальный подход через запрос

Хотя исправление циклов решает проблему «самосравнения», оно не решает проблему скорости. Рассмотрим более профессиональный метод — использование запроса. Запросы работают на уровне СУБД и позволяют сравнить составы табличных частей значительно быстрее. Проанализируем шаги реализации этого метода:

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

2. Текст запроса. Мы передадим состав нашей ТЧ в запрос как параметр и сравним его с данными табличных частей в базе.

Рассмотрим пример функции, использующей этот подход:


Функция СоставУжеСуществует()
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    ПартииЭлементыПартии.Ссылка КАК Ссылка
    |ИЗ
    |    Справочник.Партии.ЭлементыПартии КАК ПартииЭлементыПартии
    |ГДЕ
    |    ПартииЭлементыПартии.Ссылка <> &ТекущаяСсылка
    |    И ПартииЭлементыПартии.Ссылка.ПометкаУдаления = ЛОЖЬ
    |    И ПартииЭлементыПартии.Номенклатура В(&СписокНоменклатуры)
    |ГРУППИРОВАТЬ ПО
    |    ПартииЭлементыПартии.Ссылка
    |ИМЕЮЩИЕ
    |    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ПартииЭлементыПартии.Номенклатура) = &КоличествоСтрок";

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

    РезультатЗапроса = Запрос.Выполнить();
    
    // Если запрос что-то нашел, нужно детально проверить количество по каждой позиции
    Если НЕ РезультатЗапроса.Пустой() Тогда
        // Здесь можно реализовать дополнительную проверку количеств для найденных ссылок
        Возврат Истина;
    КонецЕсли;

    Возврат Ложь;
КонецФункции

Нюансы при работе с табличными частями

Разберем важные детали, которые помогут сделать проверку максимально точной:

  1. Сворачивание данных. Как уже было сказано, если пользователь введет две строки: "Яблоки — 5 кг" и "Яблоки — 3 кг", а в существующем комплекте это записано одной строкой "Яблоки — 8 кг", обычное сравнение строк не сработает. Метод Свернуть("Номенклатура", "Количество") приводит данные к единому виду перед сравнением.
  2. Режим обмена данными. Всегда проверяйте свойство ОбменДанными.Загрузка. Если данные загружаются из другой базы, проверку на дубликаты обычно стоит отключать, чтобы не прерывать процесс синхронизации.
  3. Использование Хеширования. Для очень больших справочников (сотни тысяч записей) даже запрос к ТЧ может быть медленным. В таких случаях программисты создают в справочнике строковый реквизит ХешСостава. При записи объекта в него сохраняется зашифрованная строка (например, MD5 от отсортированного списка товаров и их количеств). Тогда проверка на уникальность сводится к поиску одного совпадения по индексированной строке, что происходит мгновенно.

Подведем итог: для решения проблемы автора было достаточно добавить условие Если Выборка.Ссылка = ЭтотОбъект.Ссылка Тогда Продолжить; КонецЕсли;. Однако для качественной разработки в среде 1С «Предприятие» следует переходить от переборов в циклах к использованию запросов с обязательным исключением текущей ссылки и проверкой режима пометки удаления.

← На главную