يبني 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'
بالإضافة إلى تثبيت الجوهرة وطلبها، قد ترغب في تضمين مهام PgSearch rake في ملف Rakefile الخاص بك. هذا ليس ضروريًا لمشاريع ريلز، التي تحصل على مهام Rake عبر Railtie.
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
(بحث صوتي مزدوج Metaphone):trigram
(بحث تريجرام):threshold
:word_similarity
:ranked_by
(اختيار خوارزمية الترتيب):order_within_rank
(قطع العلاقات)PgSearch#pg_search_rank
(قراءة تصنيف السجل على أنه عائم)يدعم pg_search طريقتين مختلفتين للبحث، البحث المتعدد ونطاقات البحث.
الأسلوب الأول هو البحث المتعدد، حيث يمكن دمج سجلات العديد من فئات Active Record المختلفة معًا في فهرس بحث عالمي واحد عبر التطبيق بأكمله. سترغب معظم المواقع التي ترغب في دعم صفحة بحث عامة في استخدام هذه الميزة.
الأسلوب الآخر هو نطاقات البحث، والتي تسمح لك بإجراء بحث أكثر تقدمًا مقابل فئة Active Record واحدة فقط. يعد هذا مفيدًا أكثر لإنشاء أشياء مثل الإكمال التلقائي أو تصفية قائمة العناصر في البحث متعدد الأوجه.
قبل استخدام البحث المتعدد، يجب عليك إنشاء عملية ترحيل وتشغيلها لإنشاء جدول قاعدة بيانات pg_search_documents.
$ rails g pg_search:migration:multisearch
$ rake db:migrate
لإضافة نموذج إلى فهرس البحث الشامل لتطبيقك، قم باستدعاء multisearchable في تعريف فئته.
class EpicPoem < ActiveRecord :: Base
include PgSearch :: Model
multisearchable against : [ :title , :author ]
end
class Flower < ActiveRecord :: Base
include PgSearch :: Model
multisearchable against : :color
end
إذا كان هذا النموذج يحتوي بالفعل على سجلات موجودة، فستحتاج إلى إعادة فهرسة هذا النموذج للحصول على السجلات الموجودة في جدول pg_search_documents. انظر مهمة إعادة البناء أدناه.
عندما يتم إنشاء سجل أو تحديثه أو إتلافه، سيتم إطلاق رد اتصال Active Record، مما يؤدي إلى إنشاء سجل PgSearch::Document المطابق في الجدول pg_search_documents. يمكن أن يكون الخيار: ضد طريقة واحدة أو عدة طرق سيتم استدعاؤها في السجل لإنشاء نص البحث الخاص به.
يمكنك أيضًا تمرير اسم Proc أو أسلوب للاتصال به لتحديد ما إذا كان يجب تضمين سجل معين أم لا.
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. هذا يعني أنه يجب عليك توخي الحذر عند استخدام الوقت أو الكائنات الأخرى. في المثال التالي، إذا تم حفظ السجل آخر مرة قبل الطابع الزمني Publish_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
بعد ذلك، يمكننا إرسال هذه السمة الإضافية في لامدا
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 يوجد اقتران متعدد الأشكال ينتمي إلى: قابل للبحث يشير إلى السجل الأصلي.
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 التي تضيف أساليب النطاق. تمامًا كما هو الحال مع النطاقات العادية، لن تتلقى قاعدة البيانات سوى طلبات SQL عند الضرورة.
PgSearch . multisearch ( "Bertha" ) . limit ( 10 )
PgSearch . multisearch ( "Juggler" ) . where ( searchable_type : "Occupation" )
PgSearch . multisearch ( "Alamo" ) . page ( 3 ) . per ( 30 )
PgSearch . multisearch ( "Diagonal" ) . find_each do | document |
puts document . searchable . updated_at
end
PgSearch . multisearch ( "Moro" ) . reorder ( "" ) . group ( :searchable_type ) . count ( :all )
PgSearch . multisearch ( "Square" ) . includes ( :searchable )
يمكن تكوين PgSearch.multisearch باستخدام نفس خيارات pg_search_scope
(موضحة بمزيد من التفاصيل أدناه). فقط قم بتعيين PgSearch.multisearch_options في أداة التهيئة:
PgSearch . multisearch_options = {
using : [ :tsearch , :trigram ] ,
ignoring : :accents
}
إذا قمت بتغيير الخيار: ضد فئة ما، أو أضفت إمكانية البحث المتعددة إلى فئة تحتوي بالفعل على سجلات في قاعدة البيانات، أو قمت بإزالة إمكانية البحث المتعددة من فئة لإزالتها من الفهرس، فستجد أن جدول pg_search_documents قد يصبح خارجًا- المزامنة مع السجلات الفعلية في جداولك الأخرى.
يمكن أن يصبح الفهرس أيضًا غير متزامن إذا قمت بتعديل السجلات بطريقة لا تؤدي إلى عمليات الاسترجاعات لـ Active Record. على سبيل المثال، يقوم أسلوب مثيل #update_attribute وأسلوب فئة .update_all بتخطي عمليات الاسترجاعات وتعديل قاعدة البيانات مباشرة.
لإزالة كافة المستندات الخاصة بفصل معين، يمكنك ببساطة حذف كافة سجلات PgSearch::Document.
PgSearch :: Document . delete_by ( searchable_type : "Animal" )
لإعادة إنشاء المستندات لفئة معينة، قم بتشغيل:
PgSearch :: Multisearch . rebuild ( Product )
ستحذف طريقة rebuild
جميع المستندات الخاصة بالفئة المحددة قبل إعادة إنشائها. في بعض المواقف، قد لا يكون ذلك مرغوبًا، كما هو الحال عندما تستخدم وراثة جدول واحد ويكون searchable_type
هو فئتك الأساسية. يمكنك منع rebuild
من حذف سجلاتك كما يلي:
PgSearch :: Multisearch . rebuild ( Product , clean_up : false )
يتم تشغيل rebuild
داخل معاملة واحدة. للتشغيل خارج المعاملة، يمكنك تمرير transactional: false
كما يلي:
PgSearch :: Multisearch . rebuild ( Product , transactional : false )
إعادة البناء متاحة أيضًا كمهمة Rake، للراحة.
$ rake pg_search:multisearch:rebuild[BlogPost]
يمكن تمرير وسيطة اختيارية ثانية لتحديد مسار بحث مخطط PostgreSQL المراد استخدامه، لقواعد البيانات متعددة المستأجرين التي تحتوي على جداول pg_search_documents متعددة. سيقوم ما يلي بتعيين مسار بحث المخطط إلى "my_schema" قبل إعادة الفهرسة.
$ rake pg_search:multisearch:rebuild[BlogPost,my_schema]
بالنسبة للنماذج القابلة للبحث المتعدد :against
الأساليب التي يتم تعيينها مباشرة إلى سمات Active Record، يتم تشغيل عبارة SQL واحدة فعالة لتحديث جدول pg_search_documents
مرة واحدة. ومع ذلك، إذا قمت باستدعاء أي أساليب ديناميكية في :against
، فسيتم استدعاء update_pg_search_document
على السجلات الفردية التي تتم فهرستها على دفعات.
يمكنك أيضًا توفير تطبيق مخصص لإعادة إنشاء المستندات عن طريق إضافة طريقة فئة تسمى rebuild_pg_search_documents
إلى النموذج الخاص بك.
class Movie < ActiveRecord :: Base
belongs_to :director
def director_name
director . name
end
multisearchable against : [ :name , :director_name ]
# Naive approach
def self . rebuild_pg_search_documents
find_each { | record | record . update_pg_search_document }
end
# More sophisticated approach
def self . rebuild_pg_search_documents
connection . execute <<~SQL . squish
INSERT INTO pg_search_documents (searchable_type, searchable_id, content, created_at, updated_at)
SELECT 'Movie' AS searchable_type,
movies.id AS searchable_id,
CONCAT_WS(' ', movies.name, directors.name) AS content,
now() AS created_at,
now() AS updated_at
FROM movies
LEFT JOIN directors
ON directors.id = movies.director_id
SQL
end
end
ملاحظة: إذا كنت تستخدم PostgreSQL قبل الإصدار 9.1، فاستبدل استدعاء الدالة CONCAT_WS()
بتسلسل مزدوج الأنبوب، على سبيل المثال. (movies.name || ' ' || directors.name)
. ومع ذلك، انتبه الآن إلى أنه إذا كانت أي من القيم المرتبطة NULL، فإن قيمة content
النهائية ستكون أيضًا NULL، بينما CONCAT_WS()
ستتجاهل القيم NULL بشكل انتقائي.
إذا كانت لديك عملية مجمعة كبيرة يتعين عليك تنفيذها، مثل استيراد عدد كبير من السجلات من مصدر خارجي، فقد ترغب في تسريع الأمور عن طريق إيقاف تشغيل الفهرسة مؤقتًا. يمكنك بعد ذلك استخدام إحدى التقنيات المذكورة أعلاه لإعادة إنشاء مستندات البحث دون الاتصال بالإنترنت.
PgSearch . disable_multisearch do
Movie . import_from_xml_file ( File . open ( "movies.xml" ) )
end
يمكنك استخدام pg_search_scope لإنشاء نطاق بحث. المعلمة الأولى هي اسم النطاق، والمعلمة الثانية هي تجزئة الخيارات. الخيار الوحيد المطلوب هو: ضد، والذي يخبر pg_search_scope بالعمود أو الأعمدة التي يجب البحث عنها.
للبحث في عمود، قم بتمرير رمز كخيار: ضد.
class BlogPost < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_by_title , against : :title
end
لدينا الآن نطاق ActiveRecord يُسمى search_by_title في نموذج BlogPost الخاص بنا. يستغرق الأمر معلمة واحدة، وهي سلسلة استعلام بحث.
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 الذي يُرجع مجموعة من الخيارات. على سبيل المثال، يأخذ النطاق التالي معلمة تختار بشكل ديناميكي العمود الذي سيتم البحث عنه.
هام: يجب أن تتضمن التجزئة التي تم إرجاعها مفتاح استعلام:. ليس من الضروري أن تكون قيمتها ديناميكية. يمكنك اختيار ترميزها بقيمة محددة إذا أردت ذلك.
class Person < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_by_name , lambda { | name_part , query |
raise ArgumentError unless [ :first , :last ] . include? ( name_part )
{
against : name_part ,
query : query
}
}
end
person_1 = Person . create! ( first_name : "Grant" , last_name : "Hill" )
person_2 = Person . create! ( first_name : "Hugh" , last_name : "Grant" )
Person . search_by_name :first , "Grant" # => [person_1]
Person . search_by_name :last , "Grant" # => [person_2]
من الممكن البحث عن الأعمدة في النماذج المرتبطة. لاحظ أنه إذا قمت بذلك، فسيكون من المستحيل تسريع عمليات البحث باستخدام فهارس قاعدة البيانات. ومع ذلك، فهو مدعوم كطريقة سريعة لتجربة البحث عبر النماذج.
يمكنك تمرير التجزئة إلى خيار :associated_against لإعداد البحث من خلال الارتباطات. المفاتيح هي أسماء الارتباطات وتعمل القيمة تمامًا مثل الخيار: ضد النموذج الآخر. في الوقت الحالي، البحث بشكل أعمق عن أكثر من اقتران واحد غير مدعوم. يمكنك حل هذه المشكلة عن طريق إعداد سلسلة من :من خلال الارتباطات للإشارة إلى كل الطريق.
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. إذا قمت بتمرير خيار الاستخدام إلى pg_search_scope، فيمكنك اختيار تقنيات بحث بديلة.
class Beer < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :search_name , against : :name , using : [ :tsearch , :trigram , :dmetaphone ]
end
فيما يلي مثال إذا قمت بتمرير خيارات استخدام متعددة مع تكوينات إضافية.
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]
بإضافة .with_pg_search_highlight بعد pg_search_scope يمكنك الوصول إلى سمة 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" متطابقين وبالتالي متطابقين. حاليًا، هذا ليس ميتافونًا مزدوجًا حقيقيًا، حيث يتم استخدام أول ميتافون فقط للبحث.
يتوفر دعم Double 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]
يعمل بحث Trigram عن طريق حساب عدد السلاسل الفرعية المكونة من ثلاثة أحرف (أو "trigrams") المطابقة بين الاستعلام والنص. على سبيل المثال، يمكن تقسيم السلسلة النصية "Lorem ipsum" إلى الأشكال الثلاثية التالية:
[" Lo", "Lor", "ore", "rem", "em ", "m i", " ip", "ips", "psu", "sum", "um ", "m "]
يتمتع بحث Trigram ببعض القدرة على العمل حتى مع الأخطاء المطبعية والأخطاء الإملائية في الاستعلام أو النص.
يتوفر دعم 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]
افتراضيًا، تبحث عمليات البحث عن المثلثات عن السجلات التي لها تشابه لا يقل عن 0.3 باستخدام حسابات pg_trgm. يمكنك تحديد حد مخصص إذا كنت تفضل ذلك. تتطابق الأرقام الأعلى بشكل أكثر صرامة، وبالتالي تُرجع نتائج أقل. تتطابق الأرقام الأقل بشكل أكثر تسامحًا، مما يسمح بالمزيد من النتائج. يرجى ملاحظة أن تعيين حد ثلاثي الأبعاد سيفرض فحص الجدول حيث يستخدم الاستعلام المشتق وظيفة similarity()
بدلاً من عامل التشغيل %
.
class Vegetable < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :strictly_spelled_like ,
against : :name ,
using : {
trigram : {
threshold : 0.5
}
}
pg_search_scope :roughly_spelled_like ,
against : :name ,
using : {
trigram : {
threshold : 0.1
}
}
end
cauliflower = Vegetable . create! name : "cauliflower"
Vegetable . roughly_spelled_like ( "couliflower" ) # => [cauliflower]
Vegetable . strictly_spelled_like ( "couliflower" ) # => [cauliflower]
Vegetable . roughly_spelled_like ( "collyflower" ) # => [cauliflower]
Vegetable . strictly_spelled_like ( "collyflower" ) # => []
يسمح لك بمطابقة الكلمات في سلاسل أطول. افتراضيًا، تستخدم عمليات بحث المثلث %
أو similarity()
كقيمة تشابه. اضبط word_similarity
على true
لاختيار <%
و word_similarity
بدلاً من ذلك. يؤدي هذا إلى قيام بحث المثلث باستخدام تشابه مصطلح الاستعلام والكلمة ذات التشابه الأكبر.
class Sentence < ActiveRecord :: Base
include PgSearch :: Model
pg_search_scope :similarity_like ,
against : :name ,
using : {
trigram : {
word_similarity : true
}
}
pg_search_scope :word_similarity_like ,
against : :name ,
using : [ :trigram ]
end
sentence = Sentence . create! name : "Those are two words."
Sentence . similarity_like ( "word" ) # => []
Sentence . word_similarity_like ( "word" ) # => [sentence]
في بعض الأحيان، عند إجراء استعلامات تجمع بين ميزات مختلفة، قد ترغب في البحث في بعض الحقول التي تحتوي على ميزات معينة فقط. على سبيل المثال، ربما تريد إجراء بحث ثلاثي الأبعاد فقط في الحقول الأقصر، بحيث لا تحتاج إلى تقليل الحد بشكل مفرط. يمكنك تحديد الحقول باستخدام الخيار "فقط":
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: 'هذا الوصف طويل جدًا لدرجة أنه قد يؤدي إلى فشل بحث trigram في أي حد معقول' باستخدام:
Image . combined_search ( 'reasonable' ) # found with tsearch
Image . combined_search ( 'foo' ) # found with trigram
في أغلب الأحيان سوف ترغب في تجاهل علامات التشكيل عند البحث. وهذا يجعل من الممكن العثور على كلمات مثل "piñata" عند البحث باستخدام الاستعلام "pinata". إذا قمت بتعيين 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 بدلاً من استخدام التعبير؛ يؤدي هذا إلى تسريع البحث بشكل كبير حيث أنه يلغي إنشاء tsvector الذي يتم تقييم tsquery مقابله.
لاستخدام هذه الوظيفة، ستحتاج إلى القيام ببعض الأشياء:
قم بإنشاء عمود من النوع 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 موجودًا لنوع البحث.
من الممكن البحث في أكثر من ناقل واحد في نفس الوقت. قد يكون هذا مفيدًا إذا كنت تريد الاحتفاظ بنطاقات بحث متعددة ولكنك لا تريد الاحتفاظ بمتجهات منفصلة لكل نطاق. على سبيل المثال:
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 يستخدم سلسلة لتمثيل تعبير الترتيب. وهذا يسمح بإمكانيات أكثر تعقيدا. يتم توسيع سلاسل مثل ":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)"
لا يضمن PostgreSQL ترتيبًا متسقًا عندما يكون لسجلات متعددة نفس القيمة في عبارة ORDER BY. يمكن أن يسبب هذا مشكلة مع ترقيم الصفحات. تخيل حالة يكون فيها 12 سجلًا لها نفس قيمة التصنيف. إذا كنت تستخدم مكتبة ترقيم الصفحات مثل kaminari أو will_paginate لإرجاع النتائج في صفحات مكونة من 10، فمن المتوقع أن ترى 10 من السجلات في الصفحة 1، والسجلين المتبقيين في أعلى الصفحة التالية، قبل الأسفل- النتائج المرتبة.
ولكن نظرًا لعدم وجود ترتيب متسق، فقد تختار PostgreSQL إعادة ترتيب تلك السجلات الـ 12 بين عبارات SQL المختلفة. قد ينتهي بك الأمر بالحصول على بعض السجلات نفسها من الصفحة 1 في الصفحة 2 أيضًا، وبالمثل قد تكون هناك سجلات لا تظهر على الإطلاق.
يعمل pg_search على إصلاح هذه المشكلة عن طريق إضافة تعبير ثانٍ إلى جملة ORDER BY، بعد التعبير:ranked_by الموضح أعلاه. بشكل افتراضي، يتصاعد ترتيب التعادل حسب المعرف.
ORDER BY [complicated :ranked_by expression...], id ASC
قد لا يكون هذا مرغوبًا بالنسبة لتطبيقك، خاصةً إذا كنت لا تريد أن تتفوق السجلات القديمة على السجلات الجديدة. من خلال تمرير :order_within_rank، يمكنك تحديد تعبير بديل لكسر التعادل. من الأمثلة الشائعة الترتيب التنازلي لـ Update_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" )
لم يكن PgSearch ممكنًا بدون الإلهام من النسيج (أعيدت تسميته الآن textacular). شكرًا لآرون باترسون على الإصدار الأصلي ولـ Casebook PBC (https://www.casebook.net) على إهداء المجتمع به!
يرجى قراءة دليل المساهمة لدينا.
لدينا أيضًا مجموعة Google لمناقشة pg_search ومشاريع Casebook PBC الأخرى مفتوحة المصدر.
حقوق الطبع والنشر © 2010–2022 Casebook PBC. مرخص بموجب ترخيص MIT، راجع ملف الترخيص.