Статьи

Удивительное автозаполнение: поиск триграмм в Rails и PostgreSQL

Снимок экрана 2015-11-07 15.28.34

PostgreSQL, в основном известный как Postgres, является одной из самых зрелых и надежных баз данных. Это мультиплатформенная, с открытым исходным кодом и вторая наиболее используемая СУБД с открытым исходным кодом.

Сегодня мы увидим, как реализовать базовый поиск автозаполнения с использованием триграммы в Postgres и Rails. Учебник разделен на три части:

  1. Что такое триграмма?
  2. Триграмма в Постгресе
  3. Реализация триграммы в примере приложения рельсов

Что такое триграмма?

Триграмма — это не что иное, как n-грамма с трехбуквенной последовательностью. Итак, что такое n-грамм? Из википедии,

В областях компьютерной лингвистики и вероятности n-грамм представляет собой непрерывную последовательность из n элементов из данной последовательности текста или речи.

Ну, что это значит, точно? Это означает поиск подходящих слов, поддерживая переменные последовательности символов в слове.

В триграмме последовательность переменных в n-грамме равна 3. Для нахождения сходства между двумя словами, wordA и wordB, wordA разбивается на трехбуквенные последовательности и сравнивается с трехбуквенными комбинациями последовательностей, вычисленными из wordB. Цель сравнения — найти количество общих наборов между двумя словами. Чем больше совпадений последовательности означает высокое сходство между словами. Это становится очень полезным с точки зрения автозаполнения.

Каждое слово обрабатывается двумя пробелами с префиксом и одним пробелом с суффиксом, чтобы сбалансировать количество триграмм для n-символьного слова. Это сбивает с толку, так что давайте иметь пример.

Предположим, у нас есть группа слов, состоящая из трех слов [google, toddle, beagle]googlr . Нам нужно найти наиболее подходящее слово из пакета для поискового запроса. Сначала слова пакета разделяются на три группы букв:

 google - g, go, goo, oog, ogl, gle, le
toddle - t, to, tod, odd, ddl, dle, le
beagle - b, be, bea, eag, agl, gle, le

Группы триграмм из трех букв будут рассчитываться для поискового запроса и сравниваться со словами в пакете для последовательностей, которые они разделяют:

 g, go, goo, oog, ogl, glr, lr

google - 5
toddle - 0
beagle - 0

Сходство рассчитывается по количеству разделяемых ими триграмм, что в нашем случае довольно тривиально. Таким образом, google

Для второго варианта использования, скажем, поисковый термин просто gle Триграмма это:

 g, gl, gle, le

Matches -
    google - 3
    toddle - 1
    beagle - 2

Для этого googlebeagle Как только вы освоитесь с этой концепцией, мы можем перейти к тому, как реализованы триграммы в Postgres.

Триграмма в PostgreSQL

Postgres поддерживает триграммы через расширение pg_trgm, которое официально поддерживается. Стоит отметить, что pgtrgm игнорирует специальные символы при расчете триграммы.

Следующий список состоит из функций, которые входят в расширение, которые помогают выполнять поиск триграмм:

  • similarity(text1, text2)text1text2

  • show_trgm(text)text

  • show_limit()% Индексы сходства выше этого предела отображаются только при выполнении поиска триграмм. Предел по умолчанию составляет 0,3.

  • set_limit(real)%

  • text1 % text2text1text2

  • text1 <-> text2 Возвращает расстояние между text1text2

  • gist\_trgm\_opsgin\_trgm\_ops

Давайте начнем с реализации поиска триграмм в приложении Rails.

Реализация триграммы в Rails

Наше примерное приложение будет очень простым, только с одной моделью Posttitlecontent Давайте быстро создадим приложение, модель и контроллер, используя команды ниже. Я использую Rails 4.2:

 rails new app_name -d postgresql
cd app_name
rails generate model post title content
rake db:create && rake db:migrate
rails generate controller posts index

Заполните базу данных поддельными данными. Я использую камень Faker . Ниже находится начальный файл:

 (0..100).each do |p|
  Post.create(title: Faker::Commerce.product_name, content: Faker::Company.catch_phrase)
  puts "Created #{p}"
end

Давайте также добавим некоторый базовый контент в контроллер:

 def index
  if params[:q].present?
    @posts = Post.where(title: params[:q])
  else
    @posts = Post.all
  end
end

В файле app / views / post / index.html.erb добавьте следующие строки, которые включают в себя базовое окно поиска и список всех сообщений:

 <form method="GET" action="/">
  <input placeholder="Search" id="search" type="text" name="q" />
  <input type="submit">
</form>

<table>
  <tr>
    <th>Title</th>
    <th>Content</th>
  </tr>
  <% @posts.each do |post| %>
    <tr>
      <td><%= post.title %></td>
      <td><%= post.content %></td>
    </tr>
  <% end %>
</table>

Теперь у нас есть базовое приложение с одной моделью, 100 строками постов, индексной страницей и опцией поиска, которая соответствует только полному заголовку поста. Давайте включим поиск триграмм в него.

Установите расширение pg_trgm

Как упоминалось ранее, Postgres предоставляет функциональность триграмм через расширение pg_trgm Установите его в приложении, используя миграцию, а не прямо в консоли psql. Создайте миграцию с помощью следующей команды:

 rails generate migration AddPgTrgmExtensionToDB

Добавьте следующее к миграции:

 execute "create extension pg_trgm;"

Запустите миграцию. Это установит расширение pg_trgm в Postgres.

Добавить индекс поиска

Когда мы это сделаем, давайте также добавим индекс к столбцу, который мы будем искать. GiST (обобщенное дерево поиска) и GIN (обобщенный инвертированный индекс) — это два вида индексов в Postgres. Добавление индекса не обязательно, но желательно для ускорения запросов. На данный момент я действительно не могу рекомендовать GiST или GIN, так как у меня были различия в производительности между ними в прошлом. Основные различия между двумя индексами и тем, как их выбрать, можно найти здесь . Добавьте тот, который работает для вас лучше всего.

Создайте миграцию и добавьте к ней следующую строку:

 add_index :posts, :title

Запустите миграцию и все! Мы все настроены на стороне базы данных. Быстро добавьте поисковый запрос, чтобы использовать сходство триграмм.

Метод поиска

Чтобы добавить опцию поиска, добавьте метод в нашу модель Post

 class Post < ActiveRecord::Base
  def self.text_search(query)
    self.where("similarity(title, ?) > 0.3", query).order("similarity(title, #{ActiveRecord::Base.connection.quote(query)}) DESC")
  end
end

Давайте также заменим строку поиска в нашем контроллере от

 @posts = Post.where(title: params[:q])

в

 @posts = Post.text_search(params[:q])

Вот и все. Запустите сервер, затем перейдите к поиску с опечатками и увидите магию похожих слов.

приложение

В методе text_searchpost.rb пороговое значение устанавливается равным 0.3 Не стесняйтесь настроить это, чтобы удовлетворить ваши требования. Чем больше порог, тем меньше результатов и тем строже поиски.

Одним из способов повышения скорости поиска является наличие отдельного столбца, который будет содержать все последовательности триграмм столбца заголовка. Затем мы можем выполнить поиск по предварительно заполненному столбцу. Или мы можем использовать ts_vector

Вывод

Существует гем под названием pg_search, который предоставляет функциональность поиска триграмм из коробки, но по какой-то причине поиск триграмм из этого гема для меня медленнее, чем в необработанном SQL. Я могу покрыть этот драгоценный камень в будущем.

Весь код, используемый для примера в этом, размещен на GitHub . Не стесняйтесь разыгрывать игру с этим.

Спасибо за чтение. Я надеюсь, что вы найдете это полезным при разработке приложений на Rails.