12/08/2018, 15:19

Sử dụng gem pg_search để xây dựng chức năng tìm kiếm trong PostgreSQL

Tìm kiếm là một trong những chức năng phổ biến nhất mà bất kỳ trang web nào cũng được tích hợp. Có rất nhiều giải pháp đã được đưa ra để giải bài toán tìm kiếm trong ứng dụng của bạn. Sau thời gian dài làm việc với MySQL, gần đây mình làm quen với PostgreSQL để đổi gió và mình thấy có gem ...

Tìm kiếm là một trong những chức năng phổ biến nhất mà bất kỳ trang web nào cũng được tích hợp. Có rất nhiều giải pháp đã được đưa ra để giải bài toán tìm kiếm trong ứng dụng của bạn.

Sau thời gian dài làm việc với MySQL, gần đây mình làm quen với PostgreSQL để đổi gió và mình thấy có gem pg_search có khả năng hỗ trợ tìm kiếm rất hay trên PostgreSQL.

Trong bài viết này mình sẽ xây dựng một ứng dụng đơn giản và sử dụng gem pg_search để làm chức năng tìm kiếm cho ứng dụng đó.

Khởi tạo

Môi trường làm việc :

  • Rails 5.0.1
  • PostgreSQL (nếu chưa cài đặt thì bạn có thể xem hướng dẫn)

Hãy cùng thực hiện nhanh các bước chuẩn bị này vì nó rất quen thuộc.

Trước tiên, tạo một ứng dụng Rails mới sử dụng tìm kiếm trong Postgres :

rails new autocomplete --database=postgresql

Chuẩn bị cấu hình trong file config/database.yml để kết nối tới DB.

development:
  adapter: postgresql
  database: autocomplete
  host: localhost
  user: username
  password: password

Tạo một bảng đơn giản để tiến hành việc tìm kiếm với 2 trường name, surname

rails g model User name:string surname:string
rails db:migrate

Tạo một vài dữ liệu mẫu cho bảng users, tốt nhất là nên tạo ra các use có trường name là phân biệt. Bạn có thể dùng gem Faker tạo ra file seed đơn giản như sau :

50.times do
  User.create({name: Faker::Name.first_name, surname: Faker::Name.last_name})
end

Cuối cùng, tạo controller, view và trỏ router đến controller

  • users_controller.rb
class UsersController < ApplicationController
  def index
    @users = User.all
  end
end
  • views/users/index.html.erb
<ul>
  <% @users.each do |user| %>
    <li><%= user.name %> <%= user.surname %></li>
</ul>
  • config/routes.rb
resources :users, only: [:index]
root to: "users#index"

Như vậy là chúng ta đã chuẩn bị xong môi trường. Hãy tiến hành thêm chức năng tìm kiếm vào nhé.

Tìm kiếm users

Trước tiên, hãy thêm một box search vào view như sau :

<%= form_tag users_path, method: :get do %>
  <%= text_field_tag "term", params[:term], placeholder: "Enter user's name or surname" %>
  <%= submit_tag "Search!"" %>
<% end

Bây giờ là lúc cần cài đặt gem pg_search cho ứng dụng.

Gem pg_search hỗ trợ chế độ pg_search_scope (tương tự như scope trong ActiveRecord) với cú pháp pg_search_scope SCOPE_NAME, against: OPTIONS trong đó :

  • OPTIONS là danh sách các trường mà chúng ta cần tìm kiếm

Áp dụng vào model User cho việc tìm kiếm với cả 2 trường name và surname như sau :

# ...
include PgSearch
pg_search_scope :search_by_full_name, against: [:name, :surname]

Sửa lại action index trong controller để sử dụng scope vừa khai báo như sau :

def index
  if params[:term]
    @users = User.search_by_full_name(params[:term])
  else
    @users = User.all
  end
end

Đến đây thì bạn hãy chạy server lên để xem kết quả đầu tiên nhé.

Tìm kiếm với options nâng cao

Chức năng tìm kiếm của chúng ta đã làm việc, tuy nhiên hiện tại, nó chỉ đơn thuần là so sánh = giữa text mình truyền vào box search với trường name hoặc surname của các bản ghi trong BD.

Hãy nâng cấp khả năng tìm kiếm cho ứng dụng bằng việc sử dụng các kỹ thuật tìm kiếm mà pg_search cung cấp :

  • tsearch - full text search, được tích hợp vào PostgreSQL
  • trigram - yêu cầu phải có phần mở rộng trigram
  • dmetaphone - yêu cầu phái có phần mở rộng fuzzystrmatch

Sẽ được khai báo trong param using của scope tìm kiếm đã được định nghĩa ở trên. Trong bài viết này mình chỉ trình bày về tsearch, hai kỹ thuật còn lại bạn có thể tham khảo thêm tại đây

PostgreSQL tích hợp full text search với các hỗ trợ : trọng số, tìm kiếm tiền tố,...

  • Trọng số

Với các trường cho phép tìm kiếm thì sẽ được gán các trọng số A > B > C > D, ưu tiên theo thứ tự trên. Trọng số có thể được khai báo trong key against dưới dạng Hash, Array như sau :

class Article < ActiveRecord::Base
  include PgSearch
  pg_search_scope :search_full_text1, against: {
    title: "A",
    subtitle: "B",
    content: "C"
  }

  pg_search_scope :search_full_text2, against: [
    [:title, "A"],
    [:subtitle, "B"],
    [:content, "C"]
  ]

  pg_search_scope :search_full_text3, against: [
    [:title, "A"],
    {subtitle: "B"},
    :content
  ]
end

Như ở trên thì thứ tự ưu tiên khi tìm kiếm sẽ là title > subtitle > content

  • :prefix

Chỉ hỗ trợ cho PostgreSQL 8.4 trở lên.

Mặc định thì full text search sẽ tìm kiếm trong toàn bộ từ, nếu bạn chỉ muốn tìm kiếm một phần thì bạn sẽ thêm key prefix: true vào trong tsearch như sau :

class Superhero < ActiveRecord::Base
  include PgSearch
  pg_search_scope :whose_name_starts_with,
                  against: :name,
                  using {
                    tsearch: { prefix: true }
                  }
end

Khi đó ta sẽ tìm kiếm được như sau :

batman = Superhero.create name: "Batman"
batgirl = Superhero.create name: "Batgirl"
robin = Superhero.create name: "Robin"

Superhero.whose_name_starts_with("Bat") # => [batman, batgirl]
  • :negation

Mặc định thì full text search sẽ tìm kiếm với tất cả các cụm từ đầu vào, nếu muốn tìm kiếm loại trừ một số cụm từ thì bạn có thể khai báo negation: true bên trong tsearch. Khi tìm kiếm thì đầu vào của bạn chỉ cần thêm ký tự ! trước cụm từ bạn muốn loại bỏ khi tìm kiếm :

class Animal < ActiveRecord::Base
  include PgSearch
  pg_search_scope :with_name_matching,
                  against: :name,
                  using: {
                    tsearch: { negation: true }
                  }
end

one_fish = Animal.create(:name => "one fish")
two_fish = Animal.create(:name => "two fish")
red_fish = Animal.create(:name => "red fish")
blue_fish = Animal.create(:name => "blue fish")

Animal.with_name_matching("fish !red !blue") # => [one_fish, two_fish]

Ngoài ra còn có một số option khác cũng khá hay nữa như dictionary, normalization, highlight... mà việc kết hợp sử dụng linh hoạt giữa chúng sẽ giúp ta xây dựng một ứng dụng tìm kiếm rất hiệu quả.

Tham khảo

  • https://github.com/Casecommons/pg_search
  • https://www.sitepoint.com/search-autocomplete-rails-apps/

Hi vọng bài viết sẽ giúp bạn có thêm lựa chọn để xây xựng tính năng tìm kiếm cho ứng dụng của mình.

Cám ơn bạn đã theo dõi bài viết

tribeo.
0