При разработке конфигураций на платформе 1С часто возникает задача предотвращения создания дублирующих наборов данных. Типичный пример — справочник Комплекты, где каждый элемент состоит из списка номенклатуры в определенном количестве. Нам необходимо гарантировать, что в системе не будет двух разных комплектов с абсолютно одинаковым составом.
В этой статье мы разберем ошибки начинающих разработчиков, проанализируем причины низкой производительности кода и реализуем надежный механизм проверки уникальности с помощью запросов.
Рассмотрим ситуацию, с которой столкнулся автор темы. Его код пытался перебирать все существующие комплекты в цикле и сравнивать каждую строку табличной части текущего объекта со строками из базы данных. Выясним, почему такой подход не работает корректно и приводит к ошибкам:
СокрЛП() к ссылкам (Номенклатура) принудительно преобразует их в строковое представление. Это не только замедляет работу, но и теоретически может привести к ложным срабатываниям, если наименования объектов совпадают, а сами ссылки — нет. Сравнение должно идти строго по ссылкам.Выборка.ЭлементыКомплекта внутри цикла — это «антипаттерн». При каждой итерации система обращается к базе данных за данными табличной части, что при росте справочника приведет к катастрофическому падению производительности.ПередЗаписью срабатывает всегда, включая моменты, когда пользователь просто хочет пометить объект на удаление. Если в этот момент сработает логика проверки, система выдаст ошибку и не даст удалить объект.Прежде чем приступать к проверке состава, необходимо убедиться, что она вообще уместна в данный момент. Разберем правильный каркас процедуры ПередЗаписью в модуле объекта:
Процедура ПередЗаписью(Отказ)
// 1. Если запись инициирована обменом данными, проверки обычно пропускаются
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// 2. Если объект помечается на удаление, проверять уникальность состава не нужно
Если ПометкаУдаления Тогда
Возврат;
КонецЕсли;
// 3. Вызываем функцию проверки
Если Не СоставКомплектаУникален() Тогда
Отказ = Истина;
Сообщить("В системе уже существует комплект с аналогичным составом номенклатуры!");
КонецЕсли;
КонецПроцедуры
Вместо перебора всех записей программно, мы поручим поиск дублей СУБД. Проанализируем ситуацию: два комплекта идентичны, если количество строк в них совпадает и каждая строка одного комплекта (Номенклатура + Количество) присутствует в другом.
Рассмотрим по шагам реализацию функции СоставКомплектаУникален():
Функция СоставКомплектаУникален()
// Сначала подготовим таблицу состава текущего объекта
ТаблицаСостава = ЭлементыКомплекта.Выгрузить();
// Сворачиваем на случай, если одна и та же позиция введена двумя строками
ТаблицаСостава.Свернуть("Номенклатура", "Количество");
КоличествоСтрок = ТаблицаСостава.Количество();
Если КоличествоСтрок = 0 Тогда
Возврат Истина; // Пустой состав считаем условно допустимым или обрабатываем иначе
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВТ_Состав.Номенклатура КАК Номенклатура,
| ВТ_Состав.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_ТекущийСостав
|ИЗ
| &ТЧ КАК ВТ_Состав
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| КомплектыСостав.Ссылка КАК Ссылка
|ИЗ
| Справочник.Комплекты.ЭлементыКомплекта КАК КомплектыСостав
|ГДЕ
| КомплектыСостав.Ссылка <> &Ссылка
| И КомплектыСостав.Ссылка.ПометкаУдаления = ЛОЖЬ
|
|СГРУППИРОВАТЬ ПО
| КомплектыСостав.Ссылка
|
|ИМЕЮЩИЕ
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ КомплектыСостав.Номенклатура) = &КолСтрок
| И КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВЫБОР
| КОГДА (КомплектыСостав.Номенклатура, КомплектыСостав.Количество) В
| (ВЫБРАТЬ
| Т.Номенклатура,
| Т.Количество
| ИЗ
| ВТ_ТекущийСостав КАК Т)
| ТОГДА КомплектыСостав.Номенклатура
| ИНАЧЕ NULL
| КОНЕЦ) = &КолСтрок";
Запрос.УстановитьПараметр("ТЧ", ТаблицаСостава);
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Запрос.УстановитьПараметр("КолСтрок", КоличествоСтрок);
Результат = Запрос.Выполнить();
Возврат Результат.Пустой();
КонецФункции
Разберем, что делает этот запрос. Мы помещаем текущий состав во временную таблицу. Затем выбираем все существующие комплекты (кроме текущего) и фильтруем их по двум условиям в секции ИМЕЮЩИЕ:
&КолСтрок).&КолСтрок.Если запрос вернул хотя бы одну ссылку, значит, найден полный дубль.
Если справочник Комплекты содержит десятки тысяч записей, выполнение запроса с соединением табличных частей при каждой записи может стать накладным. В таких случаях профессионалы используют метод контрольных сумм (хеширования).
Рассмотрим алгоритм реализации этого метода:
Комплекты скрытый реквизит ХешСостава (тип: Строка, длина 32 или более).ГДЕ ХешСостава = &ТекущийХеш И Ссылка <> &Ссылка.Посмотрим на пример формирования строки для хеша:
ТаблицаСравнения = ЭлементыКомплекта.Выгрузить();
ТаблицаСравнения.Сортировать("Номенклатура Возр");
СтрокаДляХеша = "";
Для Каждого Стр Из ТаблицаСравнения Цикл
СтрокаДляХеша = СтрокаДляХеша + "_" + Стр.Номенклатура.УникальныйИдентификатор() + ":" + Стр.Количество;
КонецЦикла;
// Далее получаем хеш от СтрокаДляХеша и записываем в реквизит
Этот метод гарантирует мгновенный поиск дублей даже при огромных объемах данных, так как поиск идет по индексированному строковому полю.
Проанализируем ситуацию: два бухгалтера одновременно создают одинаковый комплект. Оба нажимают кнопку «Записать» одновременно. В этот момент запрос на проверку уникальности у обоих покажет, что такого комплекта в базе еще нет. В итоге в базе появятся дубли.
Чтобы этого избежать, необходимо использовать блокировку данных. Перед выполнением запроса на проверку уникальности следует установить исключительную блокировку на справочник Комплекты. Это заставит вторую транзакцию подождать, пока первая не завершит запись, и тогда проверка во второй транзакции честно найдет созданный первым пользователем дубль.
Выясним, как это реализовать:
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Справочник.Комплекты");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
// Теперь спокойно выполняем запрос проверки уникальности
Подводя итог, отметим, что качественная проверка уникальности состава требует комплексного подхода: отказа от медленных циклов в пользу запросов, корректной обработки системных полей (пометка удаления, обмен данными) и обеспечения безопасности в многопользовательской среде.