我有一个Oracle数据库,与许多人一样,有一个包含传记信息的表。在其中,我想以“自然”的方式按名称搜索。
该表格包含forename
和surname
字段,目前我正在使用以下内容:
select id, forename, surname
from mytable
where upper(forename) like '%JOHN%'
and upper(surname) like '%SMITH%';
这可行,但它可能非常慢,因为此表上的索引显然无法解释前面的通配符。此外,用户通常会根据他们通过电话告诉他们的内容来搜索他们 - 包括大量的非英文姓名 - 所以最好也进行一些语音分析。
因此,我一直在试验Oracle Text:
create index forenameFTX on mytable(forename) indextype is ctxsys.context;
create index surnameFTX on mytable(surname) indextype is ctxsys.context;
select score(1)+score(2) relevance,
id,
forename,
surname
from mytable
where contains(forename,'!%john%',1) > 0
and contains(surname,'!%smith%',2) > 0
order by relevance desc;
这具有使用Soundex算法以及全文索引的优点,因此它应该更高效。 (虽然,我的轶事结果显示它很慢!)我对此的唯一担忧是:
首先,需要以一些有意义的方式刷新文本索引。使用on commit
会太慢并且可能会干扰前端软件 - 这是我无法控制的 - 如何与数据库交互;所以需要考虑一下......
Oracle返回的结果并不是非常自然地排序;我不太确定这个score
函数。例如,我的开发数据显示“乔纳森彼得杰森史密斯”在顶部 - 罚款 - 但“简玛格丽特辛普森”与“约翰特伦斯史密斯”处于同一水平
我认为删除前面的通配符可能会提高性能而不会降低结果,因为在现实生活中,您永远不会在名称中间搜索块。但是,否则,我对这些想法持开放态度......这种情况必须在恶心的情况下实施!任何人都可以提出一个更好的方法来解决我现在正在做/正在考虑的问题吗?
谢谢:)
答案 0 :(得分:5)
根据评论中的建议,我提出了一个效果很好的解决方案。特别是,@ X-Zero建议创建一个Soundexes表:在我的例子中,我可以创建新表,但是不允许改变现有的模式!
所以,我的流程如下:
创建一个包含以下列的新表:ID
,token
,sound
和position
;使用主键(ID
,sound
,position
)以及(ID
,sound
)上的其他索引。
浏览传记表中的每个人:
连接他们的姓氏和姓氏。
将代码页更改为us7ascii
,因此重音字符会被标准化。这是因为Soundex算法不适用于重音字符。
将所有非字母字符转换为空格,并将其视为标记之间的边界。
对此字符串进行标记并将令牌(小写),令牌的Soundex以及令牌在原始字符串中的位置插入表中。将此与ID
相关联。
像这样:
declare
nameString varchar2(82);
token varchar2(40);
posn integer;
cursor myNames is
select id,
forename||' '||surname person_name
from mypeople;
begin
for person in myNames
loop
nameString := trim(
utl_i18n.escape_reference(
regexp_replace(
regexp_replace(person.person_name,'[^[:alpha:]]',' '),
'\s+',' '),
'us7ascii')
)||' ';
posn := 1;
while nameString is not null
loop
token := substr(nameString,1,instr(nameString,' ') - 1);
insert into personsearch values (person.id,lower(token),soundex(token),posn);
nameString := substr(nameString,instr(nameString,' ') + 1);
posn := posn + 1;
end loop;
end loop;
end;
/
因此,例如,“SiânO'Conner”被标记为“sian”(位置1),“o”(位置2)和“conner”(位置3)以及这三个条目,以及他们的Soundex,获得插入personsearch
及其ID。
ld
)从每个令牌的原始搜索中进行排序。例如,此查询将搜索两个令牌(即预先标记的搜索字符串):
with searchcriteria as (
select 'john' token1,
'smith' token2
from dual)
select alpha.id,
mypeople.forename||' '||mypeople.surname
from peoplesearch alpha
join mypeople
on mypeople.student_id = alpha.student_id
join peoplesearch beta
on beta.student_id = alpha.student_id
and beta.position > alpha.position
join searchcriteria
on 1 = 1
where alpha.sound = soundex(searchcriteria.token1)
and beta.sound = soundex(searchcriteria.token2)
order by alpha.position,
ld(alpha.token,searchcriteria.token1),
beta.position,
ld(beta.token,searchcriteria.token2),
alpha.student_id;
要搜索任意数量的令牌,我们需要使用动态SQL:连接搜索表的次数与令牌一样多,其中连接表中的position
字段必须大于{以前加入的表的{1}} ...我打算编写一个函数来执行此操作 - 以及搜索字符串标记化 - 它将返回一个ID表。但是,我只是在这里发布,所以你得到了这个想法:)
正如我所说,这非常有效:它很快就能恢复良好的效果。甚至搜索“John Smith”,一旦被服务器缓存,运行时间不到0.2秒;返回超过200行...我对它很满意,并希望将其投入生产。唯一的问题是:
令牌的预先计算需要一段时间,但这是一次性过程,因此不是太大的问题。然而,相关的问题是,只要在position
上执行相应的操作,就需要在mypeople
表上放置触发器以将令牌插入/更新/删除到搜索表中。这可能会减慢系统的速度;但由于这应该只发生在一年中的几个时期,或许更好的解决方案是按计划重建搜索表。
没有进行任何干扰,因此Soundex算法仅匹配完整的令牌。例如,搜索“chris”不会返回任何“christopher”。一个可能的解决方案是只存储令牌的词干的Soundex,但计算词干不是一个简单的问题!这将是未来的升级,可能使用TeX使用的连字引擎...
无论如何,希望有所帮助:)欢迎评论!
编辑我的完整解决方案(编写和实施)现在here,使用Metaphone和Damerau-Levenshtein距离。