Эта статья описывает один из способов завершения слов в Manticore Search.
Что такое автозаполнение?
Автозаполнение (или завершение слов) — это функция, которая позволяет приложению предсказывать оставшуюся часть слова, пока пользователь его вводит. Обычно это работает так: пользователь начинает вводить слово в строке поиска, и появляется выпадающий список с предложениями, из которого пользователь может выбрать одно из них.
Источник предложений может быть различным. Лучше, если слово или предложение, которое отображается, доступно в существующей коллекции данных, чтобы пользователь не выбирал что-то, что вернет пустые результаты. Но в некоторых случаях автозаполнение основано на предыдущих (успешных) поисках, которые в теории могут найти ноль результатов, но могут все равно иметь смысл. Все зависит от специфики вашего приложения.
Самое простое автозаполнение можно сделать, найдя предложения по заголовкам элементов в наборе данных. Это может быть название статьи/новости, имя продукта или, как мы скоро покажем, название фильма. Для этого нам нужно иметь поле, определенное как строковый атрибут или сохраненное поле . Просто чтобы не выполнять лишний запрос к первоначальным данным.
Поскольку пользователь должен предоставить неполное слово, нам нужно выполнить поиск с подстановочными знаками. Поиск с подстановочными знаками возможен с помощью активации префиксирования или инфиксирования в индексе. Поскольку это может влиять на время отклика , нужно решить, хотите ли вы, чтобы это было включено в индекс, используемый для поиска, или вы включаете его только в специальный индекс, предназначенный для функции автозаполнения. Еще одна причина сделать это — сделать последний как можно компактнее для обеспечения минимальной задержки, так как это особенно важно для пользовательского опыта автозаполнения. Обычно мы добавляем подстановочный астериск справа, так как мы предполагаем, что пользователь начинает слово, однако для более широких результатов мы добавляем астериски с обеих сторон, чтобы получить слова, которые также могут иметь префикс. В этом курсе для набора данных фильмов давайте выберем инфиксирование, так как это также включает функцию SUGGEST для исправления слов (см. как это работает в этом курсе ). Наше объявление индекса будет следующим:
index movies {
type = plain
path = /var/lib/manticore/data/movies
source = movies
min_infix_len = 3
}
Поскольку мы собираемся предоставлять автозаполнение по названию фильма, наши запросы будут ограничены полем ‘movie_title’.
Автозаполнение по названию фильма
Фронтенд вашего приложения может начать запрашивать предложения с первого символа, введенного в поле поиска. Однако это может создать дополнительную нагрузку на систему в случае огромного индекса, поскольку будет выполнено больше запросов к серверу, а также поиск с подстановочными знаками на 1-2 символах может быть медленнее. Давайте предположим, что пользователь вводит ‘sha’.
MySQL [(none)]> SELECT id, movie_title FROM movies WHERE MATCH('@movie_title sha*');
+------+---------------------------------------+
| id | movie_title |
+------+---------------------------------------+
| 118 | A Low Down Dirty Shame |
| 394 | Austin Powers: The Spy Who Shagged Me |
| 604 | Book of Shadows: Blair Witch 2 |
| 951 | Dark Shadows |
| 1318 | Fifty Shades of Black |
| 1319 | Fifty Shades of Grey |
| 1389 | Forty Shades of Blue |
| 1853 | In the Shadow of the Moon |
| 1928 | Jack Ryan: Shadow Recruit |
| 3114 | Shade |
| 3115 | Shadow Conspiracy |
| 3116 | Shadow of the Vampire |
| 3117 | Shadowlands |
| 3118 | Shaft |
| 3119 | Shakespeare in Love |
| 3120 | Shalako |
| 3121 | Shall We Dance |
| 3122 | Shallow Hal |
| 3123 | Shame |
| 3124 | Shanghai Calling |
+------+---------------------------------------+
20 rows in set (0.00 sec)
Мы в основном заботимся только о названии фильма, поэтому не возвращаем все столбцы. Как мы видим, возвращено много результатов. Мы можем попробовать изменить запрос, например, добавив вторичную сортировку по количеству лайков в Facebook, но будет все еще слишком рано, чтобы хорошо догадаться о том, что ищет пользователь:
MySQL [(none)]> SELECT id, movie_title FROM movies WHERE MATCH('@movie_title sha*') ORDER BY WEIGHT() DESC, cast_total_facebook_likes DESC;
+------+--------------------------------------------------+
| id | movie_title |
+------+--------------------------------------------------+
| 951 | Dark Shadows |
| 3131 | Shark Tale |
| 394 | Austin Powers: The Spy Who Shagged Me |
| 3118 | Shaft |
| 4326 | The Shaggy Dog |
| 3142 | Sherlock Holmes: A Game of Shadows |
| 3134 | Shattered |
| 3123 | Shame |
| 3525 | The Adventures of Sharkboy and Lavagirl 3-D |
| 3117 | Shadowlands |
| 3129 | Shark Lake |
| 4328 | The Shawshank Redemption |
| 3494 | Teenage Mutant Ninja Turtles: Out of the Shadows |
| 3135 | Shattered Glass |
| 3130 | Shark Night 3D |
| 1319 | Пятьдесят оттенков серого |
| 4619 | Тристрама Шэнди: Невероятная история |
| 118 | Грязная стыдливость |
| 3132 | Шаркнэйдо |
| 1318 | Пятьдесят оттенков черного |
+------+--------------------------------------------------+
20 rows in set (0.00 sec)
Предположим, пользователь вводит другую букву:
MySQL [(none)]> SELECT id, movie_title FROM movies WHERE MATCH('@movie_title shaf*') ORDER BY WEIGHT() DES , cast_total_facebook_likes DESC;
+------+-------------+
| id | movie_title |
+------+-------------+
| 3118 | Шафт |
+------+-------------+
1 row in set (0.00 sec)
Теперь у нас есть единственный результат.
Рассмотрим другой пример, где мы вводим ‘shad*’ вместо.
MySQL [(none)]> SELECT id, movie_title FROM movies WHERE MATCH('@movie_title shad*') ORDER BY WEIGHT() DES , cast_total_facebook_likes DESC;
+------+--------------------------------------------------+
| id | movie_title |
+------+--------------------------------------------------+
| 951 | Темные Shadows |
| 3142 | Шерлок Холмс: Игра теней |
| 3117 | Shadowlands |
| 3494 | Черепашки-ниндзя: В тени |
| 1319 | Пятьдесят оттенков серого |
| 1318 | Пятьдесят оттенков черного |
| 4325 | Тень |
| 3115 | Заговор теней |
| 3116 | Тень вампира |
| 1928 | Джек Райан: Теневой рекрут |
| 1389 | Сорок оттенков синего |
| 604 | Книга теней: Блейр, 2 |
| 3114 | Тень |
| 1853 | В тени Луны |
| 4353 | Звук и тень |
+------+--------------------------------------------------+
15 rows in set (0.00 sec)
Затем shado*
:
MySQL [(none)]> SELECT id, movie_title FROM movies WHERE MATCH('@movie_title shado*') ORDER BY WEIGHT() DE C, cast_total_facebook_likes DESC;
+------+--------------------------------------------------+
| id | movie_title |
+------+--------------------------------------------------+
| 951 | Темные Shadows |
| 3142 | Шерлок Холмс: Игра теней |
| 3117 | Shadowlands |
| 3494 | Черепашки-ниндзя: В тени |
| 4325 | Тень |
| 3115 | Заговор теней |
| 3116 | Тень вампира |
| 1928 | Джек Райан: Теневой рекрут |
| 604 | Книга теней: Блейр, 2 |
| 1853 | В тени Луны |
| 4353 | Звук и тень |
+------+--------------------------------------------------+
11 rows in set (0.00 sec)
А ‘shadow’:
MySQL [(none)]> SELECT id, movie_title FROM movies WHERE MATCH('@movie_title shadow') ORDER BY WEIGHT() DE C, cast_total_facebook_likes DESC;
+------+---------------------------+
| id | movie_title |
+------+---------------------------+
| 4325 | Тень |
| 3115 | Заговор теней |
| 3116 | Тень вампира |
| 1928 | Джек Райан: Теневой рекрут |
| 1853 | В тени Луны |
| 4353 | Звук и тень |
+------+---------------------------+
6 rows in set (0.00 sec)
Предполагая, что пользователь искал ‘shadow’ как первое слово, он продолжит вводить другое слово, например ‘shadow c’:
MySQL [(none)]> SELECT id, movie_title FROM movies WHERE MATCH('@movie_title shadow c*') ORDER BY WEIGHT() DESC, cast_total_facebook_likes DESC;
+------+-------------------+
| id | movie_title |
+------+-------------------+
| 3115 | Заговор теней |
+------+-------------------+
1 row in set (0.01 sec)
В этом случае мы получаем единственный результат, который должен удовлетворить пользователя, но в других случаях мы можем получить больше, и пользователь будет продолжать вводить буквы, как и для первого слова, а Manticore будет возвращать больше предложений на основе ввода:
Добавление дополнительных фильтров
В предыдущих примерах единственным ограничением для совпадающих терминов было то, что они были частью указанного поля. Мы можем получить более строгую автозаполнение, если захотим.
Например, здесь мы получаем совпадения, начинающиеся с ‘americ’, например ‘American Hustle’, но также ‘Captain America: Civil War’:
MySQL [(none)]> SELECT id, movie_title FROM movies WHERE MATCH('@movie_title americ* ') ORDER BY WEIGHT() D SC, cast_total_facebook_likes DESC;
+------+---------------------------------------------------+
| id | movie_title |
+------+---------------------------------------------------+
| 277 | Американская афера |
| 701 | Капитан Америка: Гражданская война |
| 703 | Капитан Америка: Зимний солдат |
| 282 | Американский психопат |
| 2612 | Однажды в Америке |
| 272 | Американский гангстер |
| 702 | Капитан Америка: Первый мститель |
| 269 | Американская красота |
| 478 | Бивис и Батхед: Делают Америку |
| 284 | Американский снайпер |
| 4036 | Легенда о вратах ада: Американский заговор |
| 273 | Американская граффити |
| 285 | Американская слава |
| 274 | Американская афиша |
| 287 | Избранники Америки |
| 283 | Американский пирог: Воссоединение |
| 280 | Американский пирог |
| 281 | Американский пирог 2 |
| 271 | Американские мечты |
| 286 | Американский пирог: Свадьба |
+------+---------------------------------------------------+
20 строк в наборе (0,00 сек)
Мы можем использовать оператор начального поля, чтобы показать только записи, начинающиеся с введенного термина:
MySQL [(none)]> SELECT id, movie_title FROM movies WHERE MATCH('@movie_title ^americ* ') ORDER BY WEIGHT() ESC, cast_total_facebook_likes DESC;
+------+-------------------------------------+
| id | movie_title |
+------+-------------------------------------+
| 277 | Американский мошенник |
| 282 | Американский психопат |
| 272 | Американский гангстер |
| 269 | Красота по-американски |
| 284 | Снайпер |
| 273 | Американские граффити |
| 285 | Американский сплэндор |
| 274 | Американский налет |
| 287 | Избранники Америки |
| 283 | Американский пирог: Воссоединение |
| 280 | Американский пирог |
| 281 | Американский пирог 2 |
| 271 | Американские мечты |
| 286 | Американский пирог: Свадьба |
| 276 | Американская история X |
| 268 | Америка - это по-прежнему место |
| 279 | Американские разбойники |
| 275 | Американский герой |
| 278 | Американский ниндзя 2: Противостояние |
| 270 | Американский деси |
+------+-------------------------------------+
20 строк в наборе (0,00 сек)
Еще одно, на что следует обратить внимание, так это дубликаты. Это больше относится к случаям, когда мы хотим автозаполнение по полю, не имеющему уникальных значений. В качестве примера попробуем сделать автозаполнение по имени актера:
MySQL [(none)]> SELECT actor_1_name FROM movies WHERE MATCH('@actor_1_name john* ');
+--------------------+
| actor_1_name |
+--------------------+
| Джонни Депп |
| Джонни Депп |
| Джонни Депп |
| Дуэйн Джонсон |
| Джонни Депп |
| Джонни Депп |
| Дон Джонсон |
| Дуэйн Джонсон |
| Джонни Депп |
| Джонни Депп |
| Джонни Депп |
| Джонни Депп |
| Джонни Депп |
| Дуэйн Джонсон |
| Джонни Депп |
| Джонни Депп |
| Р. Брэндон Джонсон |
| Дуэйн Джонсон |
| Джонни Депп |
| Джонни Депп |
+--------------------+
20 строк в наборе (0,09 сек)
Это можно решить, просто сгруппировав это поле - при условии, что у нас есть атрибут строки:
MySQL [(none)]> SELECT actor_1_name FROM movies WHERE MATCH('@actor_1_name john* ') GROUP BY actor_1_name; [AMySQL [(none)]> SELECT actor_1_name FROM movies WHERE MATCH('@actor_1_name john* ') GROUP BY actor_1_name;
+------------------------+
| actor_1_name |
+------------------------+
| Джонни Депп |
| Дуэйн Джонсон |
| Дон Джонсон |
| Р. Брэндон Джонсон |
| Джонни Пакар |
| Кенни Джонстон |
| Джонни Каннизаро |
| Николь Рэндалл Джонсон |
| Джонни Льюис |
| Ричард Джонсон |
| Билл Джонсон |
| Эрик Джонсон |
| Джон Белуши |
| Джон Котрэн |
| Джон Рацenberger |
| Джон Кэмерон Митчелл |
| Джон Саксон |
| Джон Гэтинс |
| Джон Бойега |
| Джон Майкл Хиггинс |
+------------------------+
20 строк в наборе (0,10 сек)
Выделение
Запрос автозаполнения может возвращать результаты с включенным выделением. Хотя это также может быть выполнено на стороне приложений, выделение, выполненное Manticore Search, более мощное, потому что оно будет следовать поисковому запросу (те же настройки токенизации, И, ИЛИ и НЕТ в запросе и т.д. ). Взяв предыдущий пример, все, что нам нужно, это использовать функцию ‘SNIPPET’:
MySQL [(none)]> SELECT SNIPPET(actor_1_name,' john*') FROM movies WHERE MATCH('@actor_1_name john* ') GROUP BY actor_1_name ORDER BY WEIGHT() DESC, cast_total_facebook_likes DESC;
+--------------------------------+
| snippet(actor_1_name,' john*') |
+--------------------------------+
| <b>Джонни</b> Депп |
| Дуэйн <b>Джонсон</b> |
| <b>Джонни</b> Пакар |
| Дон <b>Джонсон</b> |
| <b>Джонни</b> Каннизаро |
| <b>Джонни</b> Льюис |
| Эрик <b>Джонсон</b> |
| Николь Рэндалл <b>Джонсон</b> |
| Кенни <b>Джонстон</b> |
| Р. Брэндон <b>Джонсон</b> |
| Билл <b>Джонсон</b> |
| Ричард <b>Джонсон</b> |
| <b>Джон</b> Рацenberger |
| <b>Джон</b> Белуши |
| <b>Джон</b> Кэмерон Митчелл |
| <b>Джон</b> Котрэн |
| Оливия Ньютон-<b>Джон</b> |
| <b>Джон</b> Майкл Хиггинс |
| <b>Джон</b> Уизерспун |
| <b>Джон</b> Амос |
+------------------------+
20 строк в наборе (0,00 сек)
Больше информации о выделении можно найти в этом курсе .
Кроме использования инфиксов и префиксов, есть и другие способы сделать автозаполнение в Manticore: используя CALL KEYWORDS , с включенным или выключенным bigram_index , используя CALL QSUGGEST / CALL SUGGEST . Но способ, показанный в этой статье, кажется самым простым для начала. Если вы хотите поиграть с Manticore Search, попробуйте наш docker image , который позволяет запустить Manticore на любом сервере всего за несколько секунд.