Solr : sélection des données

Implémenter Solr dans une application web Java

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
Jusque là on abordait les concepts liés à la maintenance des indexes Solr. Cependant aucun moteur de recherche ne peut fonctionner sans la récupération des données. C'est elle qu'on va expliquer dans cet article.

Tout au long de cet article on verra comment est construite une requête de sélection. On passera des conditions simples (par exemple id égal 3) pour en terminer sur des conditions complexes (coordonnées géographiques, SQL LIKE "%%"). A la fin on verra quels moyens nous offre Solr pour créer une interface utilisateur aimable.

Comment chercher dans Solr ?
Comme on a pu voir dans les articles précédents, la requête de Solr est compris dans le paramètre "q" de l'URL. La construction d'une condition prend la forme suivante : "champ:valeur", tout comme le principe de l'index inversé.

Regardons maintenant les requêtes. Chaque requête sera précédée par un court texte explicatif qui dira ce qu'on veut retrouver :

  1. Le pays dont le nom est "France" : countryName:France

  2. Le pays dont le nom est "France", "Italy" ou "Poland" : (countryName:France)OR(countryName:Italy)OR(countryName:Poland)

  3. Les pays dont le nom commence par "A" : countryName:A*. Cette condition récupère tous les pays dont un des mots composants le nom commence par A. Du coup, dans le résultat on retrouvera aussi bien "Andorra", que "United Arab Emirates" ou "Bonaire, Saint Eustatius and Saba". Ce résultat est provoqué par l'indexation qui a séparé tous ces pays en plusieurs mots. Et ainsi le pays "United Arab Emirates" a été indexé pour les phrases "united", "arab" et "emirates". On peut en déduire facilement que pour ne plus voir ce pays apparaître dans les résultats pour la phrase "countryName:A*", on doit enlever les espaces blancs avant l'indexation. Pour ce faire, il faut d'abord remplacer notre <tokenizer class="solr.StandardTokenizerFactory"/> par <tokenizer class="solr.KeywordTokenizerFactory"/>. Grâce à cela, aucune tokenisation n'est faite. Du coup chaque chaîne de caractères soumise est un seul token. Autrement dit, notre "United Arab Emirates" reste "United Arab Emirates". Un seul token est créé à la place des trois (united, arab et emirates).Après une nouvelle indexation (command=full-import), déjà avec KeywordTokenizerFactory, et une nouvelle requête de sélection, on s'aperçoit que le nombre de résultats a diminué. De 34 il est passé à 16. Parmi ces 16 pays, tous commencent avec A.Au titre d'un rappel, StandardTokenizerFactory construit des tokens en se basant sur les séparateurs spécifiés dans le dictionnaire. Un des séparateurs spécifiés est l'espace blanc. C'est la raison pour laquelle avant on indexait 3 mots pour "United Arab Emirates".Les requêtes LIKE du Solr s'appellent des requêtes wildcard.

  4. Les pays dont la superficie est à la superficie de la France (547030) : area:[547031 TO *]. La traduction au langage parlé est : récupère les pays dont la superficie est de 547031 à l'infini (*, n'importe quelle valeur). Et si l'on veut récupérer les pays plus petits que la France ? Il suffit d'inverser la condition (area:[* TO 547029

  5. Les pays dont le nom ne commence pas par A* : -countryName:A*. On s'aperçoit que la négation se fait en précédant le champ par un signe moins (-). Si l'on analyse le nombre de résultats (237 sur le total de 253 documents, vu que 16 pays commencent par A), on s'aperçoit que la requête fonctionne.



Les requêtes complexes dans Solr
Cependant, les simples requêtes clé:valeur ne sont pas les seules qu'on peut réaliser avec Solr. On peut aller plus loin et, par exemple, utiliser les fonctions pour récupérer les données intéressant. On s'occupera de cela dans cette partie :

  1. Les pays dont la superficie est supérieur à 1 000 000 et dont le nom commencer par "G" ou dont le continent est "AF" (Afrique) : area:[1000000 TO *]AND((countryName:G*)OR(continent:AF)). On y retrouve le même principe que dans les sous-requêtes SQL. La partie séparée par les parenthèses groupe une condition composée des 2 autres conditions. Parmi ces 2 autres clauses, il suffit qu'une seule soit valable.

  2. Les pays dont le nom est égal ou ressemble à "Argentina" : countryName:Argentina~0.4. Solr (ou plutôt Lucene), utilise des algorithmes de ressemblance des mots (l'algorithme de la distance Levensthein). En spécifiant ~ et la valeur comprise entre 0 et 1, on peut trouver les documents indexés qui ressemblent à la phrase recherchée. Plus la valeur numérique est grand, plus le niveau de ressemblence est grand. La valeur par défaut est 0.5. Dans notre exemple, Solr a retourné : Argentina, Greenland et Armenia.

  3. Les villes françaises aux alentours de Paris (distance 10 km) : sfield=latLong&pt=2.34,48.87&d=10&fq={!bbox}. Pour voir cette requête marcher, il faut faire quelques modifications dans les fichiers de configuration. Les champs avec les coordonnées géographiques (latitude et longitude), doivent être concaténés en un seul (logLat). Ensuite un nouveau type de champs doit être défini (solr.LatLonType). On doit égalemenr préciser un champ "dynamique" (<dynamicField />). Son rôle est de décomposer "in the fly" la paire latitude-longitude, séparé par un virgule dans le champ normal. Regardons notre configuration après ces modifications :
    schema.xml

    <fields>
    <!-- latitude-longitude field -->
    <field name="latLong" type="location" indexed="true" stored="true" />
    <dynamicField name="*_coordinate" type="tdouble" indexed="true" stored="false"/>
    </fields>

    <types>
    <fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate" />
    <fieldType name="tdouble" class="solr.TrieDoubleField" precisionStep="8" positionIncrementGap="0"/>
    </types>

    data-config.xml

    <document name="cities">
    <entity name="cities" query="SELECT co.*, ci.id AS cityId, ci.name AS cityName, ci.postal_code, ci.latitude, ci.longitude, co.name AS countryName, CONCAT_WS(',',
    ci.latitude, ci.longitude) AS latLong FROM cities ci JOIN countries co ON co.id = ci.countries_id WHERE co.name = 'France' AND
    ci.longitude IS NOT NULL AND ci.latitude IS NOT NULL">
    <field column="cityId" name="id" />
    <field column="cityName" name="cityName" />
    <field column="countryName" name="countryName" />
    <field column="postal_code" name="postalCode" />
    <field column="latitude" name="latitude" />
    <field column="longitude" name="longitude" />
    <field column="latLong" name="latLong" />
    <field column="iso" name="iso" />
    <field column="iso_numeric" name="isoNumeric" />
    <field column="capital" name="capital" />
    <field column="area" name="area" />
    <field column="population" name="population" />
    <field column="continent" name="continent" />
    <field column="currency" name="currency" />
    <field column="currency_code" name="currencyCode" />
    <field column="postal_code_format" name="postalCodeFormat" />
    <field column="postal_code_regex" name="postalCodeRegex" />
    </entity>
    </document>

    Après ces modifications il faut bien sûr reindexer les documents avec la commande full-import pour le document cities (décommenter cette partie et commenter les 2 autres - pour éviter l'exception "java.lang.OutOfMemoryError: Java heap space", essayez de jouer avec la taille des villes récupérées). Maintenant on peut passer à l'explication de la requête.

    On remarque le rajout des paramètres. En ce qui concerne le "q", il peut prendre n'importe quelle forme. On a donc rajouté le paramètre "sfield". C'est lui qui indique le champ stockant les coordonnées géographiques. Le paramètre "pt" renseigne l'endroit à partir duquel on veut chercher les éléments (coordonnées de Paris). Le dernier parmaètre, "d", est l'abbreviation du mot "distance". Il signifie donc, en kilomètres, la distance entre le point spécifié dans "pt" et les éléments indexés dans le champ "sfield". Si l'on rajoute le pramètre debugQuery, on verra l'utilisation des champs dynamiques : parsed_filter_queries":["+latLong_0_coordinate:[2.250067966450711 TO 2.429932033549289] +latLong_1_coordinate:[48.779992912755056 TO 48.960007087244946]"]. En traduisant cette ligne, on peut dire que deux champs ont été créés pour les besoins de la requête : latLong_0, représentant la latitude et latLong_1 pour la longitude. Ensuite on retrouve une condition déjà expliquée "x TO y". Les deux conditions sont concaténées avec un signe de plus (+).
    Le dernier paramètre, pas concerné par les conditions de la requête, est "fq={!bbox}". Il indique l'utilisation d'un filtre, en occurrence bbox (Bounding Box Filter). Ce filtre de la requête réduit le nombre des documents trouvés via la condition "q".



Options supplémentaires à la recherche Solr
La recherche de Solr ne signifie pas que la récupérations des documents. A part cela, plusieurs d'autres options sont mises à disposition des développeurs pour construire un moteur de recherche pertinant. Voici les principales :

  • la pagination : grâce aux paramètres start et rows, on peut facilement reproduire le comportement avec LIMIT OFFSET du SQL. Le paramètre start indique le début des résultats (commence par 0). Le paramètre rows spécifie le nombre de documents à afficher. Pour l'illustrer, reprenons notre dernier résultat, celui avec Argentina~0.4. On avait 3 résultats. Si l'on rajoute à l'URL de la recherche "rows=1&start=0", on verra le résultat le plus pertinant (Argentina). Ensuite, en changeant start=0 à start=1, on verra le deuxième pays (Greenland). En faisant la dernière modification (start=2), Solr nous retournera le dernier résultat (Armenia).

  • le tri : la reproduction du fameux ORDER BY du SQL est possible dans Solr. Le tri se fait avec la paramètre sort qui prend les mêmes valeurs que la clause ORDER BY du SQL, à savoir le champ et la méthode de tri (asc ou desc). En voulant trier les pays par nom, dans l'ordre alphabetique, il suffirait de faire "sort=countryName asc". Pour rajouter le tri par la superficie (du plus grand au plus petit), il faudrait faire : "sort=countryName asc, area desc"



Plusieurs points abordés dans cet article ont démontré la puissance de Solr en tant qu'un moteur de recherche. On peut non seulement faire des conditions simples sous le modèle "clé:valeur", mais aussi celles plus complexes. On peut même aller jusqu'au développement d'un moteur de recherche géo-localisé.
Bartosz KONIECZNY 08-09-2013 17:25 Solr
Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil Symfony2

Un problème filemtime()

Si pendant le développement de votre projet Symfony2 vous rencontrez un problème avec fonction filemtime, il peut s'agir d'un dysfonctionnement temporaire. Warning: filemtime() [function.filemtime]: stat failed for C:\Program Files (x86)\EasyPHP-5.3.5.0\www\gagu\src\Bun\DleBundle/Resources/views/Bundle/show.html.php in C:\Program Files (x86)\EasyPHP-5.3.5.0\www\appli\app\cache\dev\classes.php line 2064 Pour résoudre ce problème, vous pouvez être amenés à supprimer le cache du répertoire dev (si vous êtes en mode développement).