Фасетный поиск — это важная функция для нахождения иголки в стоге сена и улучшения пользовательского опыта поиска во всех типах приложений. В этом руководстве мы разберём, что такое фасетный поиск и как создать простой пример.
Фасетный поиск является такой же необходимой функцией современного поискового приложения, как autocomplete , исправление орфографии и подсветка поисковых ключевых слов. Особенно в e‑commerce продуктах.
Он приходит на помощь, когда мы имеем дело с большими объёмами данных и различными свойствами, связанными друг с другом, будь то размер, цвет, производитель или что‑то ещё. При запросе больших объёмов данных результаты поиска часто включают огромные списки записей, которые не соответствуют ожиданиям пользователя. Фасетный поиск позволяет конечному пользователю явно указать измерения, которым должны соответствовать результаты поиска.
Проще говоря, он даёт возможность фильтровать результаты поиска по свойствам искомых элементов.
Предположим, пользователь хочет найти фильм с Николасом Кейджем, поэтому он вводит его имя в строку поиска. Это вернёт результаты с фильмами разных годов, разных режиссёров и с разными оценками IMDb.
Так это может выглядеть:
Мы видим, что у пользователя есть 34 разных фильма с его любимым актёром, но как ему выбрать, что смотреть из такого большого количества фильмов?
Вот где появляется фасетный поиск. Мы можем позволить пользователю найти нужный фильм, дав ему возможность выбрать дополнительные параметры, такие как «Год», «Рейтинг», «Имя режиссёра» и «Оценка IMDb» и т.д.
Так будут выглядеть результаты фасетного поиска:
С помощью фасетного поиска мы делаем возможным определить, что именно хочет пользователь. Выбирая фасеты, пользователи получают более релевантные результаты поиска, что делает поиск лучше, удобнее и полезнее с точки зрения пользовательского опыта и бизнес‑целей.
Итак, посмотрим, как работает фасетный поиск и как вы можете создать его самостоятельно для вашего поискового приложения.
Implementing the movies faceted search
В Manticore Search существует оптимизация, сохраняющая набор результатов оригинального запроса и повторно использующая его для расчёта каждой фасеты. Поскольку агрегации применяются к уже вычисленному подмножеству документов, они работают быстро, и общее время выполнения во многих случаях лишь незначительно превышает время первоначального запроса. Фасеты можно добавить к любому запросу, а фасет может быть любой атрибут или выражение. Результат фасеты содержит значения фасеты вместе с их счётчиками. Фасеты доступны через SQL SELECT оператор, объявляя их в самом конце запроса в следующем формате:
FACET {expr_list} [BY {expr_list}] [ORDER BY {expr | FACET()} {ASC | DESC}] [LIMIT [offset,] count]
Конечно, вы можете просто отправить все запросы, которые хотите, в одном multi-query , но если вы ищете более элегантное решение, существует оператор FACET.
Теперь мы запустим несколько запросов с фасетами фильмов, таких как:
- Title_year
- Content_rating
- Director_name
И будем использовать interval для получения значений в целочисленном диапазоне оценки IMDb.
Попробуем простые фасеты в индексе «movies» из курса автодополнения и выполним простые фасеты по нескольким атрибутам. Например, выберем «robert de niro» из свойства «Director name».
SELECT * FROM movies WHERE MATCH('robert de niro') LIMIT 10 FACET title_year FACET content_rating FACET director_name;
Это даёт несколько наборов результатов, где первый — из нашего основного запроса, а остальные — фасеты. Каждый результат фасеты содержит значения атрибутов и количество групп.
+------------+----------+
| title_year | count(*) |
+------------+----------+
| 1987 | 1 |
| 1991 | 1 |
| 2005 | 1 |
| 1997 | 3 |
| 1974 | 1 |
| 2001 | 2 |
| 2002 | 2 |
| 1999 | 2 |
| 1985 | 1 |
| 1995 | 1 |
| 2016 | 2 |
| 2009 | 1 |
| 2004 | 4 |
| 1990 | 1 |
| 2013 | 3 |
| 2015 | 3 |
| 2011 | 2 |
| 2010 | 3 |
| 1996 | 3 |
| 1973 | 1 |
+------------+----------+
20 rows in set (0.10 sec)
+----------------+----------+
| content_rating | count(*) |
+----------------+----------+
| R | 37 |
| PG-13 | 12 |
| PG | 4 |
+----------------+----------+
3 rows in set (0.10 sec)
+----------------------+----------+
| director_name | count(*) |
+----------------------+----------+
| Brian De Palma | 1 |
| Martin Scorsese | 7 |
| John Polson | 1 |
| Quentin Tarantino | 1 |
| Francis Ford Coppola | 1 |
| John Herzfeld | 1 |
| Harold Ramis | 2 |
| Terry Gilliam | 1 |
| Michael Caton-Jones | 1 |
| James Mangold | 1 |
| Dan Mazer | 1 |
| Kirk Jones | 1 |
| Joel Schumacher | 1 |
| Nick Hamm | 1 |
| Peter Segal | 1 |
| Jonathan Jakubowicz | 1 |
| Scott Mann | 1 |
| David O. Russell | 2 |
| Gary McKendry | 1 |
| Jon Turteltaub | 1 |
+----------------------+----------+
20 rows in set (0.10 sec)
Sorting in FACETS
По умолчанию результаты фасет не сортируются и ограничены 20 строками. Каждая фасета может иметь собственный оператор limit. Это можно сделать так:
SELECT * FROM movies WHERE MATCH('robert de niro') LIMIT 10 FACET title_year LIMIT 5 FACET content_rating LIMIT 1 FACET director_name LIMIT 100;
+------------+----------+
| title_year | count(*) |
+------------+----------+
| 1987 | 1 |
| 1991 | 1 |
| 2005 | 1 |
| 1997 | 3 |
| 1974 | 1 |
+------------+----------+
5 rows in set (0.19 sec)
+----------------+----------+
| content_rating | count(*) |
+----------------+----------+
| R | 37 |
+----------------+----------+
1 row in set (0.19 sec)
+----------------------+----------+
| director_name | count(*) |
+----------------------+----------+
| Brian De Palma | 1 |
| Martin Scorsese | 7 |
| John Polson | 1 |
| Quentin Tarantino | 1 |
| Francis Ford Coppola | 1 |
.................................
| Tony Scott | 1 |
| Nancy Meyers | 1 |
| Frank Oz | 1 |
| Neil Jordan | 1 |
+----------------------+----------+
42 rows in set (0.19 sec)
Как видите, результаты по умолчанию не отсортированы. Каждая фасета может иметь своё правило сортировки, например, мы можем отсортировать группы по счётчикам, используя COUNT(*):
SELECT * FROM movies WHERE MATCH('robert de niro') LIMIT 10 FACET title_year ORDER BY COUNT(*) DESC;
+------------+----------+
| title_year | count(*) |
+------------+----------+
| 2004 | 4 |
| 2013 | 3 |
| 2015 | 3 |
| 1997 | 3 |
| 2010 | 3 |
| 1996 | 3 |
| 2000 | 3 |
| 2001 | 2 |
| 2002 | 2 |
| 1999 | 2 |
| 2016 | 2 |
| 2011 | 2 |
| 2012 | 2 |
| 2008 | 2 |
| 1985 | 1 |
| 1991 | 1 |
| 1995 | 1 |
| 2009 | 1 |
| 1990 | 1 |
| 2005 | 1 |
+------------+----------+
20 rows in set (0.09 sec)
Или мы можем сортировать по атрибуту, например, title_year:
SELECT * FROM movies WHERE MATCH('robert de niro') LIMIT 10 FACET title_year ORDER BY title_year DESC;
+------------+----------+
| title_year | count(*) |
+------------+----------+
| 2016 | 2 |
| 2015 | 3 |
| 2013 | 3 |
| 2012 | 2 |
| 2011 | 2 |
| 2010 | 3 |
| 2009 | 1 |
| 2008 | 2 |
| 2005 | 1 |
| 2004 | 4 |
| 2002 | 2 |
| 2001 | 2 |
| 2000 | 3 |
| 1999 | 2 |
| 1998 | 1 |
| 1997 | 3 |
| 1996 | 3 |
| 1995 | 1 |
| 1991 | 1 |
| 1990 | 1 |
+------------+----------+
20 rows in set (0.09 sec)
Facet selection
В самых простых примерах мы используем FACET attr_name, и набор результатов будет содержать столбцы attr_name и count.
Но фасету можно построить из нескольких атрибутов:
SELECT * FROM movies WHERE MATCH('robert de niro') LIMIT 10 FACET director_facebook_likes,director_name BY director_name ORDER BY director_facebook_likes DESC;
+-------------------------+---------------------+----------+
| director_facebook_likes | director_name | count(*) |
+-------------------------+---------------------+----------+
| 17000 | Martin Scorsese | 7 |
| 16000 | Quentin Tarantino | 1 |
| 12000 | Tony Scott | 1 |
| 11000 | Harold Ramis | 2 |
| 737 | David O. Russell | 2 |
| 541 | Joel Schumacher | 1 |
| 517 | Michael Cimino | 1 |
| 446 | James Mangold | 1 |
| 287 | John Frankenheimer | 1 |
| 278 | Nancy Meyers | 1 |
| 277 | Neil Jordan | 1 |
| 272 | Barry Levinson | 3 |
| 226 | Jon Turteltaub | 1 |
| 116 | Jay Roach | 2 |
| 105 | Michael Caton-Jones | 1 |
| 102 | Martin Brest | 1 |
| 89 | Rodrigo Cortés | 1 |
| 88 | Peter Segal | 1 |
| 88 | George Tillman Jr. | 1 |
| 80 | Paul Weitz | 1 |
+-------------------------+---------------------+----------+
20 rows in set (0.01 sec)
Expressions in FACETs
В некоторых ситуациях фасетирование по реальным значениям не то, что нам нужно. Наиболее распространённый пример — цены продуктов, где диапазон значений может быть широким. Вместо получения фасетных реальных значений мы хотим получить список диапазонов. Это легко реализовать с помощью функции INTERVAL().
В нашем примере мы используем imdb_score, так как это значение с плавающей точкой, которое явно не нужно группировать, вместо этого мы группируем по диапазонам между целыми значениями:
SELECT * FROM movies WHERE MATCH('robert de niro') LIMIT 100 FACET title_year LIMIT 100 FACET content_rating LIMIT 100 FACET director_name LIMIT 100;
Empty set (0.09 sec)
+------------+----------+
| title_year | count(*) |
+------------+----------+
| 1987 | 1 |
| 1991 | 1 |
| 2005 | 1 |
| 1997 | 3 |
| 1974 | 1 |
| 2001 | 2 |
| 2002 | 2 |
| 1999 | 2 |
| 1985 | 1 |
| 1995 | 1 |
| 2016 | 2 |
| 2009 | 1 |
| 2004 | 4 |
| 1990 | 1 |
| 2013 | 3 |
| 2015 | 3 |
| 2011 | 2 |
| 2010 | 3 |
| 1996 | 3 |
| 1973 | 1 |
| 2000 | 3 |
| 1988 | 1 |
| 1977 | 1 |
| 1984 | 1 |
| 1980 | 1 |
| 2012 | 2 |
| 2008 | 2 |
| 1998 | 1 |
| 1976 | 1 |
| 1978 | 1 |
| 1989 | 1 |
+------------+----------+
31 rows in set (0.07 sec)
+----------------+----------+
| content_rating | count(*) |
+----------------+----------+
| R | 37 |
| PG-13 | 12 |
| PG | 4 |
+----------------+----------+
3 rows in set (0.08 sec)
+----------------------+----------+
| director_name | count(*) |
+----------------------+----------+
| Brian De Palma | 1 |
| Martin Scorsese | 7 |
| John Polson | 1 |
| Quentin Tarantino | 1 |
| Francis Ford Coppola | 1 |
| John Herzfeld | 1 |
| Harold Ramis | 2 |
| Terry Gilliam | 1 |
| Michael Caton-Jones | 1 |
| James Mangold | 1 |
| Dan Mazer | 1 |
| Kirk Jones | 1 |
| Joel Schumacher | 1 |
| Nick Hamm | 1 |
| Peter Segal | 1 |
| Jonathan Jakubowicz | 1 |
| Scott Mann | 1 |
| David O. Russell | 2 |
| Gary McKendry | 1 |
| Jon Turteltaub | 1 |
| Paul Weitz | 1 |
| Ethan Maniquis | 1 |
| Jerry Zaks | 1 |
| Jay Roach | 2 |
| George Tillman Jr. | 1 |
| Martin Brest | 1 |
| Garry Marshall | 1 |
| Sergio Leone | 1 |
| Rodrigo Cortés | 1 |
| Jon Avnet | 1 |
| John Frankenheimer | 1 |
| Bibo Bergeron | 1 |
| Barry Levinson | 3 |
| John Curran | 1 |
| Des McAnuff | 1 |
| Justin Zackham | 1 |
| Mary McGuckian | 1 |
| Michael Cimino | 1 |
| Tony Scott | 1 |
| Nancy Meyers | 1 |
| Frank Oz | 1 |
| Neil Jordan | 1 |
+----------------------+----------+
42 rows in set (0.08 sec)
<img src="Faceted-search-course-example-optimized.webp" alt="img">
Interactive Course
Вы можете узнать больше о фасетном поиске, пройдя наш бесплатный интерактивный курс «Manticore Faceting», который включает командную строку и веб‑панель для более лёгкого обучения.
