blog-post

Фасетный поиск

Фасетный поиск является важной функцией для поиска иголки в стоге сена и улучшения пользовательского поиска для всех типов приложений. В этом руководстве мы разберемся, что такое фасетный поиск и как создать простой вариант.

Фасетный поиск так же важен для современного поискового приложения, как автозаполнение , коррекция правописания и выделение ключевых слов в поиске. Особенно в товарах электронной коммерции.

Он приходит на помощь, когда мы имеем дело с большими объемами данных и различными свойствами, относящимися друг к другу, будь то размер, цвет, производитель или что-то еще. При запросе больших объемов данных результаты поиска часто включают большие группы записей, которые не соответствуют ожиданиям пользователя. Фасетный поиск позволяет конечному пользователю явно указывать параметры, которым они хотят, чтобы их результаты поиска соответствовали.

Проще говоря, это дает возможность фильтровать результаты поиска по свойствам искомых элементов.

Предположим, что пользователь хочет найти фильм с Николасом Кейджем, поэтому он вводит его имя в строку поиска. Это вернет результаты поиска с фильмами из разных годов, разных режиссеров и с разными оценками и рейтингами на IMDB.

Вот как это может выглядеть:

imgМы видим, что у пользователя есть 34 различных фильма с его любимым актером, но как он может выбрать, что смотреть из такого большого числа фильмов?

На помощь приходит фасетный поиск. Мы можем позволить пользователю найти фильм, который он хочет посмотреть, предоставив ему возможность выбрать дополнительные параметры, такие как “Год”, “Рейтинг”, “Имя режиссера” и “Оценка IMDB” и т.д.

Вот как будут выглядеть результаты фасетного поиска:

imgС помощью фасетного поиска мы делаем возможным определить, что именно хочет пользователь. Выбирая фасеты, пользователи получают более релевантные результаты поиска, что делает поиск лучше, удобнее и полезнее с точки зрения пользовательского опыта и бизнес-целей.

Итак, давайте посмотрим, как работает фасетный поиск и как вы можете сделать его сами для своего поискового приложения.

Реализация фасетного поиска фильмов


В Manticore Search есть оптимизация, которая сохраняет набор результатов исходного запроса и повторно использует его для каждого вычисления фасета. Поскольку агрегирования применяются к уже вычисленному подмножеству документов, они быстрые, и общее время выполнения может в большинстве случаев быть лишь немного больше, чем начальный запрос. Фасеты могут быть добавлены к любому запросу, и фасет может быть любым атрибутом или выражением. Результат фасета содержит значения фасета вместе с количеством фасетов. Фасеты доступны с помощью SQL SELECT операторов, объявляя их в самом конце запроса в следующем формате:

FACET {expr_list} [BY {expr_list}] [ORDER BY {expr | FACET()} {ASC | DESC}] [LIMIT [offset,] count]

Конечно, вы можете просто отправить все запросы, которые хотите, в одном мульти-запросе , но если вы ищете более элегантное решение, есть условие FACET.

Теперь мы собираемся выполнить несколько запросов с фасетами фильмов, таких как:

  • Title_year
  • Content_rating
  • Director_name

И будем использовать интервал , чтобы получить значения в пределах целочисленного диапазона для оценки IMDB.

Давайте попробуем некоторые простые фасеты на индексе ‘movies’ из курса автозаполнения и выполним простые фасеты по нескольким атрибутам. Например, давайте выберем ‘robert de niro’ из свойств ‘Имя режиссера’.

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)

Сортировка в FACETS


По умолчанию результаты фасетов не отсортированы и также ограничены 20 строками. Каждый фасет может иметь свое собственное ограничение. Это можно сделать так:

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 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)

Выражения в 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">

Интерактивный курс

Вы можете узнать больше о фасетном поиске, если пройдете наш бесплатный интерактивный курс “Manticore Faceting”, который включает командную строку и веб-панель для более простого обучения.

Установить Manticore Search

Установить Manticore Search