Эта статья описывает один из способов выполнения автозаполнения слов в 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 | Fifty Shades of Grey |
| 4619 | Tristram Shandy: A Cock and Bull Story |
| 118 | A Low Down Dirty Shame |
| 3132 | Sharknado |
| 1318 | Fifty Shades of Black |
+------+--------------------------------------------------+
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 | Shaft |
+------+-------------+
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 | Dark Shadows |
| 3142 | Sherlock Holmes: A Game of Shadows |
| 3117 | Shadowlands |
| 3494 | Teenage Mutant Ninja Turtles: Out of the Shadows |
| 1319 | Fifty Shades of Grey |
| 1318 | Fifty Shades of Black |
| 4325 | The Shadow |
| 3115 | Shadow Conspiracy |
| 3116 | Shadow of the Vampire |
| 1928 | Jack Ryan: Shadow Recruit |
| 1389 | Forty Shades of Blue |
| 604 | Book of Shadows: Blair Witch 2 |
| 3114 | Shade |
| 1853 | In the Shadow of the Moon |
| 4353 | The Sound and the Shadow |
+------+--------------------------------------------------+
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 | Dark Shadows |
| 3142 | Sherlock Holmes: A Game of Shadows |
| 3117 | Shadowlands |
| 3494 | Teenage Mutant Ninja Turtles: Out of the Shadows |
| 4325 | The Shadow |
| 3115 | Shadow Conspiracy |
| 3116 | Shadow of the Vampire |
| 1928 | Jack Ryan: Shadow Recruit |
| 604 | Book of Shadows: Blair Witch 2 |
| 1853 | In the Shadow of the Moon |
| 4353 | The Sound and the Shadow |
+------+--------------------------------------------------+
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 | The Shadow |
| 3115 | Shadow Conspiracy |
| 3116 | Shadow of the Vampire |
| 1928 | Jack Ryan: Shadow Recruit |
| 1853 | In the Shadow of the Moon |
| 4353 | The Sound and the Shadow |
+------+---------------------------+
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 | Shadow Conspiracy |
+------+-------------------+
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 | American Hustle |
| 701 | Captain America: Civil War |
| 703 | Captain America: The Winter Soldier |
| 282 | American Psycho |
| 2612 | Once Upon a Time in America |
| 272 | American Gangster |
| 702 | Captain America: The First Avenger |
| 269 | American Beauty |
| 478 | Beavis and Butt-Head Do America |
| 284 | American Sniper |
| 4036 | The Legend of Hell's Gate: An American Conspiracy |
| 273 | American Graffiti |
| 285 | American Splendor |
| 274 | American Heist |
| 287 | America's Sweethearts |
| 283 | American Reunion |
| 280 | American Pie |
| 281 | American Pie 2 |
| 271 | American Dreamz |
| 286 | American Wedding |
+------+---------------------------------------------------+
20 rows in set (0.00 sec)
Мы можем использовать оператор поля start, чтобы показать только записи, начинающиеся с вводимого термина:
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 | American Hustle |
| 282 | American Psycho |
| 272 | American Gangster |
| 269 | American Beauty |
| 284 | American Sniper |
| 273 | American Graffiti |
| 285 | American Splendor |
| 274 | American Heist |
| 287 | America's Sweethearts |
| 283 | American Reunion |
| 280 | American Pie |
| 281 | American Pie 2 |
| 271 | American Dreamz |
| 286 | American Wedding |
| 276 | American History X |
| 268 | America Is Still the Place |
| 279 | American Outlaws |
| 275 | American Hero |
| 278 | American Ninja 2: The Confrontation |
| 270 | American Desi |
+------+-------------------------------------+
20 rows in set (0.00 sec)
Еще одна вещь, которую мы должны учитывать, — это дубликаты. Это больше относится к случаям, когда мы хотим выполнить автозаполнение по полю, которое не имеет уникальных значений. В качестве примера давайте попробуем сделать автозаполнение по имени актера:
MySQL [(none)]> SELECT actor_1_name FROM movies WHERE MATCH('@actor_1_name john* ');
+--------------------+
| actor_1_name |
+--------------------+
| Johnny Depp |
| Johnny Depp |
| Johnny Depp |
| Dwayne Johnson |
| Johnny Depp |
| Johnny Depp |
| Don Johnson |
| Dwayne Johnson |
| Johnny Depp |
| Johnny Depp |
| Johnny Depp |
| Johnny Depp |
| Johnny Depp |
| Dwayne Johnson |
| Johnny Depp |
| Johnny Depp |
| R. Brandon Johnson |
| Dwayne Johnson |
| Johnny Depp |
| Johnny Depp |
+--------------------+
20 rows in set (0.09 sec)
Мы видим много дубликатов. Это можно решить, просто сгруппировав по этому полю — предполагая, что у нас есть это как строковый атрибут:
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 |
+------------------------+
| Johnny Depp |
| Dwayne Johnson |
| Don Johnson |
| R. Brandon Johnson |
| Johnny Pacar |
| Kenny Johnston |
| Johnny Cannizzaro |
| Nicole Randall Johnson |
| Johnny Lewis |
| Richard Johnson |
| Bill Johnson |
| Eric Johnson |
| John Belushi |
| John Cothran |
| John Ratzenberger |
| John Cameron Mitchell |
| John Saxon |
| John Gatins |
| John Boyega |
| John Michael Higgins |
+------------------------+
20 rows in set (0.10 sec)
Подсветка
Запрос автозаполнения может возвращать результаты с включенной подсветкой. Хотя это также можно выполнить на стороне приложений, подсветка, выполненная Manticore Search, более мощная, потому что она будет следовать запросу поиска (те же настройки токенизации, AND, OR и NOT в запросе и так далее ). Беря предыдущий пример, все, что нам нужно сделать, это использовать функцию '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>Johnny</b> Depp |
| Dwayne <b>Johnson</b> |
| <b>Johnny</b> Pacar |
| Don <b>Johnson</b> |
| <b>Johnny</b> Cannizzaro |
| <b>Johnny</b> Lewis |
| Eric <b>Johnson</b> |
| Nicole Randall <b>Johnson</b> |
| Kenny <b>Johnston</b> |
| R. Brandon <b>Johnson</b> |
| Bill <b>Johnson</b> |
| Richard <b>Johnson</b> |
| <b>John</b> Ratzenberger |
| <b>John</b> Belushi |
| <b>John</b> Cameron Mitchell |
| <b>John</b> Cothran |
| Olivia Newton-<b>John</b> |
| <b>John</b> Michael Higgins |
| <b>John</b> Witherspoon |
| <b>John</b> Amos |
+--------------------------------+
20 rows in set (0.00 sec)
Вы можете найти больше информации о подсветке в этом курсе .
Кроме использования инфиксов и префиксов, есть и другие способы сделать автозаполнение в Manticore: используя CALL KEYWORDS , с включенным или выключенным bigram_index , используя CALL QSUGGEST / CALL SUGGEST . Но способ, показанный в этой статье, кажется самым простым для начала. Если вы хотите поиграть с Manticore Search, попробуйте наш docker image , который имеет однострочную команду для запуска Manticore на любом сервере всего за несколько секунд.
