· about 7 hours ago
Casecommons/pg_search 是 Ruby on Rails 的一个流行 gem,它基于 PostgreSQL 的内置全文搜索(tsvector/tsquery)提供方便的 ActiveRecord scopes,支持 tsearch、trigram 和 dmetaphone 等策略。
PgSearch 构建命名范围,利用 PostgreSQL 的全文搜索功能。
请阅读这篇介绍 PgSearch 的博客文章: https://tanzu.vmware.com/content/blog/pg-search-how-i-learned-to-stop-worrying-and-love-postgresql-full-text-search
$ gem install pg_search
或者将这行代码添加到你的 Gemfile 文件中:
gem 'pg_search'
除了安装并引入 gem 之外,您可能还需要在 Rakefile 文件中包含 PgSearch 的 Rake 任务。对于 Rails 项目来说,这并非必要,因为 Rails 项目可以通过 Railtie 获取 Rake 任务。
load "pg_search/tasks.rb"
要将 PgSearch 添加到 Active Record 模型中,只需包含 PgSearch 模块即可。
class Shape < ActiveRecord::Base
include PgSearch::Model
end
multisearchablepg_search_scope:tsearch (全文搜索):prefix (仅限 PostgreSQL 8.4 及更高版本):negation:dictionary:normalization:any_word:sort_only:highlight:dmetaphone (双音节 Metaphone 同音词搜索):trigram (三元组搜索):threshold:word_similarity:ranked_by (选择排名算法):order_within_rank (打破平局)PgSearch#pg_search_rank (读取记录的排名,类型为浮点数)pg_search 支持两种不同的搜索技术:多重搜索和搜索范围。
第一种方法是多重搜索,它可以将许多不同 Active Record 类的记录混合到一个全局搜索索引中,从而覆盖整个应用程序。大多数希望支持通用搜索页面的网站都会用到这个功能。
另一种技巧是搜索范围,它允许你仅针对一个 Active Record 类执行更高级的搜索。这对于构建自动完成功能或在分面搜索中筛选项目列表等操作更为有用。
在使用多重搜索之前,必须生成并运行迁移来创建 pg_search_documents 数据库表。
$ rails g pg_search:migration:multisearch
$ rake db:migrate
要将模型添加到应用程序的全局搜索索引中,请在其类定义中调用 multisearchable。
class EpicPoem < ActiveRecord::Base
include PgSearch::Model
multisearchable against: [:title, :author]
end
class Flower < ActiveRecord::Base
include PgSearch::Model
multisearchable against: :color
end
如果此模型中已存在记录,则需要重新索引此模型,才能将现有记录添加到 pg_search_documents 表中。请参阅下面的重建任务。
每当创建、更新或删除记录时,都会触发 Active Record 回调,从而在 pg_search_documents 表中创建相应的 PgSearch::Document 记录。`:against` 选项可以指定一个或多个方法,这些方法将被调用以生成记录的搜索文本。
您还可以传递一个过程或方法名称来调用,以确定是否应包含特定记录。
class Convertible < ActiveRecord::Base
include PgSearch::Model
multisearchable against: [:make, :model],
if: :available_in_red?
end
class Jalopy < ActiveRecord::Base
include PgSearch::Model
multisearchable against: [:make, :model],
if: lambda { |record| record.model_year > 1970 }
end
请注意,Proc 或方法名称是在 after_save 钩子中调用的。这意味着在使用 Time 或其他对象时应格外小心。在以下示例中,如果记录上次保存的时间早于 published_at 时间戳,则只有在该时间戳之后再次访问该记录时,它才会出现在全局搜索中。
class AntipatternExample
include PgSearch::Model
multisearchable against: [:contents],
if: :published?
def published?
published_at < Time.now
end
end
problematic_record = AntipatternExample.create!(
contents: "Using :if with a timestamp",
published_at: 10.minutes.from_now
)
problematic_record.published? # => false
PgSearch.multisearch("timestamp") # => No results
sleep 20.minutes
problematic_record.published? # => true
PgSearch.multisearch("timestamp") # => No results
problematic_record.save!
problematic_record.published? # => true
PgSearch.multisearch("timestamp") # => Includes problematic_record
有条件地更新 pg_search_documents 表。
您还可以使用 :update_if 选项传递要调用的 Proc 或方法名称,以确定是否应该更新特定记录。
请注意,Proc 或方法名称是在 after_save 钩子中调用的,因此如果您依赖 ActiveRecord 脏标志,请使用 *_previously_changed? 。
class Message < ActiveRecord::Base
include PgSearch::Model
multisearchable against: [:body],
update_if: :body_previously_changed?
end
指定要保存到 pg_search_documents 表中的其他属性
您可以指定要保存到 pg_search_documents 表中的 :additional_attributes 。例如,假设您正在索引书籍模型和文章模型,并且希望包含 author_id 属性。
首先,我们需要在创建 pg_search_documents 表的迁移中添加对作者的引用。
create_table :pg_search_documents do |t|
t.text :content
t.references :author, index: true
t.belongs_to :searchable, polymorphic: true, index: true
t.timestamps null: false
end
然后,我们可以将这个额外的属性通过 lambda 函数传递进去。
multisearchable(
against: [:title, :body],
additional_attributes: -> (article) { { author_id: article.author_id } }
)
这样一来,后续无需进行连接操作即可实现更快的搜索,例如:
PgSearch.multisearch(params['search']).where(author_id: 2)
注意:目前您必须手动调用 record.update_pg_search_document 才能将附加属性添加到 pg_search_documents 表中。
系统会自动建立两个关联。在原始记录中,有一个 has_one:pg_search_document 关联指向 PgSearch::Document 记录;在 PgSearch::Document 记录中,有一个 belongs_to:searchable 多态关联指向原始记录。
odyssey = EpicPoem.create!(title: "Odyssey", author: "Homer")
search_document = odyssey.pg_search_document #=> PgSearch::Document instance
search_document.searchable #=> #<EpicPoem id: 1, title: "Odyssey", author: "Homer">
要获取与给定查询匹配的所有记录的 PgSearch::Document 条目,请使用 PgSearch.multisearch。
odyssey = EpicPoem.create!(title: "Odyssey", author: "Homer")
rose = Flower.create!(color: "Red")
PgSearch.multisearch("Homer") #=> [#<PgSearch::Document searchable: odyssey>]
PgSearch.multisearch("Red") #=> [#<PgSearch::Document searchable: rose>]
`pgSearch.multisearch` 返回一个 `ActiveRecord::Relation` 对象,就像作用域一样,因此你可以将作用域调用链接起来。这可以与 Kaminari 等添加作用域方法的 gem 配合使用。与常规作用域一样,数据库只会在必要时接收 SQL 请求。
PgSearch.multisearch("Bertha").limit(10)
PgSearch.multisearch("Juggler").where(searchable_type: "Occupation")
PgSearch.multisearch("Alamo").page(3).per(30)
PgSearch.multisearch("Diagonal").find_each do |document|
puts document.searchable.updated_at
end
PgSearch.multisearch("Moro").reorder("").group(:searchable_type).count(:all)
PgSearch.multisearch("Square").includes(:searchable)
PgSearch.multisearch 可以使用与上述相同的选项进行配置。 pg_search_scope (详见下文)。只需在初始化程序中设置 PgSearch.multisearch_options 即可:
PgSearch.multisearch_options = {
using: [:tsearch, :trigram],
ignoring: :accents
}
如果您更改类的:against 选项,向数据库中已有记录的类添加 multisearchable,或者从类中删除 multisearchable 以便将其从索引中删除,您会发现 pg_search_documents 表可能与其它表中的实际记录不同步。
如果您以不会触发 Active Record 回调的方式修改记录,索引也可能出现不同步的情况。例如,`#update_attribute` 实例方法和 `.update_all` 类方法都会跳过回调并直接修改数据库。
要删除给定类别的所有文档,您可以直接删除所有 PgSearch::Document 记录。
PgSearch::Document.delete_by(searchable_type: "Animal")
要重新生成指定类别的文档,请运行:
PgSearch::Multisearch.rebuild(Product)
rebuild 方法会在重新生成文档之前删除给定类的所有文档。在某些情况下,这可能并不理想,例如当您使用单表继承和 searchable_type 时。 这是你的基类。你可以像这样防止 rebuild 删除你的记录:
PgSearch::Multisearch.rebuild(Product, clean_up: false)
rebuild 会在单个事务内运行。要在事务外运行,可以传递 transactional: false ,如下所示:
PgSearch::Multisearch.rebuild(Product, transactional: false)
为了方便起见,重建也可以作为 Rake 任务使用。
$ rake pg_search:multisearch:rebuild[BlogPost]
对于具有多个 pg_search_documents 表的多租户数据库,可以传递第二个可选参数来指定要使用的 PostgreSQL 模式搜索路径。以下代码会在重新索引之前将模式搜索路径设置为“my_schema”。
$ rake pg_search:multisearch:rebuild[BlogPost,my_schema]
对于支持多重搜索的模型 :against 方法直接映射到 Active Record 属性,则会运行一条高效的 SQL 语句来一次性更新 ` pg_search_documents 表。但是,如果在 :against 中调用任何动态方法,则会对正在分批索引的各个记录调用 update_pg_search_document 。
您还可以通过向模型中添加名为 rebuild_pg_search_documents 的类方法来提供重建文档的自定义实现。
class Movie < ActiveRecord::Base
belongs_to :director
def director_name
director.name
end
multisearchable against: [:name, :director_name]
# Naive approach
def self.rebuild_pg_search_documents
find_each { |record| record.update_pg_search_document }
end
# More sophisticated approach
def self.rebuild_pg_search_documents
connection.execute <<~SQL.squish
INSERT INTO pg_search_documents (searchable_type, searchable_id, content, created_at, updated_at)
SELECT 'Movie' AS searchable_type,
movies.id AS searchable_id,
CONCAT_WS(' ', movies.name, directors.name) AS content,
now() AS created_at,
now() AS updated_at
FROM movies
LEFT JOIN directors
ON directors.id = movies.director_id
SQL
end
end
注意: 如果使用 PostgreSQL 9.1 之前的版本,请将 CONCAT_WS() 函数调用替换为双竖线连接,例如 (movies.name || ' ' || directors.name) 。但是,请注意,如果连接的 任何 值为 NULL,则最终 content 值也将为 NULL,而 CONCAT_WS() 会选择性地忽略 NULL 值。
如果您需要执行大规模批量操作,例如从外部源导入大量记录,则可以暂时关闭索引以加快速度。然后,您可以使用上述方法之一离线重建搜索文档。
PgSearch.disable_multisearch do
Movie.import_from_xml_file(File.open("movies.xml"))
end
您可以使用 pg_search_scope 来构建搜索范围。第一个参数是范围名称,第二个参数是选项哈希表。唯一必需的选项是:against,它告诉 pg_search_scope 要针对哪一列或哪些列进行搜索。
要针对某一列进行搜索,请将符号作为:against 选项传递。
class BlogPost < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :search_by_title, against: :title
end
现在,我们的 BlogPost 模型中有一个名为 search_by_title 的 ActiveRecord 作用域。它接受一个参数,即搜索查询字符串。
BlogPost.create!(title: "Recent Developments in the World of Pastrami")
BlogPost.create!(title: "Prosciutto and You: A Retrospective")
BlogPost.search_by_title("pastrami") # => [#<BlogPost id: 2, title: "Recent Developments in the World of Pastrami">]
如果要搜索多个列,只需传递一个数组即可。
class Person < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :search_by_full_name, against: [:first_name, :last_name]
end
现在我们的搜索查询可以匹配其中一列或两列。
person_1 = Person.create!(first_name: "Grant", last_name: "Hill")
person_2 = Person.create!(first_name: "Hugh", last_name: "Grant")
Person.search_by_full_name("Grant") # => [person_1, person_2]
Person.search_by_full_name("Grant Hill") # => [person_1]
与 Active Record 的命名作用域类似,您可以传入一个 Proc 对象,该对象返回一个选项哈希。例如,以下作用域接受一个参数,该参数可以动态选择要搜索的列。
重要提示:返回的哈希表必须包含一个 `:query` 键。它的值不一定是动态的。您可以根据需要将其硬编码为特定值。
class Person < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :search_by_name, lambda { |name_part, query|
raise ArgumentError unless [:first, :last].include?(name_part)
{
against: name_part,
query: query
}
}
end
person_1 = Person.create!(first_name: "Grant", last_name: "Hill")
person_2 = Person.create!(first_name: "Hugh", last_name: "Grant")
Person.search_by_name :first, "Grant" # => [person_1]
Person.search_by_name :last, "Grant" # => [person_2]
可以搜索关联模型中的列。请注意,如果这样做,将无法使用数据库索引来加速搜索。但是,这提供了一种快速尝试跨模型搜索的方法。
您可以将一个哈希表传递给 `:associated_against` 选项,以设置跨关联搜索。键是关联表的名称,值的作用与另一个模型的 `:against` 选项相同。目前,不支持搜索超过一个关联表的深度。您可以通过设置一系列 `:through` 关联表来解决这个问题,从而指向所有关联表。
class Cracker < ActiveRecord::Base
has_many :cheeses
end
class Cheese < ActiveRecord::Base
end
class Salami < ActiveRecord::Base
include PgSearch::Model
belongs_to :cracker
has_many :cheeses, through: :cracker
pg_search_scope :tasty_search, associated_against: {
cheeses: [:kind, :brand],
cracker: :kind
}
end
salami_1 = Salami.create!
salami_2 = Salami.create!
salami_3 = Salami.create!
limburger = Cheese.create!(kind: "Limburger")
brie = Cheese.create!(kind: "Brie")
pepper_jack = Cheese.create!(kind: "Pepper Jack")
Cracker.create!(kind: "Black Pepper", cheeses: [brie], salami: salami_1)
Cracker.create!(kind: "Ritz", cheeses: [limburger, pepper_jack], salami: salami_2)
Cracker.create!(kind: "Graham", cheeses: [limburger], salami: salami_3)
Salami.tasty_search("pepper") # => [salami_1, salami_2]
默认情况下,pg_search_scope 使用 PostgreSQL 内置的文本搜索 。如果将:using 选项传递给 pg_search_scope,则可以选择其他搜索技术。
class Beer < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :search_name, against: :name, using: [:tsearch, :trigram, :dmetaphone]
end
如果您传递多个带有附加配置的:using 选项,以下是一个示例。
class Beer < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :search_name,
against: :name,
using: {
:trigram => {},
:dmetaphone => {},
:tsearch => { :prefix => true }
}
end
目前已实现的功能有:
PostgreSQL 内置的全文搜索支持多种语言的加权搜索、前缀搜索和词干提取。
每个可搜索列都可以被赋予“A”、“B”、“C”或“D”的权重。字母越靠前的列权重越高。因此,在下面的示例中,标题最重要,其次是副标题,最后是正文。
class NewsArticle < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :search_full_text, against: {
title: 'A',
subtitle: 'B',
content: 'C'
}
end
您还可以将权重作为数组的数组或其他任何响应 `#each` 并返回单个符号或符号及其权重的结构传递。如果您省略权重,则将使用默认值。
class NewsArticle < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :search_full_text, against: [
[:title, 'A'],
[:subtitle, 'B'],
[:content, 'C']
]
end
class NewsArticle < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :search_full_text, against: [
[:title, 'A'],
{subtitle: 'B'},
:content
]
end
PostgreSQL 的全文搜索默认匹配整个单词。但是,如果您想搜索部分单词,可以将 `:prefix` 设置为 `true`。由于这是一个 `:tsearch` 特有的选项,您应该将其直接传递给 `:tsearch`,如下例所示。
class Superhero < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :whose_name_starts_with,
against: :name,
using: {
tsearch: { prefix: true }
}
end
batman = Superhero.create name: 'Batman'
batgirl = Superhero.create name: 'Batgirl'
robin = Superhero.create name: 'Robin'
Superhero.whose_name_starts_with("Bat") # => [batman, batgirl]
PostgreSQL 的全文搜索默认匹配所有搜索词。如果您想排除某些词语,可以将 `:negation` 设置为 `true`。这样,所有以感叹号 ` ! ` 开头的词语都会从搜索结果中排除。由于这是一个 `:tsearch` 特有的选项,您应该直接将其传递给 `:tsearch`,如下例所示。
请注意,将此功能与其他搜索功能结合使用可能会产生意想不到的结果。例如,`:trigram` 搜索没有排除词的概念,因此,如果您同时使用 `:tsearch` 和 `:trigram`,仍然可能会找到包含您想要排除的词的搜索结果。
class Animal < ActiveRecord::Base
include PgSearch::Model
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]
PostgreSQL 全文搜索也支持使用多个词典进行词干提取。您可以阅读 PostgreSQL 文档 了解更多关于词典工作原理的信息。如果您使用某个语言词典,例如“english”,那么单词的不同变体(例如“jumping”和“jumped”)将会相互匹配。如果您不需要词干提取,则应选择“simple”词典,该词典不进行任何词干提取。如果您未指定词典,则将使用“simple”词典。
class BoringTweet < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :kinda_matching,
against: :text,
using: {
tsearch: {dictionary: "english"}
}
pg_search_scope :literally_matching,
against: :text,
using: {
tsearch: {dictionary: "simple"}
}
end
sleep = BoringTweet.create! text: "I snoozed my alarm for fourteen hours today. I bet I can beat that tomorrow! #sleep"
sleeping = BoringTweet.create! text: "You know what I like? Sleeping. That's what. #enjoyment"
sleeps = BoringTweet.create! text: "In the jungle, the mighty jungle, the lion sleeps #tonight"
BoringTweet.kinda_matching("sleeping") # => [sleep, sleeping, sleeps]
BoringTweet.literally_matching("sleeps") # => [sleeps]
PostgreSQL 支持多种算法对查询结果进行排名。 例如,您可能需要考虑文档的整体大小或距离。 在原文中查找多个搜索词之间的位置。此选项需要一个 整数,直接传递给 PostgreSQL。根据最新数据 根据 PostgreSQL 文档 ,支持的算法有:
0 (the default) ignores the document length
1 divides the rank by 1 + the logarithm of the document length
2 divides the rank by the document length
4 divides the rank by the mean harmonic distance between extents
8 divides the rank by the number of unique words in document
16 divides the rank by 1 + the logarithm of the number of unique words in document
32 divides the rank by itself + 1
这个整数是一个位掩码,所以如果你想组合使用不同的算法,可以将它们的结果相加。(例如,要使用算法 1、8 和 32,你需要传递 1 + 8 + 32 = 41)
class BigLongDocument < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :regular_search,
against: :text
pg_search_scope :short_search,
against: :text,
using: {
tsearch: {normalization: 2}
}
long = BigLongDocument.create!(text: "Four score and twenty years ago")
short = BigLongDocument.create!(text: "Four score")
BigLongDocument.regular_search("four score") #=> [long, short]
BigLongDocument.short_search("four score") #=> [short, long]
将此属性设置为 true 将执行搜索,该搜索将返回包含搜索词中任何单词的所有模型。
class Number < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :search_any_word,
against: :text,
using: {
tsearch: {any_word: true}
}
pg_search_scope :search_all_words,
against: :text
end
one = Number.create! text: 'one'
two = Number.create! text: 'two'
three = Number.create! text: 'three'
Number.search_any_word('one two three') # => [one, two, three]
Number.search_all_words('one two three') # => []
将此属性设置为 true 将使此功能可用于排序,但不会将其包含在查询的 WHERE 条件中。
class Person < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :search,
against: :name,
using: {
tsearch: {any_word: true},
dmetaphone: {any_word: true, sort_only: true}
}
end
exact = Person.create!(name: 'ash hines')
one_exact_one_close = Person.create!(name: 'ash heinz')
one_exact = Person.create!(name: 'ash smith')
one_close = Person.create!(name: 'leigh heinz')
Person.search('ash hines') # => [exact, one_exact_one_close, one_exact]
在 pg_search_scope 之后添加.with_pg_search_highlight,即可访问 每个对象的 pg_highlight 属性。
class Person < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :search,
against: :bio,
using: {
tsearch: {
highlight: {
StartSel: '<b>',
StopSel: '</b>',
MinWords: 123,
MaxWords: 456,
ShortWord: 4,
HighlightAll: true,
MaxFragments: 3,
FragmentDelimiter: '…'
}
}
}
end
Person.create!(:bio => "Born in rural Alberta, where the buffalo roam.")
first_match = Person.search("Alberta").with_pg_search_highlight.first
first_match.pg_search_highlight # => "Born in rural <b>Alberta</b>, where the buffalo roam."
高亮显示选项接受 ts_headline 支持的所有选项 ,并使用 PostgreSQL 的默认值。
参见 文档 有关每个选项的具体含义,请参阅相关说明。
双元音算法 是一种用于匹配发音相似但拼写差异很大的单词的算法。例如,“Geoff”和“Jeff”发音相同,因此可以匹配。目前,这并非真正的双元音算法,因为搜索时仅使用第一个元音。
目前, fuzzystrmatch 扩展程序 已提供对双重元音的支持。 必须先安装该组件才能使用此功能。此外, 要添加此扩展,您必须在数据库中安装一个实用函数。 并为此运行迁移,请运行:
$ rails g pg_search:migration:dmetaphone
$ rake db:migrate
以下示例展示了如何使用:dmetaphone。
class Word < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :that_sounds_like,
against: :spelling,
using: :dmetaphone
end
four = Word.create! spelling: 'four'
far = Word.create! spelling: 'far'
fur = Word.create! spelling: 'fur'
five = Word.create! spelling: 'five'
Word.that_sounds_like("fir") # => [four, far, fur]
三元组搜索的工作原理是统计查询语句和文本中匹配的三字母子字符串(或“三元组”)的数量。例如,字符串“Lorem ipsum”可以拆分为以下三元组:
[" Lo", "Lor", "ore", "rem", "em ", "m i", " ip", "ips", "psu", "sum", "um ", "m "]
三元组搜索即使在查询或文本中存在拼写错误或笔误的情况下也能正常工作。
三元组支持目前已作为以下部分提供: 使用此功能之前必须安装 pg_trgm 扩展 。
class Website < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :kinda_spelled_like,
against: :name,
using: :trigram
end
yahooo = Website.create! name: "Yahooo!"
yohoo = Website.create! name: "Yohoo!"
gogle = Website.create! name: "Gogle"
facebook = Website.create! name: "Facebook"
Website.kinda_spelled_like("Yahoo!") # => [yahooo, yohoo]
默认情况下,三元组搜索会查找相似度至少为 0.3 的记录。 使用 pg_trgm 的计算方法。您也可以根据需要指定自定义阈值。 数字越大,匹配越严格,因此返回的结果越少。数字越小。 匹配方式更加宽松,允许更多结果出现。请注意,设置卦象时需要设置一个卦象。 阈值会强制执行表扫描,因为派生查询使用了该阈值。 similarity() 函数代替 % 运算符。
class Vegetable < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :strictly_spelled_like,
against: :name,
using: {
trigram: {
threshold: 0.5
}
}
pg_search_scope :roughly_spelled_like,
against: :name,
using: {
trigram: {
threshold: 0.1
}
}
end
cauliflower = Vegetable.create! name: "cauliflower"
Vegetable.roughly_spelled_like("couliflower") # => [cauliflower]
Vegetable.strictly_spelled_like("couliflower") # => [cauliflower]
Vegetable.roughly_spelled_like("collyflower") # => [cauliflower]
Vegetable.strictly_spelled_like("collyflower") # => []
允许您匹配更长字符串中的单词。默认情况下,三元组搜索使用 % 或 similarity() 作为相似度值。将 word_similarity 设置为 true 可改为使用 <% 和 word_similarity 。这将使三元组搜索使用查询词与相似度最高的词之间的相似度。
class Sentence < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :similarity_like,
against: :name,
using: {
trigram: {
word_similarity: true
}
}
pg_search_scope :word_similarity_like,
against: :name,
using: [:trigram]
end
sentence = Sentence.create! name: "Those are two words."
Sentence.similarity_like("word") # => []
Sentence.word_similarity_like("word") # => [sentence]
有时在进行组合不同特征的查询时,您可能只想针对某些字段的特定特征进行搜索。例如,您可能只想针对较短的字段进行三元组搜索,这样就不需要过度降低阈值。您可以使用“only”选项指定要搜索的字段:
class Image < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :combined_search,
against: [:file_name, :short_description, :long_description]
using: {
tsearch: { dictionary: 'english' },
trigram: {
only: [:file_name, :short_description]
}
}
end
现在,您可以使用以下命令成功检索文件名为“image_foo.jpg”且长描述为“此描述太长,以至于任何合理的阈值限制都会导致三元语法搜索失败”的图像:
Image.combined_search('reasonable') # found with tsearch
Image.combined_search('foo') # found with trigram
大多数情况下,您在搜索时需要忽略重音符号。这样,当您使用查询“pinata”进行搜索时,就能找到像“piñata”这样的词。如果您将 pg_search_scope 设置为忽略重音符号,它将忽略搜索文本和查询词中的重音符号。
忽略重音符号需要使用 unaccent 扩展程序 ,必须先安装该扩展程序才能使用此功能。
class SpanishQuestion < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :gringo_search,
against: :word,
ignoring: :accents
end
what = SpanishQuestion.create(word: "Qué")
how_many = SpanishQuestion.create(word: "Cuánto")
how = SpanishQuestion.create(word: "Cómo")
SpanishQuestion.gringo_search("Que") # => [what]
SpanishQuestion.gringo_search("Cüåñtô") # => [how_many]
高级用户可能希望为 pg_search 生成的表达式添加索引。遗憾的是,此扩展提供的 unaccent 函数(截至 PostgreSQL 9.1)无法建立索引。因此,您可能需要编写自己的包装函数并使用它。您可以通过调用以下代码进行配置,例如在初始化程序中调用。
PgSearch.unaccent_function = "my_unaccent"
PostgreSQL 允许您使用 tsvector 类型的列进行搜索,而不是使用表达式;这大大加快了搜索速度,因为它卸载了 tsquery 所评估的 tsvector 的创建过程。
要使用此功能,您需要执行以下几个步骤:
pg_search_scope :fast_content_search,
against: :content,
using: {
dmetaphone: {
tsvector_column: 'tsvector_content_dmetaphone'
},
tsearch: {
dictionary: 'english',
tsvector_column: 'tsvector_content_tsearch'
},
trigram: {} # trigram does not use tsvectors
}
请注意,只有当搜索类型不存在 tsvector_column 时,才会使用:against 列。
可以同时搜索多个 tsvector。如果您想要维护多个搜索范围,但又不想为每个范围维护单独的 tsvector,这将非常有用。例如:
pg_search_scope :search_title,
against: :title,
using: {
tsearch: {
tsvector_column: "title_tsvector"
}
}
pg_search_scope :search_body,
against: :body,
using: {
tsearch: {
tsvector_column: "body_tsvector"
}
}
pg_search_scope :search_title_and_body,
against: [:title, :body],
using: {
tsearch: {
tsvector_column: ["title_tsvector", "body_tsvector"]
}
}
默认情况下,pg_search 根据可搜索文本与查询之间的:tsearch 相似度对结果进行排名。要使用不同的排名算法,您可以向 pg_search_scope 传递:ranked_by 选项。
pg_search_scope :search_by_tsearch_but_rank_by_trigram,
against: :title,
using: [:tsearch],
ranked_by: ":trigram"
请注意,`:ranked_by` 使用字符串来表示排名表达式。这允许更复杂的可能性。诸如 `:tsearch`、`:trigram` 和 `:dmetaphone` 之类的字符串会自动展开为相应的 SQL 表达式。
# Weighted ranking to balance multiple approaches
ranked_by: ":dmetaphone + (0.25 * :trigram)"
# A more complex example, where books.num_pages is an integer column in the table itself
ranked_by: "(books.num_pages * :trigram) + (:tsearch / 2.0)"
当 ORDER BY 子句中多个记录具有相同的值时,PostgreSQL 不保证顺序的一致性。这可能会导致分页出现问题。例如,假设有 12 条记录的排名值都相同。如果您使用 Kaminari 或 #PostgreSQL 等分页库,则可能会出现这种情况。 如果 will_paginate 以每页 10 条记录的形式返回结果,那么您应该会看到第 1 页显示 10 条记录,而剩余的 2 条记录则显示在下一页的顶部,排在排名较低的结果之前。
但由于记录顺序不一致,PostgreSQL 可能会在不同的 SQL 语句中重新排列这 12 条记录的顺序。因此,您可能会在第 2 页看到与第 1 页相同的一些记录,同样地,也可能有些记录根本不会出现。
pg_search 通过在 ORDER BY 子句中添加第二个表达式来解决这个问题,该表达式位于上述:ranked_by 表达式之后。默认情况下,排名依据是 ID 升序排列。
ORDER BY [complicated :ranked_by expression...], id ASC
这可能并不适合您的应用场景,尤其当您不希望旧记录的排名高于新记录时。通过传递 `:order_within_rank` 参数,您可以指定一个替代的排名判定表达式。一个常见的例子是按 `updated_at` 降序排列,将最近更新的记录排在第一位。
pg_search_scope :search_and_break_ties_by_latest_update,
against: [:title, :content],
order_within_rank: "blog_posts.updated_at DESC"
查看特定记录的排名可能很有用或很有趣。这有助于调试某个记录排名高于其他记录的原因。您还可以使用它向应用程序的最终用户显示某种相关性值。
要检索排名,请在作用域上调用 .with_pg_search_rank ,然后调用 对返回的记录使用 .pg_search_rank 。
shirt_brands = ShirtBrand.search_by_name("Penguin").with_pg_search_rank
shirt_brands[0].pg_search_rank #=> 0.0759909
shirt_brands[1].pg_search_rank #=> 0.0607927
每个 PgSearch 作用域都会为搜索排名生成一个命名子查询。如果链接多个作用域,PgSearch 将为每个作用域生成一个排名查询,因此排名查询必须具有唯一的名称。如果需要引用排名查询(例如在 GROUP BY 子句中),可以使用 PgScope::Configuration.alias 方法,并通过传递被查询表的名称来重新生成子查询名称。
shirt_brands = ShirtBrand.search_by_name("Penguin")
.joins(:shirt_sizes)
.group("shirt_brands.id, #{PgSearch::Configuration.alias('shirt_brands')}.rank")
如果没有 Texticle(现已更名)的启发,#PgSearch 就不会存在。 textacular )。感谢 Aaron Patterson 提供原版,也感谢 Casebook PBC ( https://www.casebook.net ) 将其分享给社区!
请阅读我们的 贡献指南 。
我们还有一个 Google 群组 用于讨论 #pg_search 和其他 Casebook PBC 开源项目。
版权所有 © 2010–2022 Casebook PBC 。根据 MIT 许可证授权,请参阅 LICENSE 文件。 #搜索引擎 #搜索
Share with your followers.
Reply