Предложения для запроса базы данных для имен - PullRequest
12 голосов
/ 30 сентября 2011

У меня есть база данных 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.Например, мои данные по развитию показывают «Джонатана Питера Джейсона Смита» наверху - отлично - но также «Джейн Маргарет Симпсон» на том же уровне, что и «Джон Терранс Смит»

Я думаю, что удаление предыдущего подстановочного знака может улучшить производительность без ухудшения результатов, так как в реальной жизни вы никогда не будете искать фрагмент в середине имени.Однако в остальном я открыт для идей ... Этот сценарий должен был быть реализован до тошноты!Кто-нибудь может предложить лучший подход к тому, что я делаю / рассматриваю сейчас?

Спасибо:)

1 Ответ

5 голосов
/ 04 октября 2011

Я пришел к решению, которое работает довольно хорошо, следуя предложениям в комментариях.В частности, предложение @ 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ân O'Conner" получает токеныв "sian" (позиция 1), "o" (позиция 2) и "conner" (позиция 3), и эти три записи с их Soundex вставляются в personsearch вместе с их идентификатором.

  • Для поиска мы выполняем тот же процесс: разбиваем критерии поиска и затем возвращаем результаты, где совпадают Soundexes и относительно позиций.Мы упорядочиваем по позиции, а затем по очереди расстояние Левенштейна (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 ранее объединенной таблицы ... Я планирую написать для этого функцию, а также токенизацию строки поиска, которая будет возвращать таблицу идентификаторов.Тем не менее, я просто опубликую это здесь, чтобы вы поняли:)

Как я уже сказал, это работает довольно хорошо: довольно быстро возвращает хорошие результаты.Даже поиск «Джона Смита», когда-то кэшированного сервером, выполняется менее чем за 0,2 с;возвращая более 200 строк ... Я очень доволен этим и буду стремиться запустить его в производство.Единственные проблемы:

  • Предварительный расчет токенов занимает некоторое время, но это одноразовый процесс, поэтому проблема не слишком большая.Однако связанная с этим проблема заключается в том, что в таблицу mypeople должен быть помещен триггер для вставки / обновления / удаления токенов в таблицу поиска всякий раз, когда соответствующая операция выполняется для mypeople.Это может замедлить работу системы;но так как это должно происходить только в течение нескольких периодов в году, возможно, лучшим решением было бы перестроить таблицу поиска на плановой основе.

  • Не производится обработка, поэтомуАлгоритм Soundex совпадает только с полными токенами.Например, поиск «chris» не вернет никаких «christopher».Возможное решение этой проблемы - хранить только Soundex основы токена, но вычисление основы не является простой проблемой!Это будет обновление в будущем, возможно, с использованием механизма переноса, используемого TeX ...

В любом случае, надеюсь, это поможет :) Комментарии приветствуются!


РЕДАКТИРОВАТЬ Мое полное решение (написать и реализовать) теперь здесь , с использованием Метафона и расстояния Дамерау-Левенштейна.

...