Поиск нечеткого текста 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* " ничего не вернет (по некоторым причинам).
итог: текстовые индексы, вероятно, единственный способ пойти (для производительности), но вам нужно немного поиграть, чтобы понять все тонкости.
ссылки:
- нечеткий поиск: текст Oracle содержит операторы запросов
- catsearch : Oracle Text SQL операторы и операторы
предполагая, что "подстановочный знак" означает звездочку, вы хотите, чтобы имя, которое соответствует всем указанным буквам, ранжировалось выше, с более указанными буквами, соответствующими лучше, чем меньше, в противном случае ранжируйте по сходству расстояния редактирования.
использование заполнителя ?
для вашего поискового запроса попробуйте следующее:
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 " совпадения имеют отрицательное число для их упорядочения с величиной количества указанных букв. Все подобные промахи имеют неотрицательный порядковый номер с величина процентной разницы. Во всех случаях меньшее число лучше подходит.