Recherche plein texte¶
Les fonctions de base de données dans le module django.contrib.postgres.search
facilitent lâutilisation du moteur de recherche plein texte de PostgreSQL.
Pour les exemples de ce document, nous utiliserons les modĂšles dĂ©finis dans CrĂ©ation de requĂȘtes.
Voir aussi
Pour un aperçu de haut niveau de la recherche, consultez la documentation thématique.
Lâexpression de recherche search
¶
Une maniĂšre habituelle dâutiliser la recherche plein texte est de rechercher un seul terme dans une seule colonne de base de donnĂ©es. Par exemple :
>>> Entry.objects.filter(body_text__search="Cheese")
[<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>]
Ceci crée un vecteur to_tsvector
dans la base de données à partir du champ body_text
et une requĂȘte plainto_tsquery
Ă partir du terme de recherche 'Cheese'
, les deux utilisant la configuration de recherche par dĂ©faut de la base de donnĂ©es. Les rĂ©sultats sont obtenus en faisant correspondre la requĂȘte et le vecteur.
Pour utiliser lâexpression de recherche search
, 'django.contrib.postgres'
doit figurer dans le réglage INSTALLED_APPS
.
SearchVector
¶
-
class
SearchVector
(*expressions, config=None, weight=None)¶
La recherche dans un champ unique fonctionne bien mais est plutÎt limitée. Les instances Entry
que nous recherchons appartiennent Ă un Blog
, qui possĂšde un champ tagline
. Pour interroger les deux champs, utilisez un vecteur SearchVector
:
>>> from django.contrib.postgres.search import SearchVector
>>> Entry.objects.annotate(
... search=SearchVector("body_text", "blog__tagline"),
... ).filter(search="Cheese")
[<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>]
Les paramĂštres Ă SearchVector
peuvent ĂȘtre nâimporte quelle Expression
ou le nom dâun champ. Plusieurs paramĂštres seront concatĂ©nĂ©s par des espaces afin que le document de recherche les inclue tous.
Les objets SearchVector
peuvent ĂȘtre combinĂ©s ce qui permet de les rĂ©utiliser. Par exemple :
>>> Entry.objects.annotate(
... search=SearchVector("body_text") + SearchVector("blog__tagline"),
... ).filter(search="Cheese")
[<Entry: Cheese on Toast recipes>, <Entry: Pizza Recipes>]
Voir Modification de la configuration de recherche et PondĂ©ration des requĂȘtes pour une explication des paramĂštres config
et weight
.
SearchQuery
¶
-
class
SearchQuery
(value, config=None, search_type='plain')¶
SearchQuery
traduit les termes fournis par lâutilisateur en un objet requĂȘte de recherche que la base de donnĂ©es va comparer Ă un vecteur de recherche. Par dĂ©faut, tous les mots fournis par lâutilisateur sont passĂ©s par un algorithme de segmentation, puis la recherche de correspondance sâeffectue pour tous les termes rĂ©sultants.
Si search_type
vaut 'plain'
, la valeur par défaut, les termes sont traités comme des mots-clés séparés. Si search_type
vaut 'phrase'
, les termes sont traités comme une phrase unique. Si search_type
vaut 'raw'
, vous pouvez alors fournir un requĂȘte de recherche formatĂ©e avec des termes et des opĂ©rateurs. Si search_type
vaut 'websearch'
, vous pouvez fournir une requĂȘte de recherche mise en forme telle quâon peut la trouver dans des moteurs de recherche du Web. 'websearch'
nécessite PostgreSQL ℠11. Lisez la _`documentation de recherche plein texte`_ de PostgreSQL pour explorer les différences et la syntaxe. Exemples :
>>> from django.contrib.postgres.search import SearchQuery
>>> SearchQuery("red tomato") # two keywords
>>> SearchQuery("tomato red") # same results as above
>>> SearchQuery("red tomato", search_type="phrase") # a phrase
>>> SearchQuery("tomato red", search_type="phrase") # a different phrase
>>> SearchQuery("'tomato' & ('red' | 'green')", search_type="raw") # boolean operators
>>> SearchQuery(
... "'tomato' ('red' OR 'green')", search_type="websearch"
... ) # websearch operators
Les termes SearchQuery
peuvent ĂȘtre combinĂ©s logiquement pour fournir plus de souplesse :
>>> from django.contrib.postgres.search import SearchQuery
>>> SearchQuery("meat") & SearchQuery("cheese") # AND
>>> SearchQuery("meat") | SearchQuery("cheese") # OR
>>> ~SearchQuery("meat") # NOT
Voir Modification de la configuration de recherche pour une explication du paramĂštre config
.
SearchRank
¶
-
class
SearchRank
(vector, query, weights=None, normalization=None, cover_density=False)¶
Nous avons jusquâici renvoyĂ©s les rĂ©sultats pour lesquels au moins une correspondance entre le vecteur et la requĂȘte est possible. Il est probable que vous souhaitiez trier les rĂ©sultats par une certaine notion de pertinence. PostgreSQL fournit une fonction de classement qui prend en compte la frĂ©quence dâapparition des termes recherchĂ©s dans le document, la proximitĂ© de ces termes dans le document et lâimportance de lâendroit oĂč les termes se trouvent dans le document. Plus la correspondance est bonne, plus le classement sera Ă©levĂ©. Pour trier par pertinence :
>>> from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
>>> vector = SearchVector("body_text")
>>> query = SearchQuery("cheese")
>>> Entry.objects.annotate(rank=SearchRank(vector, query)).order_by("-rank")
[<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]
Voir PondĂ©ration des requĂȘtes pour une explication du paramĂštre weights
.
Définissez le paramÚtre cover_density
Ă True
pour activer le classement selon densité de couverture, ce qui signifie que la proximité des termes de recherche correspondants est prise en compte.
Indiquez un nombre entier dans le paramĂštre normalization
pour contrĂŽler la normalisation du classement. Ce nombre est un masque binaire, ce qui permet de combiner plusieurs comportements :
>>> from django.db.models import Value
>>> Entry.objects.annotate(
... rank=SearchRank(
... vector,
... query,
... normalization=Value(2).bitor(Value(4)),
... )
... )
La documentation PostgreSQL contient plus de détails sur les différentes options de normalisation de classement.
SearchHeadline
¶
-
class
SearchHeadline
(expression, query, config=None, start_sel=None, stop_sel=None, max_words=None, min_words=None, short_word=None, highlight_all=None, max_fragments=None, fragment_delimiter=None)¶
Accepte un champ texte ou une expression unique, une requĂȘte, une configuration et un ensemble dâoptions. Renvoie des rĂ©sultats de recherche avec mises en Ă©vidence.
Définissez les paramÚtres start_sel
et stop_sel
à des valeurs textuelles utilisées pour envelopper les termes de recherche mis en évidence dans le document. Les valeurs par défaut de PostgreSQL sont <b>
et </b>
.
Fournissez des valeurs nombre entier dans les paramĂštres max_words
et min_words
pour déterminer les longueurs minimales et maximales des résumés. Les valeurs par défaut de PostgreSQL sont 35 et 15.
Fournissez une valeur nombre entier dans le paramĂštre short_word
pour éliminer les mots de cette longueur ou plus petits dans chaque résumé. La valeur par défaut de PostgreSQL est 3.
Définissez le paramÚtre highlight_all
Ă True
pour utiliser le document complet au lieu dâun rĂ©sumĂ© et donc ignorer les paramĂštres max_words
, min_words
et short_word
. PostgreSQL désactive ce comportement par défaut.
Fournissez une valeur nombre entier différente de zéro dans le paramÚtre max_fragments
pour définir le nombre maximum de résumés à afficher. PostgreSQL désactive ce comportement par défaut.
Définissez le paramÚtre textuel fragment_delimiter
pour configurer le délimiteur entre les résumés. La valeur par défaut de PostgreSQL est " ... "
.
La documentation PostgreSQL contient plus de détails sur les mises en évidence des résultats de recherche.
Exemple dâutilisation :
>>> from django.contrib.postgres.search import SearchHeadline, SearchQuery
>>> query = SearchQuery("red tomato")
>>> entry = Entry.objects.annotate(
... headline=SearchHeadline(
... "body_text",
... query,
... start_sel="<span>",
... stop_sel="</span>",
... ),
... ).get()
>>> print(entry.headline)
Sandwich with <span>tomato</span> and <span>red</span> cheese.
Voir Modification de la configuration de recherche pour une explication du paramĂštre config
.
Modification de la configuration de recherche¶
Il est possible de prĂ©ciser lâattribut config
de SearchVector
et de SearchQuery
afin dâutiliser une configuration de recherche diffĂ©rente. Cela permet dâutiliser des analyseurs et des dictionnaires de langue diffĂ©rents tels que dĂ©finis par la base de donnĂ©es :
>>> from django.contrib.postgres.search import SearchQuery, SearchVector
>>> Entry.objects.annotate(
... search=SearchVector("body_text", config="french"),
... ).filter(search=SearchQuery("Ćuf", config="french"))
[<Entry: Pain perdu>]
La valeur de config
peut aussi ĂȘtre stockĂ©e dans une autre colonne :
>>> from django.db.models import F
>>> Entry.objects.annotate(
... search=SearchVector("body_text", config=F("blog__language")),
... ).filter(search=SearchQuery("Ćuf", config=F("blog__language")))
[<Entry: Pain perdu>]
PondĂ©ration des requĂȘtes¶
Les diffĂ©rents champs dâune requĂȘte nâont pas toujours la mĂȘme pertinence, il est donc possible de pondĂ©rer les diffĂ©rents vecteurs avant de les combiner :
>>> from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
>>> vector = SearchVector("body_text", weight="A") + SearchVector(
... "blog__tagline", weight="B"
... )
>>> query = SearchQuery("cheese")
>>> Entry.objects.annotate(rank=SearchRank(vector, query)).filter(rank__gte=0.3).order_by(
... "rank"
... )
Le poids devrait correspondre Ă lâune des lettres suivantes : D, C, B, A. Par dĂ©faut, ces poids font rĂ©fĂ©rence respectivement aux nombres 0,1
, 0,2
, 0,4
et 1,0
. Si vous souhaitez pondérer de maniÚre différente, passez une liste de quatre nombres flottants à SearchRank
pour le paramĂštre weights
dans le mĂȘme ordre que ci-dessus :
>>> rank = SearchRank(vector, query, weights=[0.2, 0.4, 0.6, 0.8])
>>> Entry.objects.annotate(rank=rank).filter(rank__gte=0.3).order_by("-rank")
Performance¶
Il nâest pas nĂ©cessaire de disposer dâune configuration de base de donnĂ©es spĂ©ciale pour utiliser ces fonctions. Cependant, si vous recherchez dans plus de quelques centaines dâenregistrements, vous risquez de rencontrer des problĂšmes de performance. La recherche plein texte est un processus plus intensif que la comparaison de la taille dâun nombre entier, par exemple.
Dans le cas oĂč tous les champs sur lesquels vous cherchez sont contenus dans une seul modĂšle particulier, vous pouvez crĂ©er un index fonctionnel GIN
ou GiST
qui correspond au vecteur de recherche que vous souhaitez utiliser. Par exemple
GinIndex(
SearchVector("body_text", "headline", config="english"),
name="search_vector_idx",
)
La documentation PostgreSQL contient des dĂ©tails sur la crĂ©ation dâindex pour la recherche plein texte.
SearchVectorField
¶
-
class
SearchVectorField
¶
Si cette approche devient trop lente, vous pouvez ajouter un champ SearchVectorField
Ă votre modĂšle. Il faudra assurer son remplissage par des dĂ©clencheurs, par exemple, comme expliquĂ© dans la documentation PostgreSQL. Il est alors possible dâinterroger le champ comme sâil sâagissait dâun vecteur annotĂ© SearchVector
:
>>> Entry.objects.update(search_vector=SearchVector("body_text"))
>>> Entry.objects.filter(search_vector="cheese")
[<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]
Similarité par trigramme¶
Une autre approche de la recherche est la similitude par trigramme. Un trigramme est un groupe de trois caractÚres consécutifs. En plus des expressions de recherche trigram_similar
, trigram_word_similar
et trigram_strict_word_similar
, il est possible dâutiliser une certain nombre dâautres expressions.
Pour les utiliser, vous devez activer lâextension pg_trgm dans PostgreSQL. Vous pouvez installer lâextension par une opĂ©ration de migration TrigramExtension
.
TrigramSimilarity
¶
-
class
TrigramSimilarity
(expression, string, **extra)¶
Accepte un nom de champ ou une expression, ainsi quâune chaĂźne ou une expression. Renvoie la similitude par trigramme entre les deux paramĂštres.
Exemple dâutilisation :
>>> from django.contrib.postgres.search import TrigramSimilarity
>>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name="Stephen Keats")
>>> test = "Katie Stephens"
>>> Author.objects.annotate(
... similarity=TrigramSimilarity("name", test),
... ).filter(
... similarity__gt=0.3
... ).order_by("-similarity")
[<Author: Katy Stevens>, <Author: Stephen Keats>]
TrigramWordSimilarity
¶
-
class
TrigramWordSimilarity
(string, expression, **extra)¶
Accepte une chaĂźne ou une expression, ainsi quâune nom de champ ou une expression. Renvoie la similitude par trigramme de mot entre les deux paramĂštres.
Exemple dâutilisation :
>>> from django.contrib.postgres.search import TrigramWordSimilarity
>>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name="Stephen Keats")
>>> test = "Kat"
>>> Author.objects.annotate(
... similarity=TrigramWordSimilarity(test, "name"),
... ).filter(
... similarity__gt=0.3
... ).order_by("-similarity")
[<Author: Katy Stevens>]
TrigramStrictWordSimilarity
¶
-
class
TrigramStrictWordSimilarity
(string, expression, **extra)¶
Accepte une chaĂźne ou une expression, ainsi quâun nom de champ ou une expression. Renvoie la similitude par trigramme de mot strict entre les deux paramĂštres. Semblable Ă TrigramWordSimilarity()
, sauf que cette recherche force à étendre les limites pour correspondre aux limites de mots.
TrigramDistance
¶
-
class
TrigramDistance
(expression, string, **extra)¶
Accepte un nom de champ ou une expression, ainsi quâune chaĂźne ou une expression. Renvoie la distance par trigramme entre les deux paramĂštres.
Exemple dâutilisation :
>>> from django.contrib.postgres.search import TrigramDistance
>>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name="Stephen Keats")
>>> test = "Katie Stephens"
>>> Author.objects.annotate(
... distance=TrigramDistance("name", test),
... ).filter(
... distance__lte=0.7
... ).order_by("distance")
[<Author: Katy Stevens>, <Author: Stephen Keats>]
TrigramWordDistance
¶
-
class
TrigramWordDistance
(string, expression, **extra)¶
Accepte une chaĂźne ou une expression, ainsi quâune nom de champ ou une expression. Renvoie la distance par trigramme de mot entre les deux paramĂštres.
Exemple dâutilisation :
>>> from django.contrib.postgres.search import TrigramWordDistance
>>> Author.objects.create(name="Katy Stevens")
>>> Author.objects.create(name="Stephen Keats")
>>> test = "Kat"
>>> Author.objects.annotate(
... distance=TrigramWordDistance(test, "name"),
... ).filter(
... distance__lte=0.7
... ).order_by("distance")
[<Author: Katy Stevens>]