PgSearch crea ámbitos con nombre que aprovechan la búsqueda de texto completo de PostgreSQL.
Lea la publicación del blog que presenta PgSearch en https://tanzu.vmware.com/content/blog/pg-search-how-i-learned-to-stop-worrying-and-love-postgresql-full-text-search
$ gem install pg_search
o agregue esta línea a su Gemfile:
gem 'pg_search'
Además de instalar y solicitar la gema, es posible que desees incluir las tareas de rake de PgSearch en tu Rakefile. Esto no es necesario para proyectos Rails, que obtienen las tareas de Rake a través de Railtie.
load "pg_search/tasks.rb"
Para agregar PgSearch a un modelo de Active Record, simplemente incluya el módulo PgSearch.
class Shape < ActiveRecord :: Base
include PgSearch :: Model
end
multisearchable
pg_search_scope
:tsearch
(Búsqueda de texto completo):prefix
(PostgreSQL 8.4 y versiones posteriores únicamente):negation
:dictionary
:normalization
:any_word
:sort_only
:highlight
:dmetaphone
(búsqueda de sonido similar a Double Metaphone):trigram
(búsqueda de trigramas):threshold
:word_similarity
:ranked_by
(Elegir un algoritmo de clasificación):order_within_rank
(Rompiendo lazos)PgSearch#pg_search_rank
(Leer la clasificación de un registro como flotante)pg_search admite dos técnicas diferentes de búsqueda, búsqueda múltiple y alcances de búsqueda.
La primera técnica es la búsqueda múltiple, en la que se pueden mezclar registros de muchas clases diferentes de Active Record en un índice de búsqueda global en toda la aplicación. La mayoría de los sitios que quieran admitir una página de búsqueda genérica querrán utilizar esta función.
La otra técnica son los ámbitos de búsqueda, que le permiten realizar búsquedas más avanzadas en una sola clase de Active Record. Esto es más útil para crear elementos como autocompletados o filtrar una lista de elementos en una búsqueda por facetas.
Antes de utilizar la búsqueda múltiple, debe generar y ejecutar una migración para crear la tabla de base de datos pg_search_documents.
$ rails g pg_search:migration:multisearch
$ rake db:migrate
Para agregar un modelo al índice de búsqueda global de su aplicación, llame a multisearchable en su definición de clase.
class EpicPoem < ActiveRecord :: Base
include PgSearch :: Model
multisearchable against : [ :title , :author ]
end
class Flower < ActiveRecord :: Base
include PgSearch :: Model
multisearchable against : :color
end
Si este modelo ya tiene registros existentes, deberá volver a indexarlo para obtener los registros existentes en la tabla pg_search_documents. Vea la tarea de reconstrucción a continuación.
Cada vez que se crea, actualiza o destruye un registro, se activará una devolución de llamada de Active Record, lo que llevará a la creación del registro PgSearch::Document correspondiente en la tabla pg_search_documents. La opción :contra puede ser uno o varios métodos que se llamarán en el registro para generar su texto de búsqueda.
También puede pasar un proc o nombre de método a llamar para determinar si se debe incluir o no un registro en particular.
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
Tenga en cuenta que el nombre del método o Proc se llama en un enlace after_save. Esto significa que debes tener cuidado al utilizar Time u otros objetos. En el siguiente ejemplo, si el registro se guardó por última vez antes de la marca de tiempo publicado_at, no aparecerá en la búsqueda global hasta que se toque nuevamente después de la marca de tiempo.
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
Actualizar condicionalmente pg_search_documents
También puede usar la opción :update_if
para pasar un Proc o nombre de método para llamar y determinar si un registro en particular debe actualizarse o no.
Tenga en cuenta que el nombre del método o Proc se llama en un gancho after_save
, por lo que si confía en las banderas sucias de ActiveRecord, utilice *_previously_changed?
.
class Message < ActiveRecord :: Base
include PgSearch :: Model
multisearchable against : [ :body ] ,
update_if : :body_previously_changed?
end
Especifique atributos adicionales que se guardarán en la tabla pg_search_documents
Puede especificar :additional_attributes
para que se guarden dentro de la tabla pg_search_documents
. Por ejemplo, quizás esté indexando un modelo de libro y un modelo de artículo y desee incluir el valor de autor_id.
Primero, necesitamos agregar una referencia al autor a la migración creando nuestra tabla 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
Luego, podemos enviar este atributo adicional en una lambda
multisearchable (
against : [ :title , :body ] ,
additional_attributes : -> ( article ) { { author_id : article . author_id } }
)
Esto permite búsquedas mucho más rápidas sin uniones más adelante haciendo algo como:
PgSearch . multisearch ( params [ 'search' ] ) . where ( author_id : 2 )
NOTA: actualmente debe llamar manualmente record.update_pg_search_document
para que el atributo adicional se incluya en la tabla pg_search_documents.
Se crean dos asociaciones automáticamente. En el registro original, hay una asociación has_one :pg_search_document que apunta al registro PgSearch::Document, y en el registro PgSearch::Document hay una asociación polimórfica que se puede buscar que apunta al registro original.
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">
Para recuperar las entradas de PgSearch::Document para todos los registros que coinciden con una consulta determinada, utilice 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 devuelve una ActiveRecord::Relation, tal como lo hacen los ámbitos, por lo que puede encadenar llamadas de alcance hasta el final. Esto funciona con gemas como Kaminari que agregan métodos de alcance. Al igual que con los ámbitos normales, la base de datos solo recibirá solicitudes SQL cuando sea necesario.
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 se puede configurar usando las mismas opciones que pg_search_scope
(que se explican con más detalle a continuación). Simplemente configure PgSearch.multisearch_options en un inicializador:
PgSearch . multisearch_options = {
using : [ :tsearch , :trigram ] ,
ignoring : :accents
}
Si cambia la opción :contra en una clase, agrega multisearchable a una clase que ya tiene registros en la base de datos, o elimina multisearchable de una clase para eliminarla del índice, encontrará que la tabla pg_search_documents podría quedar fuera de lugar. de sincronización con los registros reales en sus otras tablas.
El índice también puede desincronizarse si alguna vez modifica registros de una manera que no active devoluciones de llamada de Active Record. Por ejemplo, el método de instancia #update_attribute y el método de clase .update_all omiten las devoluciones de llamada y modifican directamente la base de datos.
Para eliminar todos los documentos de una clase determinada, simplemente puede eliminar todos los registros de PgSearch::Document.
PgSearch :: Document . delete_by ( searchable_type : "Animal" )
Para regenerar los documentos para una clase determinada, ejecute:
PgSearch :: Multisearch . rebuild ( Product )
El método rebuild
eliminará todos los documentos de la clase dada antes de regenerarlos. En algunas situaciones, esto puede no ser deseable, como cuando utiliza herencia de tabla única y searchable_type
es su clase base. Puede evitar que rebuild
elimine sus registros de esta manera:
PgSearch :: Multisearch . rebuild ( Product , clean_up : false )
rebuild
se ejecuta dentro de una sola transacción. Para ejecutar fuera de una transacción, puede pasar transactional: false
así:
PgSearch :: Multisearch . rebuild ( Product , transactional : false )
La reconstrucción también está disponible como tarea de Rake, para mayor comodidad.
$ rake pg_search:multisearch:rebuild[BlogPost]
Se puede pasar un segundo argumento opcional para especificar la ruta de búsqueda del esquema PostgreSQL que se utilizará, para bases de datos multiinquilino que tienen varias tablas pg_search_documents. Lo siguiente establecerá la ruta de búsqueda del esquema en "my_schema" antes de volver a indexar.
$ rake pg_search:multisearch:rebuild[BlogPost,my_schema]
Para los modelos que permiten búsquedas múltiples :against
métodos que se asignan directamente a los atributos de Active Record, se ejecuta una única instrucción SQL eficiente para actualizar la tabla pg_search_documents
de una sola vez. Sin embargo, si llama a algún método dinámico en :against
, se llamará update_pg_search_document
en los registros individuales que se indexan en lotes.
También puede proporcionar una implementación personalizada para reconstruir los documentos agregando un método de clase llamado rebuild_pg_search_documents
a su modelo.
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
Nota: Si usa PostgreSQL anterior a 9.1, reemplace la llamada a la función CONCAT_WS()
con concatenación de doble canal, por ejemplo. (movies.name || ' ' || directors.name)
. Sin embargo, ahora tenga en cuenta que si alguno de los valores unidos es NULL, entonces el valor content
final también será NULL, mientras que CONCAT_WS()
ignorará selectivamente los valores NULL.
Si tiene que realizar una operación masiva de gran tamaño, como importar una gran cantidad de registros desde una fuente externa, es posible que desee acelerar las cosas desactivando la indexación temporalmente. Luego podría utilizar una de las técnicas anteriores para reconstruir los documentos de búsqueda fuera de línea.
PgSearch . disable_multisearch do
Movie . import_from_xml_file ( File . open ( "movies.xml" ) )
end
Puede utilizar pg_search_scope para crear un alcance de búsqueda. El primer parámetro es un nombre de ámbito y el segundo parámetro es un hash de opciones. La única opción requerida es :contra, que le dice a pg_search_scope en qué columna o columnas buscar.
Para buscar en una columna, pase un símbolo como opción: contra.
class BlogPost < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_by_title , against : :title
end
Ahora tenemos un alcance de ActiveRecord llamado search_by_title en nuestro modelo BlogPost. Se necesita un parámetro, una cadena de consulta de búsqueda.
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">]
Simplemente pase un Array si desea buscar en más de una columna.
class Person < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_by_full_name , against : [ :first_name , :last_name ]
end
Ahora nuestra consulta de búsqueda puede coincidir con una o ambas columnas.
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]
Al igual que con los ámbitos con nombre de Active Record, puede pasar un objeto Proc que devuelva un hash de opciones. Por ejemplo, el siguiente alcance toma un parámetro que elige dinámicamente en qué columna buscar.
Importante: El hash devuelto debe incluir una clave de consulta. Su valor no tiene por qué ser necesariamente dinámico. Puede optar por codificarlo con un valor específico si lo desea.
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 posible buscar columnas en modelos asociados. Tenga en cuenta que si hace esto, será imposible acelerar las búsquedas con índices de bases de datos. Sin embargo, se admite como una forma rápida de probar la búsqueda entre modelos.
Puede pasar un Hash a la opción :associated_against para configurar la búsqueda a través de asociaciones. Las claves son los nombres de las asociaciones y el valor funciona como una opción: contra para el otro modelo. En este momento, no se admite la búsqueda más profunda que una asociación de distancia. Puede solucionar este problema configurando una serie de asociaciones :through para señalar hasta el final.
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]
De forma predeterminada, pg_search_scope utiliza la búsqueda de texto integrada de PostgreSQL. Si pasa la opción :using a pg_search_scope, puede elegir técnicas de búsqueda alternativas.
class Beer < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_name , against : :name , using : [ :tsearch , :trigram , :dmetaphone ]
end
A continuación se muestra un ejemplo si pasa varias opciones :using con configuraciones adicionales.
class Beer < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_name ,
against : :name ,
using : {
:trigram => { } ,
:dmetaphone => { } ,
:tsearch => { :prefix => true }
}
end
Las características implementadas actualmente son
La búsqueda de texto completo integrada de PostgreSQL admite ponderación, búsquedas de prefijos y derivaciones en varios idiomas.
A cada columna de búsqueda se le puede asignar un peso de "A", "B", "C" o "D". Las columnas con letras anteriores tienen un peso mayor que aquellas con letras posteriores. Entonces, en el siguiente ejemplo, el título es lo más importante, seguido del subtítulo y finalmente el contenido.
class NewsArticle < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_full_text , against : {
title : 'A' ,
subtitle : 'B' ,
content : 'C'
}
end
También puede pasar los pesos como una matriz de matrices o cualquier otra estructura que responda a #each y produzca un solo símbolo o un símbolo y un peso. Si omite el peso, se utilizará un valor predeterminado.
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
La búsqueda de texto completo de PostgreSQL coincide con palabras completas de forma predeterminada. Sin embargo, si desea buscar palabras parciales, puede establecer :prefix en verdadero. Dado que esta es una opción específica de :tsearch, debe pasarla directamente a :tsearch, como se muestra en el siguiente ejemplo.
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]
La búsqueda de texto completo de PostgreSQL coincide con todos los términos de búsqueda de forma predeterminada. Si desea excluir ciertas palabras, puede establecer :negation en verdadero. ¡Entonces cualquier término que comience con un signo de exclamación !
serán excluidos de los resultados. Dado que esta es una opción específica de :tsearch, debe pasarla directamente a :tsearch, como se muestra en el siguiente ejemplo.
Tenga en cuenta que combinar esto con otras funciones de búsqueda puede generar resultados inesperados. Por ejemplo, las búsquedas de :trigram no tienen un concepto de términos excluidos y, por lo tanto, si usas :tsearch y :trigram en conjunto, aún puedes encontrar resultados que contengan el término que estabas intentando excluir.
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]
La búsqueda de texto completo de PostgreSQL también admite múltiples diccionarios para derivar. Puede obtener más información sobre cómo funcionan los diccionarios leyendo la documentación de PostgreSQL. Si utiliza uno de los diccionarios de idiomas, como "inglés", las variantes de las palabras (p. ej. "jumping" y "jumped") coincidirán entre sí. Si no desea derivar, debe elegir el diccionario "simple" que no utiliza derivaciones. Si no especifica un diccionario, se utilizará el diccionario "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 admite múltiples algoritmos para clasificar los resultados según las consultas. Por ejemplo, es posible que desee considerar el tamaño general del documento o la distancia entre varios términos de búsqueda en el texto original. Esta opción toma un número entero, que se pasa directamente a PostgreSQL. Según la última documentación de PostgreSQL, los algoritmos admitidos son:
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
Este número entero es una máscara de bits, por lo que si desea combinar algoritmos, puede sumar sus números. (por ejemplo, para usar los algoritmos 1, 8 y 32, pasaría 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]
Establecer este atributo en verdadero realizará una búsqueda que devolverá todos los modelos que contengan cualquier palabra en los términos de búsqueda.
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' ) # => []
Establecer este atributo en verdadero hará que esta función esté disponible para ordenar, pero no la incluirá en la condición WHERE de la consulta.
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]
Al agregar .with_pg_search_highlight después de pg_search_scope, puede acceder al atributo pg_highlight
para cada objeto.
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."
La opción resaltada acepta todas las opciones admitidas por ts_headline y utiliza los valores predeterminados de PostgreSQL.
Consulte la documentación para obtener detalles sobre el significado de cada opción.
Double Metaphone es un algoritmo para unir palabras que suenan igual incluso si se escriben de manera muy diferente. Por ejemplo, "Geoff" y "Jeff" suenan idénticos y, por tanto, coinciden. Actualmente, este no es un verdadero metáfono doble, ya que solo se utiliza el primer metáfono para la búsqueda.
La compatibilidad con Double Metaphone está disponible actualmente como parte de la extensión fuzzystrmatch que debe instalarse antes de poder utilizar esta función. Además de la extensión, debe instalar una función de utilidad en su base de datos. Para generar y ejecutar una migración para esto, ejecute:
$ rails g pg_search:migration:dmetaphone
$ rake db:migrate
El siguiente ejemplo muestra cómo utilizar :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]
La búsqueda de trigramas funciona contando cuántas subcadenas de tres letras (o "trigramas") coinciden entre la consulta y el texto. Por ejemplo, la cadena "Lorem ipsum" se puede dividir en los siguientes trigramas:
[" Lo", "Lor", "ore", "rem", "em ", "m i", " ip", "ips", "psu", "sum", "um ", "m "]
La búsqueda de Trigram tiene cierta capacidad para funcionar incluso con errores tipográficos y ortográficos en la consulta o el texto.
La compatibilidad con Trigram está disponible actualmente como parte de la extensión pg_trgm que debe instalarse antes de poder utilizar esta función.
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]
De forma predeterminada, las búsquedas de trigramas encuentran registros que tienen una similitud de al menos 0,3 utilizando los cálculos de pg_trgm. Puede especificar un umbral personalizado si lo prefiere. Los números más altos coinciden de manera más estricta y, por lo tanto, arrojan menos resultados. Los números más bajos coinciden de manera más permisiva, lo que permite obtener más resultados. Tenga en cuenta que establecer un umbral de trigrama forzará un escaneo de la tabla ya que la consulta derivada utiliza la función similarity()
en lugar del operador %
.
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" ) # => []
Le permite unir palabras en cadenas más largas. De forma predeterminada, las búsquedas de trigramas utilizan %
o similarity()
como valor de similitud. Establezca word_similarity
en true
para optar por <%
y word_similarity
en su lugar. Esto hace que la búsqueda de trigramas utilice la similitud del término de consulta y la palabra con mayor similitud.
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]
A veces, al realizar consultas que combinan diferentes funciones, es posible que desee buscar solo en algunos de los campos con determinadas funciones. Por ejemplo, tal vez desee realizar únicamente una búsqueda de trigramas en los campos más cortos para no tener que reducir el umbral excesivamente. Puede especificar qué campos utilizando la opción 'solo':
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
Ahora puede recuperar con éxito una imagen con un nombre de archivo: 'image_foo.jpg' y una descripción larga: 'Esta descripción es tan larga que haría que una búsqueda de trigramas fallara en cualquier límite de umbral razonable' con:
Image . combined_search ( 'reasonable' ) # found with tsearch
Image . combined_search ( 'foo' ) # found with trigram
La mayoría de las veces querrás ignorar los acentos al realizar la búsqueda. Esto hace posible encontrar palabras como "piñata" cuando se busca con la consulta "pinata". Si configura pg_search_scope para ignorar los acentos, los ignorará tanto en el texto buscable como en los términos de consulta.
Al ignorar los acentos se utiliza la extensión sin acentos que debe instalarse antes de poder utilizar esta función.
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]
Es posible que los usuarios avanzados deseen agregar índices para las expresiones que genera pg_search. Desafortunadamente, la función sin acento proporcionada por esta extensión no es indexable (a partir de PostgreSQL 9.1). Por lo tanto, es posible que desee escribir su propia función contenedora y utilizarla en su lugar. Esto se puede configurar llamando al siguiente código, quizás en un inicializador.
PgSearch . unaccent_function = "my_unaccent"
PostgreSQL le permite buscar en una columna con tipo tsvector en lugar de usar una expresión; esto acelera la búsqueda dramáticamente ya que descarga la creación del tsvector con el que se evalúa el tsquery.
Para utilizar esta funcionalidad necesitarás hacer algunas cosas:
Cree una columna de tipo tsvector en la que desee realizar la búsqueda. Si desea buscar utilizando varios métodos de búsqueda, por ejemplo tsearch y dmetaphone, necesitará una columna para cada uno.
Cree una función desencadenante que actualice las columnas utilizando la expresión apropiada para ese tipo de búsqueda. Consulte: la documentación de PostgreSQL para activadores de búsqueda de texto
Si tiene datos preexistentes en la tabla, actualice las columnas tsvector recién creadas con la expresión que utiliza su función de activación.
Agregue la opción a pg_search_scope, por ejemplo:
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
}
Tenga en cuenta que la columna :contra solo se usa cuando tsvector_column no está presente para el tipo de búsqueda.
Es posible buscar en más de un tsvector a la vez. Esto podría resultar útil si desea mantener múltiples ámbitos de búsqueda pero no desea mantener tsvectors separados para cada ámbito. Por ejemplo:
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" ]
}
}
De forma predeterminada, pg_search clasifica los resultados según la similitud :tsearch entre el texto buscable y la consulta. Para utilizar un algoritmo de clasificación diferente, puede pasar una opción :ranked_by a pg_search_scope.
pg_search_scope :search_by_tsearch_but_rank_by_trigram ,
against : :title ,
using : [ :tsearch ] ,
ranked_by : ":trigram"
Tenga en cuenta que :ranked_by usa una cadena para representar la expresión de clasificación. Esto permite posibilidades más complejas. Cadenas como ":tsearch", ":trigram" y ":dmetaphone" se expanden automáticamente en las expresiones SQL apropiadas.
# 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 no garantiza un orden consistente cuando varios registros tienen el mismo valor en la cláusula ORDER BY. Esto puede causar problemas con la paginación. Imagine un caso en el que 12 registros tienen todos el mismo valor de clasificación. Si utiliza una biblioteca de paginación como kaminari o will_paginate para devolver resultados en páginas de 10, entonces esperaría ver 10 de los registros en la página 1 y los 2 registros restantes en la parte superior de la página siguiente, delante de los inferiores. resultados clasificados.
Pero como no existe un orden consistente, PostgreSQL podría optar por reorganizar el orden de esos 12 registros entre diferentes declaraciones SQL. Es posible que termine obteniendo algunos de los mismos registros de la página 1 en la página 2 también y, del mismo modo, es posible que haya registros que no aparezcan en absoluto.
pg_search soluciona este problema agregando una segunda expresión a la cláusula ORDER BY, después de la expresión :ranked_by explicada anteriormente. De forma predeterminada, el orden de desempate es ascendente por id.
ORDER BY [complicated :ranked_by expression...], id ASC
Es posible que esto no sea deseable para su aplicación, especialmente si no desea que los registros antiguos superen en rango a los nuevos. Al pasar :order_within_rank, puede especificar una expresión de desempate alternativa. Un ejemplo común sería descender por update_at, para clasificar primero los registros actualizados más recientemente.
pg_search_scope :search_and_break_ties_by_latest_update ,
against : [ :title , :content ] ,
order_within_rank : "blog_posts.updated_at DESC"
Puede resultar útil o interesante ver la clasificación de un registro en particular. Esto puede resultar útil para depurar por qué un registro supera a otro. También puede usarlo para mostrar algún tipo de valor de relevancia a los usuarios finales de una aplicación.
Para recuperar la clasificación, llame .with_pg_search_rank
en un ámbito y luego llame .pg_search_rank
en un registro devuelto.
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
Cada alcance de PgSearch genera una subconsulta con nombre para el rango de búsqueda. Si encadena varios ámbitos, PgSearch generará una consulta de clasificación para cada ámbito, por lo que las consultas de clasificación deben tener nombres únicos. Si necesita hacer referencia a la consulta de clasificación (por ejemplo, en una cláusula GROUP BY), puede regenerar el nombre de la subconsulta con el método PgScope::Configuration.alias
pasando el nombre de la tabla consultada.
shirt_brands = ShirtBrand . search_by_name ( "Penguin" )
. joins ( :shirt_sizes )
. group ( "shirt_brands.id, #{ PgSearch :: Configuration . alias ( 'shirt_brands' ) } .rank" )
PgSearch no habría sido posible sin la inspiración de texticle (ahora rebautizado como textacular). ¡Gracias a Aaron Patterson por la versión original y a Casebook PBC (https://www.casebook.net) por regalársela a la comunidad!
Por favor lea nuestra guía CONTRIBUCIÓN.
También tenemos un grupo de Google para discutir pg_search y otros proyectos de código abierto de Casebook PBC.
Copyright © 2010–2022 Libro de casos PBC. Licenciado bajo la licencia MIT, consulte el archivo LICENCIA.