查询名称数据库的建议

Xop*_*ter 12 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%';
Run Code Online (Sandbox Code Playgroud)

这可行,但它可能非常慢,因为此表上的索引显然无法解释前面的通配符.此外,用户通常会根据他们通过电话告诉他们的内容来搜索人员 - 包括大量的非英文姓名 - 所以最好也进行一些语音分析.

因此,我一直在试验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;
Run Code Online (Sandbox Code Playgroud)

这具有使用Soundex算法以及全文索引的优点,因此它应该更有效.(虽然,我的轶事结果显示它很慢!)我对此的唯一担忧是:

  • 首先,需要以一些有意义的方式刷新文本索引.使用on commit速度太慢,可能会干扰前端软件(我无法控制)与数据库的交互方式; 所以需要一些思考......

  • Oracle返回的结果并不是非常自然地排序; 我不太确定这个score功能.例如,我的开发数据显示"乔纳森彼得杰森史密斯"在顶部 - 罚款 - 但"简玛格丽特辛普森"与"约翰特伦斯史密斯"处于同一水平

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

谢谢 :)

Xop*_*ter 5

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

所以,我的流程如下:

  • 与列创建一个新表:ID,token,soundposition; 超过主键(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;
/
Run Code Online (Sandbox Code Playgroud)

因此,例如,"仙奥康"被标记化到"仙"(位置1),"O"(位置2)和"康纳"(3位)以及那些三个条目,用他们的Soundex,得到插入personsearch沿他们的身份证.

  • 要进行搜索,我们会执行相同的过程:标记搜索条件,然后返回Soundexes和相对位置匹配的结果.我们按位置排序,然后依次ld从原始搜索中获得Levenshtein距离().

例如,此查询将搜索两个令牌(即,预标记化的搜索字符串):

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;
Run Code Online (Sandbox Code Playgroud)

要搜索任意数量的令牌,我们需要使用动态SQL:连接搜索表的次数与令牌一样多,其中position连接表中的字段必须大于position先前连接的表中的字段... I计划编写一个函数来执行此操作 - 以及搜索字符串标记化 - 它将返回一个ID表.但是,我只是在这里发布,所以你明白了:)

正如我所说,这非常有效:它很快就能恢复良好的效果.甚至搜索"John Smith",一旦被服务器缓存,运行时间不到0.2秒; 返回超过200行...我对它很满意,并希望将其投入生产.唯一的问题是:

  • 预先计算令牌需要一段时间,但这是一次性过程,所以不是太多问题.然而,相关的问题是mypeople每当执行相应的操作时,需要在表上放置触发器以将令牌插入/更新/删除到搜索表中mypeople.这可能会减慢系统的速度; 但由于这应该只发生在一年中的几个时期,或许更好的解决方案是按计划重建搜索表.

  • 没有阻塞,所以Soundex算法只匹配完整的令牌.例如,搜索"chris"不会返回任何"christopher".一个可能的解决方案是只存储令牌的词干的Soundex,但计算词干不是一个简单的问题!这将是未来的升级,可能使用TeX使用的连字引擎...

无论如何,希望有所帮助:)欢迎评论!


编辑我的完整解决方案(编写和实现)现在在这里,使用Metaphone和Damerau-Levenshtein距离.