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
¶
La façon la plus simple 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
¶
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
¶
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.
Les termes SearchQuery
peuvent être combinés logiquement pour fournir plus de souplesse :
>>> from django.contrib.postgres.search import SearchQuery
>>> SearchQuery('potato') & SearchQuery('ireland') # potato AND ireland
>>> SearchQuery('potato') | SearchQuery('penguin') # potato OR penguin
>>> ~SearchQuery('sausage') # NOT sausage
Voir Modification de la configuration de recherche pour une explication du paramètre config
.
SearchRank
¶
Nous n’avons jusqu’ici que 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
.
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 dans lesquels vous recherchez sont contenus dans un modèle particulier, vous pouvez créer un index fonctionnel qui correspond au vecteur de recherche que vous souhaitez utiliser. La documentation de PostgreSQL contient des détails sur la création d’index pour la recherche plein texte.
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 de l’expression de recherche trigram_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
¶
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>]
TrigramDistance
¶
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>]