
Реализация полнотекстового поиска в СУБД PostgreSQL является амбициозной задачей. Использование stem-словарей помогает сделать его быстрым, но не всегда точным. Рассмотрим, как эта проблема была решена в СУБД Jatoba от компании «Газинформсервис».
Введение
В современных приложениях, работающих с большими объёмами информации, одной из важнейших потребностей является возможность полнотекстового поиска, способного искать слова или словосочетания в больших массивах текста, с учётом морфологии, вариативности окончаний в словах и других особенностей, связанных с этим процессом.
В ходе реализации одного из проектов наша компания («Газинформсервис» — отечественный разработчик программных и программно-аппаратных средств обеспечения информационной безопасности) столкнулась с задачей, когда в рамках проекта интеграции с СУБД Jatoba потребовалось реализовать большое количество вариантов использования полнотекстового поиска. Хотя на базе ядра Jatoba (PostgreSQL) и можно решать большой пласт задач поиска по тексту с помощью функций tsvector и tsquery, некоторые требования заказчика выходили за рамки его возможностей.
Для решения данной проблемы в поставку Jatoba были добавлены словари Hunspell.
Проблема использования STEM-словарей
Одной из обозначенных задач являлся поиск по тексту с учётом морфологии слов. Базово в Jatoba присутствуют stem-словари (стеммеры) для большого перечня языков. Напомним, что эти словари используют алгоритм стемминга, который при работе со словами применяет ряд правил, отсекая окончания и суффиксы — это позволяет работать быстро. Однако подобный метод имеет ряд недостатков.
Во-первых, при приведении слов к нормальному виду у них обрезаются окончания — это может приводить к неточности. Рассмотрим следующий пример. В СУБД содержатся значения «забыть», «забить». При использовании команды tsvector по отношению к словарю stem происходит нормализация, в ходе которой PostgreSQL обрезает окончания и считает корнем обоих слов «заб». Таким образом, при поиске слова «забить» будет выдан абсолютно нерелевантный ответ, содержащий «забыть».
Во-вторых, stem-словари не распознают разные формы одного и того же слова, если у них слишком разные окончания. Из-за этого может быть нужно учитывать разные формы слов при выполнении запроса.
Например, в СУБД содержатся разные спряжения слова «забить»: «забью», «забьёшь», «забьёт», «забьём», «забьёте», «забьют». В результате того, что словарь не распознает эти слова как однокоренные, после нормализации по запросу «заб» будут выданы только слова «забью» и «забьём».
Примеры, описанные выше, демонстрируют, что стандартные словари PostgreSQL не отвечают требованиям полнотекстового поиска с морфологией: слова с разными корнями (ЗАБЫ-ть и за-БИ-ть) могут считаться однокоренными, а однокоренные слова — разными.
Применение словарей Hunspell
Словари семейства Hunspell были разработаны для проверки орфографии в языках со сложной системой словообразования. В них содержится информация о том, какие формы могут быть у слов с определёнными корнями и как их морфологически разобрать на части слова.
В отличие от словарей-стеммеров, словари Hunspell способны распознавать слово и его форму, а также приводить к начальной форме (инфинитив у глаголов, именительный падеж у существительных и другие). Благодаря этому свойству с их помощью можно выполнять полнотекстовый поиск с учётом морфологии языка.
Попробуем выполнить запросы из примеров выше, но используя при этом новые словари. Для этого необходимо создать соответствующее расширение. Словарь для каждого языка подключается как отдельный компонент Jatoba. Например, для русского языка создание расширения выглядит так:
postgres=# CREATE EXTENSION hunspell_ru_ru;
CREATE EXTENSION
После создания расширения нам доступен предсозданный словарь russian_hunspell, который будем использовать в дальнейшем.
Возьмём новую конфигурацию поиска russian_hunspell, чтобы выполнить запросы из примеров выше. В первую очередь проведём нормализацию:
postgres=# SELECT * FROM to_tsvector('russian_hunspell', 'забыть забить');to_tsvector
-----------------------
'забить':2 'забыть':1
Как можно увидеть, в этот раз словарь russian_hunspell правильно распознал слова «забыть» и «забить» как разные. Также примечательно, что полученные результаты поиска — это не «корни» слова, а полная форма слова.
Проверим следующий запрос:
postgres=# SELECT * FROM to_tsvector('russian_hunspell', 'забью забьешь забьет забьем забьете забьют');to_tsvector
----------------------
'забить':1,2,3,4,5,6
Здесь также видно: хоть по символам слова отличаются, в силу того, что морфологически это разные формы одного слова, russian_hunspell правильно привёл их к одной форме.
Так как заказчику требовалась возможность полнотекстового поиска с морфологией на разных языках, в поставку были добавлены словари для 10 языков:
- английский;
- болгарский;
- греческий;
- латышский;
- литовский;
- немецкий;
- польский;
- русский;
- финский;
- эстонский.
Помимо того, что было описано выше, Hunspell также позволяет значительно упростить поддержку нескольких языков одновременно. В отличие от базовых словарей, которые превращают все наборы символов в слова, Hunspell-словари оставляют нераспознанные слова и словосочетания незаписанными. Это позволяет обработать их следующими в конфигурации словарями.
Также появляется возможность создавать конфигурации полнотекстового поиска, которые могут обрабатывать несколько языков. Особенно хорошо такие структуры работают для языков с разными алфавитами.
Для примера создадим конфигурацию для обработки русского и английского языков.
В первом случае проверим, как система определяет русские слова:
postgres=# SELECT * FROM to_tsvector('rus_eng','Кто устоят, тот устоял свободно,
И столь же был свободен тот, кто пал.');
to_tsvector
--------------------------------------------------------------
'пал':13 'пасть':13 'свободный':5,10 'столь':7 'устоять':2,4
Как видно из результатов выдачи, ложных срабатываний нет, слова определены корректно.
Зададим поиск английских слов:
postgres=# SELECT * FROM to_tsvector('rus_eng','I made him just and right,
Sufficient to have stood, though free to fall.');
to_tsvector
------------------------------------------------------------------------------
'fall':14 'free':12 'made':2 'right':6 'stood':10 'sufficient':7 'though':11
Проблем при определении английских слов не возникло. Сгенерируем комбинированный текст из русских и английских слов.
postgres=# SELECT * FROM to_tsvector('rus_eng','Mixed text из русских and english слов words');
to_tsvector
-----------------------------------------------------------------------
'english':6 'mix':1 'mixed':1 'text':2 'word':8 'русский':4 'слово':7
Таким образом, можно убедиться в корректной работе словарей Hunspell с многоязычными текстами и точности их анализа.
Выводы
В результате данного внедрения мы смогли повысить точность и качество поиска слов на естественных языках. Также наш заказчик смог сократить число используемых индексов и упростить их использование: проще указывать один словарь на несколько случаев, чем каждый раз выбирать словари из списка в зависимости от желаемого языка.
Реклама, 18+. ООО «Газинформсервис» ИНН 7838017968
ERID: 2VfnxyJHsd3







