Oracle 带通配符的模糊文本搜索
Oracle fuzzy text search with wildcards
我有一个充满客户数据的 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
除非在搜索字符串中使用通配符 *
(这很常见),否则此查询工作正常。
假设 "wildcard" 表示一个星号,您希望匹配所有指定字母的名称排名最高,指定字母越多匹配越好,否则按编辑距离相似度排名。
使用占位符 ?
作为搜索词,试试这个:
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
仅供参考,所有 "like" 匹配项的排序都是负数,大小为指定的字母数。所有类似的未命中都有一个 non-negative 的顺序号,其中包含百分比差异的大小。在所有情况下,数字越小越好。
注意您的方法对性能的影响。即使 "functionally" 有效,使用 UTL_MATCH
您也只能 过滤 内部 table 扫描获得的结果。
您可能需要的是此类数据的 index。
前往 Oracle Text,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;
此查询会 return 您可能会喜欢的结果(输入是字符串 "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" 它可能 return 什么都没有(在我的例子中它就是这样做的)。
另请注意,一般情况下,少于 3 个字符的输入默认被视为 non-matching。
如果取消 "fuzzy" 要求并坚持查找正好 "contains" 您传入的确切顺序的行,您可能会得到更多 "predictable" 结果。
在这种情况下尝试使用 ctxcat
索引,顺便说一句,它支持一些通配符(警告:支持多列,但一列的大小不能超过 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*”不会return任何东西(出于某些原因)。
底线:文本索引可能是唯一的方法(为了提高性能),但您必须 fiddle 相当多才能理解所有的复杂性。
参考文献:
我有一个充满客户数据的 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
除非在搜索字符串中使用通配符 *
(这很常见),否则此查询工作正常。
假设 "wildcard" 表示一个星号,您希望匹配所有指定字母的名称排名最高,指定字母越多匹配越好,否则按编辑距离相似度排名。
使用占位符 ?
作为搜索词,试试这个:
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
仅供参考,所有 "like" 匹配项的排序都是负数,大小为指定的字母数。所有类似的未命中都有一个 non-negative 的顺序号,其中包含百分比差异的大小。在所有情况下,数字越小越好。
注意您的方法对性能的影响。即使 "functionally" 有效,使用 UTL_MATCH
您也只能 过滤 内部 table 扫描获得的结果。
您可能需要的是此类数据的 index。
前往 Oracle Text,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;
此查询会 return 您可能会喜欢的结果(输入是字符串 "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" 它可能 return 什么都没有(在我的例子中它就是这样做的)。
另请注意,一般情况下,少于 3 个字符的输入默认被视为 non-matching。
如果取消 "fuzzy" 要求并坚持查找正好 "contains" 您传入的确切顺序的行,您可能会得到更多 "predictable" 结果。
在这种情况下尝试使用 ctxcat
索引,顺便说一句,它支持一些通配符(警告:支持多列,但一列的大小不能超过 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*”不会return任何东西(出于某些原因)。
底线:文本索引可能是唯一的方法(为了提高性能),但您必须 fiddle 相当多才能理解所有的复杂性。
参考文献: