【问题标题】:Searching multiple tables with postgreSQL 13 and Rails 6+使用 postgreSQL 13 和 Rails 6+ 搜索多个表
【发布时间】:2021-10-31 01:12:09
【问题描述】:

我提供了很多上下文来为这个问题奠定基础。我要解决的是使用结构化数据对多个数据库表进行快速准确的模糊搜索,而不是全文文档搜索。

如果重要的话,我正在使用 postgreSQL 13.4+ 和 Rails 6+。

我有几个表格的结构化数据:

class Contact
  attribute :id
  attribute :first_name
  attribute :last_name
  attribute :email
  attribute :phone
end

class Organization
  attribute :name
  attribute :license_number
end

...several other tables...

我正在尝试实现快速准确的模糊搜索,以便我可以一次搜索所有这些表(Rails 模型)。

目前,我有一个单独的搜索查询,使用 ILIKE 连接我想要针对每个模型即时搜索的列:

# contact.rb
scope :search -> (q) { where("concat_ws(' ', first_name, last_name, email, phone) ILIKE :q", q: "%#{q}%") 

# organization.rb
scope :search -> (q) { where("concat_ws(' ', name, license_number) ILIKE :q", q: "%#{q}%") }

在我的搜索控制器中,我分别查询每个表并显示每个模型的前 3 个结果。

@contacts = Contact.search(params[:q]).limit(3)
@organizations = Organization.search(params[:q]).limit(3)

有效,但速度相当慢,而且不像我想要的那样准确。

我目前的方法存在问题:

  1. 只有数千条记录,速度较慢(相对而言)。
  2. 不准确,因为ILIKE 必须在字符串中的某处完全匹配,而我想实现模糊搜索(即,使用ILIKE,“smth”不会匹配“smith”)。
  3. 未加权;我想将contacts.last_name 列的权重置于organizations.name 之上,因为联系人表通常是优先级更高的搜索项。

我的解决方案

我的理论解决方案是创建一个search_entries 多态表,该表为我要搜索的每个contactorganization 等都有一个单独的记录,然后可以为这个search_entries 表建立索引快速检索。

class SearchEntry
  attribute :data
  belongs_to :searchable, polymorphic: true

  # Store data as all lowercase to optimize search (avoid lower method in PG)
  def data=(text)
    self[:data] = text.lowercase
  end
end

但是,我遇到的问题是如何构建此表,以便可以快速对其进行索引和搜索。

contact = Contact.first
SearchEntry.create(searchable: contact, data: "#{contact.first_name} #{contact.last_name} #{contact.email} #{contact.phone}")

organization = Organization.first
SearchEntry.create(searchable: organization, data: "#{organization.name} #{organization.license_number}")

这使我能够执行以下操作:

SearchEntry.where("data LIKE :q", q: "%#{q}%")

甚至使用 PG 的 similarity() 函数进行模糊搜索:

SearchEntry.connection.execute("SELECT * FROM search_entries ORDER BY SIMILARITY(data, '#{q}') LIMIT 10")

我相信我也可以在 data 字段上使用带有 pg_trgmGIN 索引来优化搜索(不是 100%...)。

这将我的搜索简化为对单个表的单个查询,但它仍然不允许我进行加权列搜索(即,contacts.last_nameorganizations.name 更重要)。

问题

  1. 这种方法是否可以让我对数据进行索引,以便进行非常快速的模糊搜索? (我知道“非常快”是主观的,所以我的意思是有效地使用 PG 以尽快获得结果)。
  2. 我能否使用GIN 索引和pg_trgm 三元组来索引这些数据以进行快速模糊搜索?
  3. 如何在这样的方法中实现某些值的权重高于其他值?

【问题讨论】:

  • 有一个工具可以做到这一点github.com/sunspot/sunspot
  • 您是否考虑过使用诸如 Solr、Lucene 或 ElasticSearch 之类的 OFS 搜索解决方案是否会更好?
  • @max:如果无法在 PG 本身中获得我需要的东西,我会将 ElasticSearch 视为我的后备方案。我试图通过将其全部保存在数据库中来减少对成本和复杂性的依赖。
  • @AhmedKamal:我现在正试图避免外部依赖,但如果在 PG 中完成搜索不能像我需要的那样工作,我会看看 ElasticSearch,因为我还有更多熟悉它。

标签: ruby-on-rails postgresql search


【解决方案1】:

一种可能的解决方案是创建一个materialized view,该materialized view 由两个(或多个表)中的数据联合组成。举个简单的例子:

CREATE MATERIALIZED VIEW searchables AS
  SELECT
    resource_id,
    resource_type,
    name,
    weight
  FROM 
    SELECT 
      id as resource_id,
      'Contact' as resource_type
      concat_ws(' ', first_name, last_name) AS name,
      1 AS weight
    FROM contacts
    UNION
    SELECT 
      id as resource_id,
      'Organization' as resource_type
      name
      2 AS weight 
    FROM organizations
class Searchable < ApplicationRecord
  belongs_to :resource, polymorphic: true

  def readonly?
    true
  end

  # Search contacts and organziations with a higher weight on contacts
  def self.search(name)
    where(arel_table[:name].matches(name)).order(weight: :desc)
  end
end

由于物化视图存储在类似表的结构中,因此您可以像使用普通表一样应用索引:

CREATE INDEX searchables_name_trgm ON name USING gist (searchables gist_trgm_ops);

对于 ActiveRecord,它的行为也与普通表一样。

当然,这里的复杂性会随着您要搜索的列数的增加而增加,与需要数千小时的现成解决方案相比,最终结果可能最终会在功能上平淡无奇,而且在复杂性上压倒一切。

风景宝石可用于简化创建物化视图的迁移。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-19
    • 1970-01-01
    • 1970-01-01
    • 2015-05-26
    • 2017-11-21
    • 2015-09-20
    • 2012-03-11
    相关资源
    最近更新 更多