PgSearch erstellt benannte Bereiche, die die Volltextsuche von PostgreSQL nutzen.
Lesen Sie den Blogbeitrag zur Einführung von PgSearch unter https://tanzu.vmware.com/content/blog/pg-search-how-i-learned-to-stop-worrying-and-love-postgresql-full-text-search
$ gem install pg_search
oder fügen Sie diese Zeile zu Ihrer Gemfile hinzu:
gem 'pg_search'
Zusätzlich zur Installation und Anforderung des Gems möchten Sie möglicherweise die PgSearch-Rake-Aufgaben in Ihr Rakefile aufnehmen. Dies ist für Rails-Projekte nicht notwendig, die die Rake-Aufgaben über ein Railtie erhalten.
load "pg_search/tasks.rb"
Um PgSearch zu einem Active Record-Modell hinzuzufügen, schließen Sie einfach das PgSearch-Modul ein.
class Shape < ActiveRecord :: Base
include PgSearch :: Model
end
multisearchable
pg_search_scope
:tsearch
(Volltextsuche):prefix
(nur PostgreSQL 8.4 und neuer):negation
:dictionary
:normalization
:any_word
:sort_only
:highlight
:dmetaphone
(Double Metaphone Soundalike-Suche):trigram
(Trigrammsuche):threshold
:word_similarity
:ranked_by
(Auswahl eines Ranking-Algorithmus):order_within_rank
(Unentschieden brechen)PgSearch#pg_search_rank
(Rang eines Datensatzes als Float lesen)pg_search unterstützt zwei verschiedene Suchtechniken: Mehrfachsuche und Suchbereiche.
Die erste Technik ist die Mehrfachsuche, bei der Datensätze aus vielen verschiedenen Active Record-Klassen in einem globalen Suchindex für Ihre gesamte Anwendung zusammengeführt werden können. Die meisten Websites, die eine generische Suchseite unterstützen möchten, werden diese Funktion nutzen wollen.
Die andere Technik sind Suchbereiche, mit denen Sie eine erweiterte Suche nur für eine Active Record-Klasse durchführen können. Dies ist nützlicher zum Erstellen von Dingen wie Autovervollständigern oder zum Filtern einer Liste von Elementen in einer Facettensuche.
Bevor Sie die Mehrfachsuche verwenden, müssen Sie eine Migration generieren und ausführen, um die Datenbanktabelle pg_search_documents zu erstellen.
$ rails g pg_search:migration:multisearch
$ rake db:migrate
Um ein Modell zum globalen Suchindex für Ihre Anwendung hinzuzufügen, rufen Sie multisearchable in seiner Klassendefinition auf.
class EpicPoem < ActiveRecord :: Base
include PgSearch :: Model
multisearchable against : [ :title , :author ]
end
class Flower < ActiveRecord :: Base
include PgSearch :: Model
multisearchable against : :color
end
Wenn dieses Modell bereits über vorhandene Datensätze verfügt, müssen Sie dieses Modell neu indizieren, um vorhandene Datensätze in die Tabelle pg_search_documents zu übernehmen. Siehe die Wiederherstellungsaufgabe unten.
Immer wenn ein Datensatz erstellt, aktualisiert oder zerstört wird, wird ein Active Record-Rückruf ausgelöst, der zur Erstellung eines entsprechenden PgSearch::Document-Datensatzes in der Tabelle pg_search_documents führt. Die Option :against kann eine oder mehrere Methoden sein, die für den Datensatz aufgerufen werden, um seinen Suchtext zu generieren.
Sie können dem Aufruf auch einen Prozess- oder Methodennamen übergeben, um zu bestimmen, ob ein bestimmter Datensatz einbezogen werden soll oder nicht.
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
Beachten Sie, dass der Proc- oder Methodenname in einem after_save-Hook aufgerufen wird. Das bedeutet, dass Sie bei der Verwendung von Zeit oder anderen Objekten vorsichtig sein sollten. Wenn im folgenden Beispiel der Datensatz zuletzt vor dem Zeitstempel „public_at“ gespeichert wurde, wird er erst dann in der globalen Suche aufgeführt, wenn er nach dem Zeitstempel erneut berührt wird.
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 bedingt aktualisieren
Sie können auch die Option :update_if
verwenden, um einen Prozess- oder Methodennamen zu übergeben, der aufgerufen werden soll, um zu bestimmen, ob ein bestimmter Datensatz aktualisiert werden soll oder nicht.
Beachten Sie, dass der Proc- oder Methodenname in einem after_save
Hook aufgerufen wird. Wenn Sie sich also auf ActiveRecord-Dirty-Flags verlassen, verwenden Sie *_previously_changed?
.
class Message < ActiveRecord :: Base
include PgSearch :: Model
multisearchable against : [ :body ] ,
update_if : :body_previously_changed?
end
Geben Sie zusätzliche Attribute an, die in der Tabelle pg_search_documents gespeichert werden sollen
Sie können :additional_attributes
angeben, die in der Tabelle pg_search_documents
gespeichert werden sollen. Vielleicht indizieren Sie beispielsweise ein Buchmodell und ein Artikelmodell und möchten die „author_id“ einschließen.
Zuerst müssen wir der Migration einen Verweis auf den Autor hinzufügen, um unsere pg_search_documents
-Tabelle zu erstellen.
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
Dann können wir dieses zusätzliche Attribut in einem Lambda einsenden
multisearchable (
against : [ :title , :body ] ,
additional_attributes : -> ( article ) { { author_id : article . author_id } }
)
Dies ermöglicht viel schnellere Suchvorgänge ohne spätere Verknüpfungen, indem Sie beispielsweise Folgendes tun:
PgSearch . multisearch ( params [ 'search' ] ) . where ( author_id : 2 )
HINWEIS: Sie müssen derzeit record.update_pg_search_document
manuell aufrufen, damit das zusätzliche Attribut in die Tabelle pg_search_documents aufgenommen wird
Es werden automatisch zwei Assoziationen erstellt. Im Originaldatensatz gibt es eine has_one :pg_search_document-Zuordnung, die auf den PgSearch::Document-Datensatz verweist, und im PgSearch::Document-Datensatz gibt es eine Zugehörigkeit_to :searchable polymorphe Zuordnung, die zurück auf den Originaldatensatz verweist.
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">
Um die PgSearch::Document-Einträge für alle Datensätze abzurufen, die einer bestimmten Abfrage entsprechen, verwenden Sie 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 gibt genau wie Bereiche eine ActiveRecord::Relation zurück, sodass Sie Bereichsaufrufe bis zum Ende verketten können. Dies funktioniert mit Gems wie Kaminari, die Scope-Methoden hinzufügen. Genau wie bei regulären Bereichen empfängt die Datenbank SQL-Anfragen nur bei Bedarf.
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 kann mit denselben Optionen wie pg_search_scope
konfiguriert werden (weiter unten ausführlicher erläutert). Legen Sie einfach die PgSearch.multisearch_options in einem Initialisierer fest:
PgSearch . multisearch_options = {
using : [ :tsearch , :trigram ] ,
ignoring : :accents
}
Wenn Sie die Option :against für eine Klasse ändern, multisearchable zu einer Klasse hinzufügen, die bereits über Datensätze in der Datenbank verfügt, oder multisearchable aus einer Klasse entfernen, um sie aus dem Index zu entfernen, werden Sie feststellen, dass die Tabelle pg_search_documents veraltet sein könnte. nicht mit den tatsächlichen Datensätzen in Ihren anderen Tabellen synchronisiert.
Der Index kann auch nicht mehr synchron sein, wenn Sie Datensätze so ändern, dass keine Rückrufe für aktive Datensätze ausgelöst werden. Beispielsweise überspringen die Instanzmethode #update_attribute und die Klassenmethode .update_all beide Rückrufe und ändern die Datenbank direkt.
Um alle Dokumente für eine bestimmte Klasse zu entfernen, können Sie einfach alle PgSearch::Document-Datensätze löschen.
PgSearch :: Document . delete_by ( searchable_type : "Animal" )
Führen Sie Folgendes aus, um die Dokumente für eine bestimmte Klasse neu zu generieren:
PgSearch :: Multisearch . rebuild ( Product )
Die rebuild
-Methode löscht alle Dokumente für die angegebene Klasse, bevor sie neu generiert wird. In manchen Situationen ist dies möglicherweise nicht wünschenswert, beispielsweise wenn Sie die Vererbung einer einzelnen Tabelle verwenden und searchable_type
Ihre Basisklasse ist. Sie können verhindern, dass Ihre Datensätze durch rebuild
wie folgt gelöscht werden:
PgSearch :: Multisearch . rebuild ( Product , clean_up : false )
rebuild
wird innerhalb einer einzelnen Transaktion ausgeführt. Um außerhalb einer Transaktion ausgeführt zu werden, können Sie transactional: false
wie folgt übergeben:
PgSearch :: Multisearch . rebuild ( Product , transactional : false )
Der Einfachheit halber ist der Neuaufbau auch als Rake-Aufgabe verfügbar.
$ rake pg_search:multisearch:rebuild[BlogPost]
Ein zweites optionales Argument kann übergeben werden, um den zu verwendenden PostgreSQL-Schema-Suchpfad für mehrinstanzenfähige Datenbanken anzugeben, die über mehrere pg_search_documents-Tabellen verfügen. Im Folgenden wird der Schemasuchpfad vor der Neuindizierung auf „my_schema“ festgelegt.
$ rake pg_search:multisearch:rebuild[BlogPost,my_schema]
Für Modelle, die mehrfach durchsuchbar sind :against
Methoden, die direkt Active Record-Attributen zugeordnet sind), wird eine effiziente einzelne SQL-Anweisung ausgeführt, um die Tabelle pg_search_documents
auf einmal zu aktualisieren. Wenn Sie jedoch dynamische Methoden in :against
aufrufen, wird update_pg_search_document
für die einzelnen Datensätze aufgerufen, die stapelweise indiziert werden.
Sie können auch eine benutzerdefinierte Implementierung zum Neuerstellen der Dokumente bereitstellen, indem Sie Ihrem Modell eine Klassenmethode namens rebuild_pg_search_documents
hinzufügen.
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
Hinweis: Wenn Sie PostgreSQL vor 9.1 verwenden, ersetzen Sie den Funktionsaufruf CONCAT_WS()
durch eine Doppelpipe-Verkettung, z. (movies.name || ' ' || directors.name)
. Beachten Sie jedoch, dass, wenn einer der verbundenen Werte NULL ist, der endgültige content
ebenfalls NULL ist, wohingegen CONCAT_WS()
NULL-Werte selektiv ignoriert.
Wenn Sie einen großen Massenvorgang ausführen müssen, beispielsweise den Import vieler Datensätze aus einer externen Quelle, möchten Sie möglicherweise die Arbeit beschleunigen, indem Sie die Indizierung vorübergehend deaktivieren. Anschließend können Sie eine der oben genannten Techniken verwenden, um die Suchdokumente offline neu zu erstellen.
PgSearch . disable_multisearch do
Movie . import_from_xml_file ( File . open ( "movies.xml" ) )
end
Sie können pg_search_scope verwenden, um einen Suchbereich zu erstellen. Der erste Parameter ist ein Bereichsname und der zweite Parameter ist ein Options-Hash. Die einzige erforderliche Option ist :against, die pg_search_scope mitteilt, nach welcher Spalte oder welchen Spalten gesucht werden soll.
Um nach einer Spalte zu suchen, übergeben Sie ein Symbol als Option :against.
class BlogPost < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_by_title , against : :title
end
Wir haben jetzt einen ActiveRecord-Bereich mit dem Namen „search_by_title“ in unserem BlogPost-Modell. Es benötigt einen Parameter, eine Suchabfragezeichenfolge.
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">]
Übergeben Sie einfach ein Array, wenn Sie mehr als eine Spalte durchsuchen möchten.
class Person < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_by_full_name , against : [ :first_name , :last_name ]
end
Jetzt kann unsere Suchanfrage mit einer oder beiden Spalten übereinstimmen.
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]
Genau wie bei benannten Active Record-Bereichen können Sie ein Proc-Objekt übergeben, das einen Hash von Optionen zurückgibt. Der folgende Bereich benötigt beispielsweise einen Parameter, der dynamisch auswählt, nach welcher Spalte gesucht werden soll.
Wichtig: Der zurückgegebene Hash muss einen :query-Schlüssel enthalten. Sein Wert muss nicht unbedingt dynamisch sein. Wenn Sie möchten, können Sie es auch fest auf einen bestimmten Wert codieren.
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]
Es ist möglich, Spalten nach zugehörigen Modellen zu durchsuchen. Beachten Sie, dass es in diesem Fall nicht möglich ist, die Suche mit Datenbankindizes zu beschleunigen. Es wird jedoch als schnelle Möglichkeit zum Ausprobieren der modellübergreifenden Suche unterstützt.
Sie können einen Hash an die Option :associated_against übergeben, um die Suche nach Assoziationen einzurichten. Die Schlüssel sind die Namen der Assoziationen und der Wert funktioniert genau wie eine :against-Option für das andere Modell. Derzeit wird die Suche tiefer als eine Assoziation entfernt nicht unterstützt. Sie können dies umgehen, indem Sie eine Reihe von :through-Assoziationen einrichten, die durchgehend zeigen.
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]
Standardmäßig verwendet pg_search_scope die integrierte PostgreSQL-Textsuche. Wenn Sie die Option :using an pg_search_scope übergeben, können Sie alternative Suchtechniken auswählen.
class Beer < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_name , against : :name , using : [ :tsearch , :trigram , :dmetaphone ]
end
Hier ist ein Beispiel, wenn Sie mehrere :using-Optionen mit zusätzlichen Konfigurationen übergeben.
class Beer < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_name ,
against : :name ,
using : {
:trigram => { } ,
:dmetaphone => { } ,
:tsearch => { :prefix => true }
}
end
Die derzeit implementierten Funktionen sind
Die integrierte Volltextsuche von PostgreSQL unterstützt Gewichtung, Präfixsuche und Wortstammerkennung in mehreren Sprachen.
Jeder durchsuchbaren Spalte kann eine Gewichtung von „A“, „B“, „C“ oder „D“ zugewiesen werden. Spalten mit früheren Buchstaben werden höher gewichtet als solche mit späteren Buchstaben. Im folgenden Beispiel ist also der Titel am wichtigsten, gefolgt vom Untertitel und schließlich dem Inhalt.
class NewsArticle < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_full_text , against : {
title : 'A' ,
subtitle : 'B' ,
content : 'C'
}
end
Sie können die Gewichtungen auch als Array von Arrays oder als jede andere Struktur übergeben, die auf #each reagiert und entweder ein einzelnes Symbol oder ein Symbol und eine Gewichtung ergibt. Wenn Sie das Gewicht weglassen, wird ein Standardwert verwendet.
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
Die Volltextsuche von PostgreSQL sucht standardmäßig nach ganzen Wörtern. Wenn Sie jedoch nach Teilwörtern suchen möchten, können Sie :prefix auf true setzen. Da es sich um eine :tsearch-spezifische Option handelt, sollten Sie sie direkt an :tsearch übergeben, wie im folgenden Beispiel gezeigt.
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]
Die Volltextsuche von PostgreSQL stimmt standardmäßig mit allen Suchbegriffen überein. Wenn Sie bestimmte Wörter ausschließen möchten, können Sie :negation auf true setzen. Dann ist jeder Begriff, der mit einem Ausrufezeichen beginnt !
werden aus den Ergebnissen ausgeschlossen. Da es sich um eine :tsearch-spezifische Option handelt, sollten Sie sie direkt an :tsearch übergeben, wie im folgenden Beispiel gezeigt.
Beachten Sie, dass die Kombination mit anderen Suchfunktionen zu unerwarteten Ergebnissen führen kann. Beispielsweise gibt es bei :trigram-Suchen kein Konzept für ausgeschlossene Begriffe. Wenn Sie daher sowohl :tsearch als auch :trigram gleichzeitig verwenden, finden Sie möglicherweise immer noch Ergebnisse, die den Begriff enthalten, den Sie ausschließen wollten.
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]
Die PostgreSQL-Volltextsuche unterstützt auch mehrere Wörterbücher für die Wortstammerkennung. Weitere Informationen zur Funktionsweise von Wörterbüchern finden Sie in der PostgreSQL-Dokumentation. Wenn Sie eines der Sprachwörterbücher verwenden, z. B. „english“, dann stimmen Wortvarianten (z. B. „jumping“ und „jumped“) überein. Wenn Sie keine Wortstammerkennung wünschen, sollten Sie das „einfache“ Wörterbuch wählen, das keine Wortstammerkennung durchführt. Wenn Sie kein Wörterbuch angeben, wird das „einfache“ Wörterbuch verwendet.
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 unterstützt mehrere Algorithmen zum Einordnen von Ergebnissen anhand von Abfragen. Beispielsweise möchten Sie möglicherweise die Gesamtgröße des Dokuments oder den Abstand zwischen mehreren Suchbegriffen im Originaltext berücksichtigen. Diese Option akzeptiert eine Ganzzahl, die direkt an PostgreSQL übergeben wird. Laut der neuesten PostgreSQL-Dokumentation sind die unterstützten Algorithmen:
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
Diese Ganzzahl ist eine Bitmaske. Wenn Sie also Algorithmen kombinieren möchten, können Sie deren Zahlen addieren. (Um beispielsweise die Algorithmen 1, 8 und 32 zu verwenden, würden Sie 1 + 8 + 32 = 41 übergeben)
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]
Wenn Sie dieses Attribut auf „true“ setzen, wird eine Suche durchgeführt, die alle Modelle zurückgibt, die ein beliebiges Wort in den Suchbegriffen enthalten.
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' ) # => []
Wenn Sie dieses Attribut auf „true“ setzen, wird diese Funktion für die Sortierung verfügbar gemacht, sie wird jedoch nicht in die WHERE-Bedingung der Abfrage einbezogen.
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]
Durch Hinzufügen von .with_pg_search_highlight nach dem pg_search_scope können Sie für jedes Objekt auf das Attribut pg_highlight
zugreifen.
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."
Die Highlight-Option akzeptiert alle von ts_headline unterstützten Optionen und verwendet die Standardeinstellungen von PostgreSQL.
Einzelheiten zur Bedeutung der einzelnen Optionen finden Sie in der Dokumentation.
Double Metaphone ist ein Algorithmus zum Zuordnen von Wörtern, die gleich klingen, auch wenn sie sehr unterschiedlich geschrieben sind. „Geoff“ und „Jeff“ klingen beispielsweise identisch und stimmen daher überein. Derzeit handelt es sich hierbei nicht um ein echtes Doppelmetaphon, da nur das erste Metaphon für die Suche verwendet wird.
Die Unterstützung von Double Metaphone ist derzeit als Teil der Fuzzystrmatch-Erweiterung verfügbar, die installiert werden muss, bevor diese Funktion verwendet werden kann. Zusätzlich zur Erweiterung müssen Sie eine Hilfsfunktion in Ihrer Datenbank installieren. Um hierfür eine Migration zu generieren und auszuführen, führen Sie Folgendes aus:
$ rails g pg_search:migration:dmetaphone
$ rake db:migrate
Das folgende Beispiel zeigt die Verwendung von :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]
Bei der Trigrammsuche wird gezählt, wie viele aus drei Buchstaben bestehende Teilzeichenfolgen (oder „Trigramme“) zwischen der Abfrage und dem Text übereinstimmen. Beispielsweise kann die Zeichenfolge „Lorem ipsum“ in die folgenden Trigramme aufgeteilt werden:
[" Lo", "Lor", "ore", "rem", "em ", "m i", " ip", "ips", "psu", "sum", "um ", "m "]
Die Trigram-Suche funktioniert auch bei Tippfehlern und Rechtschreibfehlern in der Abfrage oder im Text.
Trigram-Unterstützung ist derzeit als Teil der pg_trgm-Erweiterung verfügbar, die installiert werden muss, bevor diese Funktion verwendet werden kann.
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]
Standardmäßig werden bei Trigrammsuchen mithilfe der Berechnungen von pg_trgm Datensätze gefunden, die eine Ähnlichkeit von mindestens 0,3 aufweisen. Sie können bei Bedarf einen benutzerdefinierten Schwellenwert angeben. Höhere Zahlen stimmen genauer überein und liefern daher weniger Ergebnisse. Niedrigere Zahlen stimmen freizügiger überein und führen zu mehr Ergebnissen. Bitte beachten Sie, dass das Festlegen eines Trigramm-Schwellenwerts einen Tabellenscan erzwingt, da die abgeleitete Abfrage die Funktion similarity()
anstelle des %
-Operators verwendet.
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" ) # => []
Ermöglicht das Zuordnen von Wörtern in längeren Zeichenfolgen. Standardmäßig verwenden Trigramm-Suchen %
oder similarity()
als Ähnlichkeitswert. Setzen Sie word_similarity
auf true
um sich stattdessen für „ <%
und word_similarity
zu entscheiden. Dies führt dazu, dass bei der Trigrammsuche die Ähnlichkeit des Suchbegriffs und des Wortes mit der größten Ähnlichkeit verwendet wird.
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]
Manchmal möchten Sie bei Abfragen, die verschiedene Merkmale kombinieren, möglicherweise nur nach einigen Feldern mit bestimmten Merkmalen suchen. Beispielsweise möchten Sie möglicherweise nur eine Trigrammsuche für die kürzeren Felder durchführen, damit Sie den Schwellenwert nicht übermäßig reduzieren müssen. Mit der Option „nur“ können Sie angeben, welche Felder vorhanden sind:
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
Jetzt können Sie erfolgreich ein Bild mit einem Dateinamen abrufen: „image_foo.jpg“ und einer langen_Beschreibung: „Diese Beschreibung ist so lang, dass eine Trigrammsuche jeden angemessenen Schwellenwert überschreiten würde“ mit:
Image . combined_search ( 'reasonable' ) # found with tsearch
Image . combined_search ( 'foo' ) # found with trigram
In den meisten Fällen möchten Sie Akzentzeichen bei der Suche ignorieren. Dadurch ist es möglich, bei der Suche mit der Suchanfrage „Pinata“ Wörter wie „Piñata“ zu finden. Wenn Sie pg_search_scope so einstellen, dass Akzente ignoriert werden, werden Akzente sowohl im durchsuchbaren Text als auch in den Abfragebegriffen ignoriert.
Beim Ignorieren von Akzenten wird die Unaccent-Erweiterung verwendet, die installiert werden muss, bevor diese Funktion verwendet werden kann.
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]
Fortgeschrittene Benutzer möchten möglicherweise Indizes für die Ausdrücke hinzufügen, die pg_search generiert. Leider ist die von dieser Erweiterung bereitgestellte Unaccent-Funktion nicht indizierbar (ab PostgreSQL 9.1). Daher möchten Sie möglicherweise Ihre eigene Wrapper-Funktion schreiben und diese stattdessen verwenden. Dies kann durch Aufrufen des folgenden Codes konfiguriert werden, möglicherweise in einem Initialisierer.
PgSearch . unaccent_function = "my_unaccent"
PostgreSQL ermöglicht Ihnen die Suche nach einer Spalte mit dem Typ tsvector, anstatt einen Ausdruck zu verwenden; Dies beschleunigt die Suche erheblich, da die Erstellung des TS-Vektors entlastet wird, anhand dessen die TS-Abfrage ausgewertet wird.
Um diese Funktionalität nutzen zu können, müssen Sie einige Dinge tun:
Erstellen Sie eine Spalte vom Typ tsvector, nach der Sie suchen möchten. Wenn Sie mit mehreren Suchmethoden suchen möchten, zum Beispiel tsearch und dmetaphone, benötigen Sie für jede eine Spalte.
Erstellen Sie eine Triggerfunktion, die die Spalte(n) mit dem für diesen Suchtyp geeigneten Ausdruck aktualisiert. Siehe: die PostgreSQL-Dokumentation für Textsuchauslöser
Sollten in der Tabelle bereits Daten vorhanden sein, aktualisieren Sie die neu erstellten tsvector-Spalten mit dem Ausdruck, den Ihre Triggerfunktion verwendet.
Fügen Sie die Option zu pg_search_scope hinzu, z. B.:
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
}
Bitte beachten Sie, dass die Spalte „:against“ nur verwendet wird, wenn die Spalte „tsvector_column“ für den Suchtyp nicht vorhanden ist.
Es ist möglich, nach mehr als einem TS-Vektor gleichzeitig zu suchen. Dies kann nützlich sein, wenn Sie mehrere Suchbereiche verwalten möchten, aber nicht für jeden Bereich separate TS-Vektoren verwalten möchten. Zum Beispiel:
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" ]
}
}
Standardmäßig ordnet pg_search die Ergebnisse basierend auf der :tsearch-Ähnlichkeit zwischen dem durchsuchbaren Text und der Abfrage. Um einen anderen Ranking-Algorithmus zu verwenden, können Sie eine :ranked_by-Option an pg_search_scope übergeben.
pg_search_scope :search_by_tsearch_but_rank_by_trigram ,
against : :title ,
using : [ :tsearch ] ,
ranked_by : ":trigram"
Beachten Sie, dass :ranked_by einen String zur Darstellung des Ranking-Ausdrucks verwendet. Dies ermöglicht komplexere Möglichkeiten. Zeichenfolgen wie „:tsearch“, „:trigram“ und „:dmetaphone“ werden automatisch in die entsprechenden SQL-Ausdrücke erweitert.
# 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)"
PostgreSQL garantiert keine konsistente Reihenfolge, wenn mehrere Datensätze denselben Wert in der ORDER BY-Klausel haben. Dies kann zu Problemen mit der Paginierung führen. Stellen Sie sich einen Fall vor, in dem 12 Datensätze alle denselben Rangwert haben. Wenn Sie eine Paginierungsbibliothek wie kaminari oder will_paginate verwenden, um Ergebnisse auf 10er-Seiten zurückzugeben, würden Sie davon ausgehen, dass 10 der Datensätze auf Seite 1 und die restlichen 2 Datensätze oben auf der nächsten Seite vor den unteren Datensätzen angezeigt werden. Ranglistenergebnisse.
Da es jedoch keine konsistente Reihenfolge gibt, kann es sein, dass PostgreSQL die Reihenfolge dieser 12 Datensätze zwischen verschiedenen SQL-Anweisungen neu anordnet. Es kann sein, dass Sie dieselben Datensätze von Seite 1 auch auf Seite 2 erhalten, und ebenso kann es Datensätze geben, die überhaupt nicht angezeigt werden.
pg_search behebt dieses Problem, indem es nach dem oben erläuterten Ausdruck :ranked_by einen zweiten Ausdruck zur ORDER BY-Klausel hinzufügt. Standardmäßig ist die Tiebreaker-Reihenfolge aufsteigend nach ID.
ORDER BY [complicated :ranked_by expression...], id ASC
Dies ist für Ihre Anwendung möglicherweise nicht wünschenswert, insbesondere wenn Sie nicht möchten, dass alte Datensätze den neuen Datensätzen den Rang ablaufen. Durch Übergeben eines :order_within_rank können Sie einen alternativen Tiebreaker-Ausdruck angeben. Ein gängiges Beispiel wäre das Absteigen nach „update_at“, um die zuletzt aktualisierten Datensätze zuerst zu ordnen.
pg_search_scope :search_and_break_ties_by_latest_update ,
against : [ :title , :content ] ,
order_within_rank : "blog_posts.updated_at DESC"
Es kann nützlich oder interessant sein, den Rang eines bestimmten Datensatzes anzuzeigen. Dies kann beim Debuggen hilfreich sein, warum ein Datensatz einen anderen übertrifft. Sie können es auch verwenden, um Endbenutzern einer Anwendung eine Art Relevanzwert anzuzeigen.
Um den Rang abzurufen, rufen Sie .with_pg_search_rank
für einen Bereich und dann .pg_search_rank
für einen zurückgegebenen Datensatz auf.
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
Jeder PgSearch-Bereich generiert eine benannte Unterabfrage für den Suchrang. Wenn Sie mehrere Bereiche verketten, generiert PgSearch eine Rangfolgeabfrage für jeden Bereich, sodass die Rangfolgeabfragen eindeutige Namen haben müssen. Wenn Sie auf die Rangfolgeabfrage verweisen müssen (z. B. in einer GROUP BY-Klausel), können Sie den Namen der Unterabfrage mit der Methode PgScope::Configuration.alias
neu generieren, indem Sie den Namen der abgefragten Tabelle übergeben.
shirt_brands = ShirtBrand . search_by_name ( "Penguin" )
. joins ( :shirt_sizes )
. group ( "shirt_brands.id, #{ PgSearch :: Configuration . alias ( 'shirt_brands' ) } .rank" )
PgSearch wäre ohne die Inspiration von texticle (jetzt umbenannt in textacular) nicht möglich gewesen. Vielen Dank an Aaron Patterson für die Originalversion und an Casebook PBC (https://www.casebook.net) für das Geschenk an die Community!
Bitte lesen Sie unseren Leitfaden zum BEITRAGEN.
Wir haben auch eine Google-Gruppe zur Diskussion von pg_search und anderen Open-Source-Projekten von Casebook PBC.
Copyright © 2010–2022 Casebook PBC. Lizenziert unter der MIT-Lizenz, siehe LIZENZ-Datei.