При контроле уникальности клиентов часто возникает задача запретить сохранение партнера, если указанный им номер телефона или адрес электронной почты уже зарегистрирован в базе данных. Однако поиск совпадений простым запросом по табличной части контактной информации нередко приводит к заметным задержкам даже на относительно небольших базах в несколько тысяч элементов.
Разберем, почему исходный запрос выполняется долго, какие ограничения СУБД и платформы 1С на это влияют, и рассмотрим по шагам способы оптимизации этой проверки.
Посмотрим на типовой запрос, который разработчики часто используют для поиска дубликатов:
ВЫБРАТЬ
ПартнерыКонтактнаяИнформация.Тип,
ПартнерыКонтактнаяИнформация.Представление
ИЗ
Справочник.Партнеры.КонтактнаяИнформация КАК ПартнерыКонтактнаяИнформация
ГДЕ
ПартнерыКонтактнаяИнформация.Тип = &Тип
И ПартнерыКонтактнаяИнформация.Представление = &Представление
Выясним причину, почему такой запрос работает медленно даже на объеме в 3 000–4 000 партнеров:
Представление в контактной информации обычно имеет длину Строка(500) или является строкой неограниченной длины. По умолчанию такие поля не индексируются СУБД. В результате при каждом вызове СУБД вынуждена выполнять полное сканирование всей таблицы (Table Scan), сравнивая длинные строки побайтово.Представление содержит произвольно отформатированный текст (например, +7 (999) 000-11-22, 89990001122 или 8-999-000-11-22). Прямое сравнение на равенство часто пропускает реальные дубликаты из-за разницы в символах форматирования.Рассмотрим, как правильно составить запрос проверки, если архитектура хранения пока остается прежней. Для минимизации нагрузки на СУБД необходимо ограничить выборку одной записью и исключить текущую ссылку:
ВЫБРАТЬ ПЕРВЫЕ 1
1 КАК ЕстьДубль
ИЗ
Справочник.Партнеры.КонтактнаяИнформация КАК ПартнерыКИ
ГДЕ
ПартнерыКИ.Ссылка <> &ТекущаяСсылка
И ПартнерыКИ.Тип = &Тип
И ПартнерыКИ.Представление = &Представление
Конструкция ВЫБРАТЬ ПЕРВЫЕ 1 позволяет СУБД прервать чтение таблицы сразу же после обнаружения первого совпадения, не дожидаясь полного прохода по всем строкам.
Важный момент: если в конфигурации включен механизм RLS (ограничение доступа на уровне записей), проверка перед записью может замедляться неявными проверками прав. В таком случае выполнение служебной проверки следует обернуть в привилегированный режим:
УстановитьПривилегированныйРежим(Истина);
Результат = Запрос.Выполнить();
УстановитьПривилегированныйРежим(Ложь);
Если Не Результат.Пустой() Тогда
// Обработка обнаружения дубля
КонецЕсли;
В типовых конфигурациях 1С, использующих Библиотеку стандартных подсистем (БСП), контактная информация содержит не только визуальное представление, но и нормализованные поля: НомерТелефона (очищенный от скобок и дефисов) и АдресЭлектроннойПочты.
Разберем преимущества этого подхода:
НомерТелефона в метаданных можно установить свойство Индексировать (или Индексировать с доп. упорядочиванием).Посмотрим на пример запроса по индексированному полю номера телефона:
ВЫБРАТЬ ПЕРВЫЕ 1
1
ИЗ
Справочник.Партнеры.КонтактнаяИнформация КАК ПартнерыКИ
ГДЕ
ПартнерыКИ.Ссылка <> &ТекущаяСсылка
И ПартнерыКИ.НомерТелефона = &ОчищенныйНомерТелефона
Перед передачей параметра в запрос телефонный номер необходимо предварительно очистить от любых символов, кроме цифр, чтобы обеспечить точное соответствие формату хранения.
Если в конфигурации контактная информация хранится в табличной части справочника, то при росте базы обращение к ней создает дополнительную нагрузку на таблицы самого объекта. Наиболее масштабируемое решение — использовать регистр сведений.
Разберем архитектуру такого решения:
ИспользованныеКонтакты).ТипКонтакта (ПеречислениеСсылка.ТипыКонтактнойИнформации);НормализованноеЗначение (Строка ограниченной длины, например 100 символов, с установленным свойством индексации);Партнер (СправочникСсылка.Партнеры).Посмотрим на пример проверки через срез или прямой отбор регистра:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| РегистрКонтактов.Партнер
|ИЗ
| РегистрСведений.ИспользованныеКонтакты КАК РегистрКонтактов
|ГДЕ
| РегистрКонтактов.ТипКонтакта = &ТипКонтакта
| И РегистрКонтактов.НормализованноеЗначение = &НормализованноеЗначение
| И РегистрКонтактов.Партнер <> &ТекущийПартнер";
Запрос.УстановитьПараметр("ТипКонтакта", ТипКонтакта);
Запрос.УстановитьПараметр("НормализованноеЗначение", ОчищенныйКонтакт);
Запрос.УстановитьПараметр("ТекущийПартнер", Ссылка);
Если Не Запрос.Выполнить().Пустой() Тогда
ВызватьИсключение "Партнер с такими контактными данными уже существует!";
КонецЕсли;
Такой подход гарантирует мгновенную проверку даже при миллионах записей, так как поиск выполняется строго по кластерному индексу измерений регистра сведений.