Комментарии 29
Должно быть ещё что-то. Дата рождения, номер паспорта, номер полиса, адрес. Возможно, есть смысл сначала искать прямой match по этим параметрам, а затем уже перебирать имена.
Плюс учитываем то, что женщины склонны менять фамилию после замужества. Это один и тот же пациент, но с другой фамилией начиная с определённой даты. А иногда такое и несколько раз происходит.
Это действительно упрощенная модель в статье. В реальности есть и дата рождения, и енп, снилс, паспорта, документы. Есть история изменений и архивный поиск по девичьей фамилии. Это я не тащил в статью, чтобы не загромождать запросы — история была именно про опечатки
И один и тот же человек может писать своё имя по-разному в зависимости от настроения (Наталия, Наталья).
Если у кого-то есть идеи, как можно оптимизировать функцию на Python по скорости, то отпишитесь в комментариях, я добавлю данные в тесты.
import re
from functools import reduce
# Правила замены гласных
VOWELS = {"(?:йо|ио|йе|ие)": "и", "[оыя]": "а", "[еёэ]": "и", "[ю]": "у"}
# Правила оглушения согласных
CONSONANTS = {"б": "п", "з": "с", "д": "т", "в": "ф"}
# Согласная должна быть оглушена, если за ней следует один из символов списка
DEAFENING_CHARS = "бвгджзй"
# Удаляемые символы
REMOVABLE_CHARS = {ord("ъ"): None, ord("ь"): None}
def _canonicalize(s):
return s.lower().strip()
def _remove_double_chars(s):
return "".join(c for n, c in enumerate(s) if n == 0 or c != s[n - 1])
def _deafen_consonants(s):
out = []
for n, char in enumerate(s):
if char in CONSONANTS and (
n < len(s) - 1 and s[n + 1] in DEAFENING_CHARS
or n == len(s) - 1
):
out.append(CONSONANTS[char])
else:
out.append(char)
return "".join(out)
def _replace_vowels(s):
for pattern, replacement in VOWELS.items():
s = re.sub(pattern, replacement, s)
return s
def _remove_chars(s):
return s.translate(REMOVABLE_CHARS)
return reduce(lambda x, modifier: modifier(x), (
_canonicalize,
_remove_chars,
_replace_vowels,
_deafen_consonants,
_remove_double_chars,
), in_lexeme)
# Если бы в body была строка "тест", то после преобразований получилось бы "ест"
"".join((char for num, char in enumerate(self.body) if char != self.body[num - 1]))
Первый символ сравнивался с последним и если они совпадали, то он пропускался — возможно это повлияло на изменение размера индекса.
Логика работы была в том, что если мы не нашли человека в системе, то его нужно создать (а дубли пациентов плодить нельзя).
Результаты анализа запросов:
Триграммы и GIST (7 rows)
Триграммы и GIN (15 rows)
Триграммы и полнотекстовый поиск (37 rows)
Metaphone и btree (12 rows)
Metaphone и триграммы (11 rows)
Мне кажется, что автор увлекся оптимизацией скорости запроса и потерял в качестве. Проверялись ли результаты без указания LIMIT?
Автор не потерял в качестве, информация из первых рук. Во всех выборках возвращалось менее 10 результатов при лимите в 10.
Кстати, то количество строк для разных выборок, которе вы написали, не имеет отношения к результатам. Это количество строчек в выводе плана explain (analyze, buffers) — можете сами посчитать))
Вариант транслитерации ФИО не рассматривался?
Рассматривался, пока не увидел алгоритм русского Метафона. Я его посмотрел и он показался мне вполне логичным в плане нивелирования ошибок, плюс его тестировали в бою. А транслитерация и последующая обработка фонетическими алгоритмами показалась мне чересчур сложной и потенциально дающей больше ошибок. Но я не тестировал.
Ищем имена с опечатками в PostgreSQL