查询名称数据库的建议

时间:2011-09-30 15:41:49

标签: sql oracle full-text-search soundex oracle-text

我有一个Oracle数据库,与许多人一样,有一个包含传记信息的表。在其中,我想以“自然”的方式按名称搜索。

该表格包含forenamesurname字段,目前我正在使用以下内容:

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函数。例如,我的开发数据显示“乔纳森彼得杰森史密斯”在顶部 - 罚款 - 但“简玛格丽特辛普森”与“约翰特伦斯史密斯”处于同一水平

我认为删除前面的通配符可能会提高性能而不会降低结果,因为在现实生活中,您永远不会在名称中间搜索块。但是,否则,我对这些想法持开放态度......这种情况必须在恶心的情况下实施!任何人都可以提出一个更好的方法来解决我现在正在做/正在考虑的问题吗?

谢谢:)

1 个答案:

答案 0 :(得分:5)

根据评论中的建议,我提出了一个效果很好的解决方案。特别是,@ X-Zero建议创建一个Soundexes表:在我的例子中,我可以创建新表,但是不允许改变现有的模式!

所以,我的流程如下:

  • 创建一个包含以下列的新表:IDtokensoundposition;使用主键(IDsoundposition)以及(IDsound)上的其他索引。

  • 浏览传记表中的每个人:

    • 连接他们的姓氏和姓氏。

    • 将代码页更改为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。

  • 要进行搜索,我们会执行相同的过程:标记搜索条件,然后返回Soundexes和相对位置匹配的结果。我们依次通过位置和Levenshtein距离(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距离。