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
multisearchable
pg_search_scope
:tsearch
(全文搜索):prefix
(仅限 PostgreSQL 8.4 及更高版本):negation
:dictionary
:normalization
:any_word
:sort_only
:highlight
:dmetaphone
(双元音位相似搜索):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 表上的附加属性
您可以指定:additional_attributes
保存在pg_search_documents
表中。例如,也许您正在为书籍模型和文章模型建立索引,并希望包含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 )
可以使用与pg_search_scope
相同的选项来配置 PgSearch.multisearch(下面将更详细地解释)。只需在初始值设定项中设置 PgSearch.multisearch_options 即可:
PgSearch . multisearch_options = {
using : [ :tsearch , :trigram ] ,
ignoring : :accents
}
如果您更改类上的 :against 选项,将 multisearchable 添加到数据库中已有记录的类,或者从类中删除 multisearchable 以将其从索引中删除,您会发现 pg_search_documents 表可能会变得out-与其他表中的实际记录不同步。
如果您以不触发 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
注意:如果使用 9.1 之前的 PostgreSQL,请将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]
可以搜索关联模型上的列。请注意,如果这样做,将无法加快使用数据库索引的搜索速度。但是,它作为尝试跨模型搜索的快速方法受到支持。
您可以将哈希传递到 :linked_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”)将相互匹配。如果您不想词干,则应该选择不进行任何词干提取的“简单”词典。如果您不指定字典,则将使用“简单”字典。
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>' ,
MaxWords : 123 ,
MinWords : 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 的默认值。
有关每个选项含义的详细信息,请参阅文档。
Double Metaphone 是一种匹配听起来相似的单词的算法,即使它们的拼写非常不同。例如,“Geoff”和“Jeff”听起来相同,因此匹配。目前,这不是真正的双变音位,因为仅使用第一个变音位进行搜索。
双 Metaphone 支持目前作为 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 "]
即使查询或文本中存在拼写错误,三元组搜索也具有一定的工作能力。
Trigram 支持目前作为 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]
默认情况下,三元组搜索使用 pg_trgm 的计算查找相似度至少为 0.3 的记录。如果您愿意,您可以指定自定义阈值。数字越大,匹配越严格,因此返回的结果越少。数字越小,匹配越宽松,从而获得更多结果。请注意,设置三元组阈值将强制进行表扫描,因为派生查询使用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
现在,您可以成功检索具有 file_name: 'image_foo.jpg' 和 long_description: 'This description is so long that it will make a trigram search failed any Reasonable Threshold Limit' 的图像:
Image . combined_search ( 'reasonable' ) # found with tsearch
Image . combined_search ( 'foo' ) # found with trigram
大多数时候,您在搜索时会希望忽略重音符号。这使得在使用查询“pinata”进行搜索时可以找到诸如“piñata”之类的单词。如果您将 pg_search_scope 设置为忽略重音,它将忽略可搜索文本和查询词中的重音。
忽略重音使用非重音扩展,在使用此功能之前必须安装该扩展。
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 生成的表达式添加索引。不幸的是,此扩展提供的非重音符号函数不可索引(从 PostgreSQL 9.1 开始)。因此,您可能想编写自己的包装函数并使用它。这可以通过调用以下代码(也许在初始化程序中)来配置。
PgSearch . unaccent_function = "my_unaccent"
PostgreSQL 允许您搜索 tsvector 类型的列,而不是使用表达式;这极大地加快了搜索速度,因为它减轻了 tsquery 评估所依据的 tsvector 的创建工作。
要使用此功能,您需要执行以下操作:
创建一个您想要搜索的 tsvector 类型的列。如果您想使用多种搜索方法(例如 tsearch 和 dmetaphone)进行搜索,则每种方法都需要一列。
创建一个触发器函数,该函数将使用适合该搜索类型的表达式来更新列。请参阅:文本搜索触发器的 PostgreSQL 文档
如果表中已有任何数据,请使用触发器函数使用的表达式更新新创建的 tsvector 列。
将选项添加到 pg_search_scope,例如:
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 相似度对结果进行排名。要使用不同的排名算法,您可以将 :ranked_by 选项传递给 pg_search_scope。
pg_search_scope :search_by_tsearch_but_rank_by_trigram ,
against : :title ,
using : [ :tsearch ] ,
ranked_by : ":trigram"
请注意, :ranked_by 使用 String 来表示排名表达式。这允许更复杂的可能性。 “: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 或 will_paginate)以 10 页的形式返回结果,那么您将期望在第 1 页上看到 10 条记录,而其余 2 条记录位于下一页的顶部,位于下一页之前 -排名结果。
但由于没有一致的顺序,PostgreSQL 可能会选择在不同的 SQL 语句之间重新排列这 12 条记录的顺序。您最终可能会在第 2 页上获得一些与第 1 页相同的记录,同样也可能有一些记录根本不显示。
pg_search 通过在上面解释的 :ranked_by 表达式之后向 ORDER 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(现已更名为 textaulous)的灵感,PgSearch 就不可能实现。感谢 Aaron Patterson 提供原始版本,并感谢 Casebook PBC (https://www.casebook.net) 向社区赠送了它!
请阅读我们的贡献指南。
我们还有一个 Google Group 用于讨论 pg_search 和其他 Casebook PBC 开源项目。
版权所有 © 2010–2022 中国人民银行案例手册。根据 MIT 许可证获得许可,请参阅许可证文件。