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 許可證獲得許可,請參閱許可證文件。