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 Making queries.
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>]