Поиск нечеткого текста Oracle с помощью подстановочных знаков

у меня есть база данных SAP Oracle, полная данных о клиентах. В нашей пользовательской CRM довольно часто выполняется поиск клиентов с помощью подстановочных знаков. В дополнение к стандартному поиску SAP мы хотели бы выполнить нечеткий текстовый поиск имен, похожих на Введенное имя. В настоящее время мы используем UTL_MATCH.EDIT_DISTANCE функция для поиска похожих имен. Единственным недостатком является то, что невозможно использовать некоторые шаблоны подстановочных знаков.

есть ли возможность использовать маски в сочетании с UTL_MATCH.EDIT_DISTANCE функция или существуют разные (или даже лучшие) подходы к этому?

допустим, в базе данных есть следующие имена:

PATRICK NOR
ORVILLE ALEX
OWEN TRISTAN
OKEN TRIST

запрос может выглядеть как OKEN*IST* и как OWEN TRISTAN и OKEN TRISTAN должна быть возвращена. OKEN будет 100% матч и OWEN меньше.

мой текущий тестовый запрос выглядит так:

SELECT gp.partner, gp.bu_sort1, UTL_MATCH.edit_distance(gp.bu_sort1, ?) as edit_distance, 
      FROM but000 gp
      WHERE UTL_MATCH.edit_distance(gp.bu_sort1, ?) < 4

этот запрос отлично работает, за исключением подстановочных знаков * используются в строку поиска (что довольно распространено).

2 ответов


остерегайтесь последствий вашего подхода с точки зрения производительности. Даже если он "функционально" работал, с UTL_MATCH вы можете только фильтр результаты сканирования внутренних таблиц.
То, что вам, вероятно, нужно, это индекс по таким данным.
Глава к тексту Oracle, возможности индексирования текста Oracle. Имейте в виду, что они требуют определенных усилий для того, чтобы быть приведены в действие.

вы можете жонглировать с fuzzy оператор, но ручка с уход. Большинство текстовых функций oracle зависят от языка (они учитывают английский словарь, немецкий и т. д..).

например

-- create and populate the table
create table xxx_names (name varchar2(100));

insert into xxx_names(name) values('PATRICK NOR');
insert into xxx_names(name) values('ORVILLE ALEX');
insert into xxx_names(name) values('OWEN TRISTAN');
insert into xxx_names(name) values('OKEN TRIST');
insert into xxx_names(name) values('OKENOR SAD');
insert into xxx_names(name) values('OKENEAR TRUST');

--create the domain index
create index xxx_names_ctx on xxx_names(name) indextype is ctxsys.context;

этот запрос вернет результаты, которые вам, вероятно, понравятся (ввод-строка "TRST")

select
  SCORE(1), name
from
  xxx_names n
where
  CONTAINS(n.name, 'definescore(fuzzy(TRST, 1, 6, weight),relevance)', 1) > 0
; 



  SCORE(1) NAME               
---------- --------------------
         1 OWEN TRISTAN        
        22 OKEN TRIST    

но с входной строкой " IST " он, вероятно, ничего не вернет (в моем случае это то, что он делает). Также обратите внимание, что в целом входные данные менее 3 символов считаются несоответствующими по умолчанию.
Возможно, вы получите более "предсказуемый" результат, если снимете "нечеткое" требование и будете придерживаться поиска строк, которые просто "содержат" точную последовательность, которую вы передали.
В этом случае попробуйте использовать ctxcat index, который, кстати, поддерживает некоторые подстановочные знаки (предупреждение: поддерживает несколько столбцов, но столбец не может превышать 30 символов в размере!)

-- create and populate the table
--max length is 30 chars, otherwise the catsearch index can't be created
create table xxx_names (name varchar2(30));

insert into xxx_names(name) values('PATRICK NOR');
insert into xxx_names(name) values('ORVILLE ALEX');
insert into xxx_names(name) values('OWEN TRISTAN');
insert into xxx_names(name) values('OKEN TRIST');
insert into xxx_names(name) values('OKENOR SAD');
insert into xxx_names(name) values('OKENEAR TRUST');

begin

ctx_ddl.create_index_set('xxx_names_set');
ctx_ddl.add_index('xxx_names_set', 'name'); 

end;
/

drop index xxx_names_cat;
CREATE INDEX xxx_names_cat ON xxx_names(name) INDEXTYPE IS CTXSYS.CTXCAT
PARAMETERS ('index set xxx_names_set');

последний, с этим запросом будет работать хорошо (вход "*TRIST*")

select
  UTL_MATCH.edit_distance(name, 'TRIST') dist,
  name
from
  xxx_names
where
  catsearch(name, '*TRIST*', 'order by name desc') > 0
;

      DIST NAME               
---------- --------------------
         7 OWEN TRISTAN        
         5 OKEN TRIST      

но с ввод "* O * TRIST* " ничего не вернет (по некоторым причинам).

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

ссылки:


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

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

select *
from mytable
order by case
      when name like '%' || replace(?, '*', '%') || '%' then 0 - length(replace(?, '*', ''))
      else 100 - UTL_MATCH.edit_distance_similarity(?, name) end
fetch first 10 rows

FYI все" like " совпадения имеют отрицательное число для их упорядочения с величиной количества указанных букв. Все подобные промахи имеют неотрицательный порядковый номер с величина процентной разницы. Во всех случаях меньшее число лучше подходит.