La définition des documents

La définition des champs d’un document permet de spécifier les types de ceux-ci ainsi que les analyseurs.

Les types d’Elasticsearch de base correspondent à ceux qu’on retrouve dans les langages de programmation (integer, boolean, etc…). Ils sont agrémentés de types plus complexes qui permettront des opérations avancées telles que des opérations géométriques.

Les analyseurs définissent les opérations d’indexation qu’Elasticsearch va effectuer sur les données du champ. Par exemple, des termes peuvent être extraits d’une chaine de caractères afin de pouvoir rapidement retrouver le document sur la recherche d’un des termes.

Par défaut, une chaine de caractères aura le type text et utilisera l’analyseur Standard Analyzer. Cet analyseur passe la chaine en minuscules et scinde la chaine en mots pour l’indexation.

Exemple de l’analyseur avec la requête :

POST _analyze
{
  "analyzer": "standard",
  "text": "Mon TAIlleur est riche !"
}

Les termes suivants seront extrait :

{
  "tokens": [
    {
      "token": "mon",
      "start_offset": 0,
      "end_offset": 3,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "tailleur",
      "start_offset": 4,
      "end_offset": 12,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "est",
      "start_offset": 13,
      "end_offset": 16,
      "type": "<ALPHANUM>",
      "position": 2
    },
    {
      "token": "riche",
      "start_offset": 17,
      "end_offset": 22,
      "type": "<ALPHANUM>",
      "position": 3
    }
  ]
}

Exemple de mapping d’un document avec Elastica :

<?php

// Create a type
$elasticaType = $elasticaIndex->getType('my_type');

// Define mapping
$mapping = new \Elastica\Type\Mapping();
$mapping->setType($elasticaType);

// Set mapping
$mapping->setProperties([
    'id'      => ['type' => 'integer', 'include_in_all' => false],
    'user'    => [
        'type' => 'object',
        'properties' => [
            'name'      => ['type' => 'text', 'include_in_all' => true],
            'full_name'  => ['type' => 'text', 'include_in_all' => true, 'boost' => 2]
        ],
    ],
    'description' => ['type' => 'text', 'analyzer' => 'standard', 'include_in_all' => true],
    'created_at'  => ['type' => 'date', 'include_in_all' => false]
]);

// Send mapping to type
$mapping->send();

Pour le champ description, l’analyseur a été défini, mais c’est optionnel car c’est celui par défaut. Dans les autres options, on retrouve :

  • include_in_all qui permet de définir si le champ sera présent ou non dans le mot clef _all d’ES
  • boost qui définit le poids du champ lors d’une recherche (poids de 1 par défaut). Par exemple, lors de la recherche d’un terme si pour un document A le terme apparait dans le champ full_name et pour un document B dans le champ description, le document A aura plus de poids et apparaitra avant le document B dans les résultats.

Les difficultés

Si vous ne définissez pas le mapping, il sera déduit des données du premier document. Or le format JSON ne spécifie pas les types complexes, par exemple, la représentation d’un entier est identique à celle d’un nombre flottant. Donc si dans le premier document vous envoyez 2, Elasticsearch déduira que c’est un champ avec des entiers, alors que vous utilisez des nombres flottants. Dans le second document si vous envoyez la valeur 3,3, elle sera convertie en entier.

Vous ne pouvez pas faire de recherche exacte sur un champ text avec l’analyseur standard car ces derniers permettent d’effectuer des recherches full text.

Si vous avez un document avec un champ email pour lequel vous n’avez pas défini de mapping, la requête suivante retournera une erreur :

<?php

use Elastica\Query;

// ...

$queryPart = new Query\BoolQuery();

$queryPart->addMustNot(new Query\Term([
    'email' => ['value' => 'email@host.com'],
]));

$query = Query::create($queryPart);

$myIndex->search($query);

Pour faire des recherches exactes, vous devez utiliser le type keyword.

Attention aux types, en effet, une recherche exacte sur un champ ayant des entiers en utilisant la chaine '1' ne retournera aucun résultat quand l’entier 1 vous retournera les documents recherchés. Dans Symfony, si vous récupérez un paramètre GET de la requête, il existe des méthodes dans la classe ParameterBag pour typer ces variables, exemple : $request->query->getInt('parameter_name'). En PHP, vous pouvez utiliser filter_var() (documentation).

Exemple de requêtes

Pour effectuer une recherche sur plusieurs champs. La classe MultiMatch vous permet de tout grouper :

<?php

use Elastica\Query;

// Exemple d'une recherche exacte sur plusieurs champs

$queryPart->addShould((new Query\MultiMatch())
    ->setFields(['equipementName', 'username', 'email'])
    ->setType('phrase')
    ->setQuery('YoRHa No.2 Type B')
);

Les gens font parfois des erreurs de saisie, donc ce que l’on souhaite c’est que rechercher anticonstitutionnellemet remonte des documents contenant anticonstitutionnellement ou anticonstitutionnellemetn. Elasticsearch nomme ces recherches fuzzy (documentation). Elles sont basées sur la Distance_de_Levenshtein qui est une notion de distance permettant de quantifier l’écart entre deux termes. Par exemple, anticonstitutionnellement et anticonstitutionnellemetn ont une distance de 1. Les recherches fuzzy sont effectuées en saisissant un paramètre fuzziness correspondant à la distance maximale acceptée, un entier ou ‘AUTO’ qui se base sur la longueur du terme.

<?php

use Elastica\Query;

// Exemple d'une recherche full text et fuzzy sur plusieurs champs

$queryPart->addShould((new Query\MultiMatch())
    ->setFields(['equipementDescription', 'description', 'text'])
    ->setQuery('2b is walking along with 9s and their pods.')
    ->setFuzziness('auto')
);

Pour aller plus loin

Les différentes manières de faire des requêtes de recherche est décrite dans la documentation d’ES.

Si les analyseurs par défaut ne vous conviennent pas, vous pouvez toujours créer le vôtre afin que les termes extraits correspondent à vos besoins.