# Documentation Django 6.0.7

# 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")
    <QuerySet [<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")
    <QuerySet [<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")
    <QuerySet [<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, Lexeme
    >>> 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
    >>> SearchQuery(Lexeme("tomato") & (Lexeme("red") | Lexeme("green")))  # Lexeme objects

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`.

Changed in Django 6.0:

Les objets `Lexeme` ont été ajoutés.

## `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")
    <QuerySet [<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"))
    <QuerySet [<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")))
    <QuerySet [<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")

## `Lexeme`

New in Django 6.0.

class Lexeme(value, output_field=None, *, invert=False, prefix=False, weight=None)

Les objets `Lexeme` permettent d’utiliser sûrement des opérateurs de recherche avec des chaînes provenant de sources non fiables. Le contenu de chaque lexème est échappé afin que d’éventuels opérateurs dans le contenu ne soient pas interprétés.

Vous pouvez combiner des lexèmes entre eux en utilisant les opérateurs `&` and `|`, et également utiliser la négation avec l’opérateur `~`. Par exemple :

    >>> from django.contrib.postgres.search import SearchQuery, SearchVector, Lexeme
    >>> vector = SearchVector("body_text", "blog__tagline")
    >>> Entry.objects.annotate(search=vector).filter(
    ...     search=SearchQuery(Lexeme("fruit") & Lexeme("dessert"))
    ... )
    <QuerySet [<Entry: Apple Crumble Recipes>, <Entry: Banana Split Recipes>]>

    >>> Entry.objects.annotate(search=vector).filter(
    ...     search=SearchQuery(Lexeme("fruit") & Lexeme("dessert") & ~Lexeme("banana"))
    ... )
    <QuerySet [<Entry: Apple Crumble Recipes>]>

Les objets `Lexeme` prennent aussi en charge la pondération des termes et les préfixes :

    >>> Entry.objects.annotate(search=vector).filter(
    ...     search=SearchQuery(Lexeme("Pizza") | Lexeme("Cheese"))
    ... )
    <QuerySet [<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]>
    >>> Entry.objects.annotate(search=vector).filter(
    ...     search=SearchQuery(Lexeme("Pizza") | Lexeme("Cheese", weight="A"))
    ... )
    <QuerySet [<Entry: Pizza recipes>]>
    >>> Entry.objects.annotate(search=vector).filter(
    ...     search=SearchQuery(Lexeme("za", prefix=True))
    ... )
    <QuerySet []>

## 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.

In the event that all the fields you’re querying on are contained within one particular model, you can create a functional `GinIndex` or `GistIndex` which matches the search vector you wish to use. For example:

    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")
    <QuerySet [<Entry: Cheese on Toast recipes>, <Entry: Pizza recipes>]>

### Using `GeneratedField`

An alternative method is to use a `GeneratedField`, which has PostgreSQL maintain the column rather than a trigger:

    from django.contrib.postgres.indexes import GinIndex
    from django.contrib.postgres.search import SearchVector, SearchVectorField
    from django.db import models

    class Entry(models.Model):
        headline = models.CharField(max_length=255)
        body_text = models.TextField()

        search_vector = models.GeneratedField(
            expression=SearchVector("headline", weight="A", config="english")
            + SearchVector("body_text", weight="B", config="english"),
            output_field=SearchVectorField(),
            db_persist=True,
        )

        class Meta:
            indexes = [GinIndex(fields=["search_vector"])]

## 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")
    <QuerySet [<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")
    <QuerySet [<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")
    <QuerySet [<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")
    <QuerySet [<Author: Katy Stevens>]>

### `TrigramStrictWordDistance`

class TrigramStrictWordDistance(string, expression, **extra)

Accepte une chaîne ou une expression, ainsi qu’un nom de champ ou une expression. Renvoie la distance par trigramme par mot strict entre les deux paramètres.