【问题标题】:Suggestions for Querying Database for Names查询数据库名称的建议
【发布时间】:2011-11-28 14:16:40
【问题描述】:

我有一个 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”处于同一级别

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

谢谢:)

【问题讨论】:

  • 您还可以考虑将 Lucene 用于非 Oracle 模糊搜索引擎(并且它是免费的)。但取决于您的环境和需求(和技能)
  • 与其说是答案,不如说是建议。我使用 MySQL 和全文搜索来搜索帐户详细信息 - 基于名字和姓氏。我还遇到了基于分数的不相关结果,直到我创建了一个新列“full_name”,其中包含名字和姓氏一起(反规范化),然后改为搜索该列 - 我的结果更精确,返回速度更快。我还使用了布尔模式,所以它匹配两个名字“john”和“smith”。如果我没有使用布尔模式,它会返回“john”或“smith”的结果,这是错误的
  • 您可以更改数据库架构吗?
  • @MartinG 是的,我正在考虑连接这些字段。我不知道是否可以在不更改架构的情况下做到这一点(即,使用 CONTAINS 函数中的两列)...
  • ...@X-Zero,对于表格列,我做不到;但我认为添加索引没有问题。

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


【解决方案1】:

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

所以,我的流程如下:

  • 创建一个包含列的新表:IDtokensoundposition;主键位于 (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),这三个条目及其 Soundex 得到连同他们的 ID 一起插入到personsearch

  • 要进行搜索,我们执行相同的过程:标记搜索条件,然后返回 Soundexe 和 relative 位置匹配的结果。我们依次按位置排序,然后从原始搜索中的 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 字段必须大于 position之前加入的表...我计划编写一个函数来执行此操作 - 以及搜索字符串标记化 - 这将返回一个 ID 表。但是,我只是在这里发布此内容,以便您了解:)

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

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

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

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


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

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2014-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多