Будет ли индекс триграмм по нескольким столбцам ускорять поиск и как правильно его выполнять? - PullRequest
1 голос
/ 11 октября 2019

Предположим, у меня есть таблица с несколькими столбцами. Например:

id int
name text
surname text
cars json

пример записи будет

+----+------+---------+------------------------------------+
| id | name | surname |              cars                  |
+----+------+---------+------------------------------------+
|  1 | John | Doe     | {"values":["Ford", "BMW", "Fiat"]} |
+----+------+---------+------------------------------------+

Я хочу найти все эти данные таблицы на предмет соответствия:

select *,
       similarity(
          'Malcolm Joe likes Ferrary, but hates BMW',
          (name || (cars ->> 'values') || surname)
       ) sim
from public.test_table
where similarity(
         'Malcolm Joe likes Ferrary, but hates BMW',
         (name || (cars ->> 'values') || surname)
      ) > 0.05
order by sim desc;

Есть лиспособ ускорить этот поиск? Создание индекса триграммы? Если так - как его лучше создать? в одном столбце, в каждом столбце, в выражении конкатенации? Кроме того, я не понимаю, какой тип индекса лучше - GIN или GiST. Я читал, что GIN обычно лучше для обычного полнотекстового поиска, но GiST лучше для поиска триграмм. Это правильно?

Я также хотел спросить, есть ли лучший способ написать вышеупомянутый запрос?

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

Ответы [ 2 ]

1 голос
/ 11 октября 2019

В этом случае вам нужен индекс GiST, потому что только тот, который можно использовать с ORDER BY запросами с использованием оператора расстояния триграмм:

CREATE INDEX ON public.test_table USING gist
   ((name || (cars ->> 'values') || surname) gist_trgm_ops);

Затем запрос должен быть переписан в:

SELECT *,
       similarity(
          'Malcolm Joe likes Ferrary, but hates BMW',
          (name || (cars ->> 'values') || surname)
       ) sim
FROM public.test_table
WHERE ((name || (cars ->> 'values') || surname)
       <->
       'Malcolm Joe likes Ferrary, but hates BMW')
      < 0.95
ORDER BY (name || (cars ->> 'values') || surname)
         <->   /* trigram distance */
         'Malcolm Joe likes Ferrary, but hates BMW'
LIMIT 50;

Запрос нужно было переписать, потому что есть поддержка индекса для <->, но не для similarity() в ORDER BY выражениях.

Я добавил LIMIT, чтобы подсказать оптимизаторуустановите соответствующий предел.

Я думаю , что в целом индексы GIN работают лучше для больших таблиц, но я не уверен. В любом случае, у вас нет выбора с этим запросом, потому что индексы GIN не будут поддерживать это предложение ORDER BY.

0 голосов
/ 11 октября 2019

В вашем примере вы захотите создать индекс для выражения (name || (cars ->> 'values') || surname). Однако сам ваш пример не имеет смысла. Это действительный SQL, но с какой стати вы захотите это сделать? Почему вы сравниваете английское предложение со строкой, состоящей из чьего-то полного имени, но с серединой капли JSON? Это важно, потому что в вашем примере только одна строка, поэтому индекс не имеет значения. Поэтому мы должны экстраполировать ваш пример на большое количество строк, где будет иметь значение индекс. Но поскольку это не имеет никакого смысла в реальном мире, как мы можем разумно экстраполировать его?

Кроме того, я не понимаю, какой тип индекса лучше - GIN или GiST,Я читал, что GIN обычно лучше для обычного полнотекстового поиска, но GiST лучше для поиска триграмм. Это правильно?

Не совсем так по моему опыту. Индексы триграмм GiST основаны на сигнатурах, где каждая триграмма устанавливает бит в сигнатуре. Но триграмм гораздо больше, чем битов, поэтому они сильно перегружены. Эти типы индексов работают хорошо только тогда, когда они немного заполнены. (Но трудно сказать, что означает «малонаселенный» заранее, другие «пробуют это с вашим реальным набором данных и видят».) Учитывая их непредсказуемость, я избегаю индексов GiST, если для них нет явной выгоды, которую яздесь не вижу.

Учитывая ваш запрос, вы можете использовать любой вид индекса, но его придется написать по-другому. Кроме того, сомнительно, что любой индекс помог бы, потому что в

similarity(x,exp) > 0.05

отсечение 0,05 настолько слабое, что несколько строк, вероятно, будут отклонены индексом.

Если вы имеличем выше значение отсечки, например 0,5, то с индексом GIN это будет выглядеть следующим образом:

set pg_trgm.similarity_threshold = 0.5;
select ... from test_table where x % exp order by x <-> exp ;

Это позволит извлечь все достаточно похожие, а затем отсортировать их по расстоянию. Если достаточно мало вещей "достаточно похожи", это дает довольно хорошую производительность (а если нет, вам следует вернуться к своему выбору pg_trgm.simility_threshold). Как говорит Лоренц Альбе, с помощью индекса GiST вы можете извлечь уже упорядоченные строки и затем остановиться после достижения LIMIT, но в отсутствие предложения LIMIT это не имеет значения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...