Сталкивались ли вы с кейсом, когда по заданному списку email или номеров телефонов необходимо проверить всех клиентов на совпадение с ними? Поиск через интерфейс в различных системах не всегда удобен или даже невозможен. В этой статье я расскажу, как организовать универсальное хранилище объектов различного типа под разные цели. Мы создадим единый справочник в базе данных и организуем поиск по нему. Примеры кода будут приведены на языке Oracle PL/SQL, но их легко можно адаптировать под другие СУБД.

Основная таблица для хранения объектов содержит следующие поля:

create table OBJ_LST
(
  iid   NUMBER not null,
  cacc  VARCHAR2(2000),
  ctype VARCHAR2(10),
  ddate DATE default trunc(Sysdate)
);
  • объект хранения — строка,

  • тип объекта — это бизнес‑тип, определенный в отдельном справочнике типов (например, ИНН, email или номер банковской карты),

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

В справочнике типов заводятся бизнес‑типы данных с указанием их id, описания и регулярного выражения.

Рис. 1.Справочник бизнес-типов объектов
Рис. 1.Справочник бизнес‑типов объектов

Объекты удобно группировать, чтобы работать с их подмножествами. Для этого есть справочник групп (или видов). В данном примере заведены группы для организации различных проверок в банке:

Рис. 2. Группы объектов
Рис. 2. Группы объектов

А сами группировки хранятся в отдельной таблице.

Для работы с данным хранилищем необходимо решить две задачи — работу с данными и организацию поиска.

При работе с данными помимо функций добавления, редактирования и удаление записей будем автоматизировать их группировку, а также массовое заведение. С первыми операциями проблем быть не должно, там будут простые CRUD‑операции. А вот массовое заведение по списку значений, заданных одной строкой, представляет бОльший интерес. Для этого используем процедуру, на вход которой подаем строку со списком объектов, тип объекта и группу (необязательный параметр) для автоматической группировки:

parse_str(pMess => cMess, pType => 'EMAIL', pStr => 'invanov@gmail.ru; petrov@mail.ru; sidorov@inbox.ru', pCtrl => 4)

Результатом выполнения будет заполнение хранилища объектами заданного типа.

Исходный код процедуры:

PROCEDURE parse_str(pMess OUT VARCHAR2, pType IN VARCHAR2, pStr IN CLOB, pCtrl IN NUMBER)
IS
    cPatt VARCHAR2(254);
    vStr CLOB := pStr;
    CURSOR curTypes IS
        SELECT CREGEXP FROM obj_types
            WHERE CTYPE = pType;
BEGIN
    OPEN curTypes;
    FETCH curTypes INTO cPatt;
    CLOSE curTypes;
    IF cPatt IS NULL THEN
        pMess := 'Не указано регулярное выражение для типа данных '||pType; RETURN;
    END IF;
    dbms_output.put_line('Строка для анализа: '||vStr);
    FOR rOBJ IN (SELECT SUBSTR(regexp_substr(obj, patt, 1, rownum),2) rez_obj
                    FROM (SELECT vStr AS obj
                    , cPatt AS patt FROM dual) dual
                    CONNECT BY level <= regexp_count(obj, patt)
                )
    LOOP
        IF rOBJ.rez_obj IS NOT NULL THEN
            begin
                ins_obj(rOBJ.rez_obj,pType,pCtrl);
            EXCEPTION WHEN DUP_VAL_ON_INDEX THEN
            END;
        END IF;
    END LOOP;

    pMess := 'Разбор успешно завершен.';
EXCEPTION WHEN OTHERS THEN
  pMess := 'Непредвиденная ошибка обработки: '||SQLERRM;
END parse_str;

Для организации поиска объекта по хранилищу создадим функцию, на вход которой подаем строку/объект для анализа, тип объекта (если пусто — то ищем по всем), номер группы (если пусто — то по всем) и дату загрузки данных (если пусто, то по всем):

check_str('Назначение платежа с указание номера банковской карты № 4976980039148296 договор ДГ45', 'CARD', 4, to_date('01.07.2026','dd.mm.rrrr'))

Если функция нашла совпадение исходной строки с объектом в базе, то она вернет этот самый объект.

Исходный код функции:

FUNCTION check_str(pStr IN VARCHAR2
                 , pType IN VARCHAR2 DEFAULT 'ALL'
                 , pCtrl IN NUMBER DEFAULT NULL
                 , pDate IN DATE DEFAULT NULL                
                 ) RETURN VARCHAR2
IS
BEGIN
    FOR rOBJ IN (SELECT CACC 
                    FROM obj_lst a
                    JOIN obj_grp_ctrl c ON c.idata_id = a.iid AND c.ictrl_id = nvl(pCtrl,c.ictrl_id)
                 WHERE CTYPE = DECODE(pType ,'ALL',CTYPE, pType)
                 AND DDATE >= nvl(pDate,to_date('01.01.1900','dd.mm.rrrr')))
    LOOP
        IF UPPER(pStr) LIKE '%'||UPPER(rOBJ.CACC)||'%' THEN
            RETURN rOBJ.CACC;
        END IF;
    END LOOP;
    RETURN NULL;
END check_str;

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