12

我有一个 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功能。例如,我的开发数据显示“Jonathan Peter Jason Smith”在顶部——很好——但“Jane Margaret Simpson”与“John Terrance Smith”处于同一水平

我认为删除前面的通配符可能会在不降低结果的情况下提高性能,因为在现实生活中,您永远不会在名称中间搜索块。但是,否则,我对想法持开放态度……这种情况一定是实施得令人作呕!任何人都可以对我现在正在做/考虑的事情提出更好的方法吗?

谢谢 :)

4

1 回答 1

5

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

所以,我的流程如下:

  • 创建一个包含列的新表:IDtokensound; 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ân O'Conner”被标记为“sian”(位置 1)、“o”(位置 2)和“conner”(位置 3),这三个条目连同它们的 Soundexpersonsearch一起被插入用他们的身份证。

  • 要搜索,我们执行相同的过程:标记搜索条件,然后返回 Soundexe 和相对位置匹配的结果。我们依次按位置排序,然后是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;

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

正如我所说,这很好用:它很快就会返回好的结果。即使搜索“John Smith”,一旦被服务器缓存,运行时间也不到 0.2s;返回超过 200 行...我对它非常满意,并将寻求将其投入生产。唯一的问题是:

  • 代币的预先计算需要一段时间,但这是一个一次性的过程,所以问题不大。然而,一个相关的问题是,mypeople每当对mypeople. 这可能会减慢系统速度;但由于这应该只发生在一年中的几个时期,也许更好的解决方案是按计划重建搜索表。

  • 没有进行词干提取,因此 Soundex 算法仅匹配完整的标记。例如,搜索“chris”不会返回任何“christopher”。一个可能的解决方案是只存储令牌的词干的 Soundex,但计算词干不是一个简单的问题!这将是未来的升级,可能使用 TeX 使用的连字符引擎......

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


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

于 2011-10-04T11:54:56.030 回答