Découvrir le FTS avec Postgres
Le champ de recherche unique, voilà le graal du formulaire de recherche d'une application.
Pour l'utilisateur, c'est avoir la recherche accessible à toutes les pages, s'épargner le temps de comprendre dans quel champs rentrer un terme recherché, c'est passer de la recherche d'un simple mot à la requête complexe sans changer d'interface.
Pour le développeur, c'est la simplification de l'évolution de l'interface à mesure que les données de l'application s'enrichissent et tout un tas de fonctionnalités avancées : surlignage des résultats, calcul de pertinence, performances, etc.
Pour le DBA, c'est éviter d'avoir pleins de requêtes inutiles parce que l'utilisateur s'est trompé de champs.
Quelques généralités
Postgres est livré avec une recherche plein texte performante et bien conçue. Dans cet article, nous allons tester le FTS Postgres sans même toucher au schéma !
La recherche plein texte n'est pas une technique d'indexation. C'est une technique de normalisation de texte en vue d'une recherche pertinente et efficace. Un texte normalisé est très facile à indexer, nous verrons cela plus tard.
La normalisation
Avant de jouer, assurons-nous d'avoir un Postgres récent, et quelques données:
$ docker run --detach --rm --publish 5432:5432 postgres:10-alpine
$ psql -h localhost -U postgres
psql (10.1)
Saisissez « help » pour l'aide.
postgres=# CREATE TABLE textes AS VALUES
('Chuck Norris a gagné la guerre du Golf, en 18 trous.'),
('Google, c''est le seul endroit où tu peux taper Chuck Norris...'),
('Chuck Norris mange ses oranges tout rond: Chuck Norris fait pas de quartier.');
SELECT 3
postgres=#
La table n'a aucun index ni aucune notion de FTS et pourtant nous allons faire de la recherche plein texte dessus, sans la modifier.
Dans Postgres, un texte normalisé est un tsvector. la fonction
to_tsvector(configuration, texte)
normalise un texte. Le paramètre
configuration
est important, il détermine comment Postgres doit analyser le
texte. Postgres dispose par défaut d'une configuration rudimentaire pour le
français : french
. Nous verrons plus tard comment faire notre propre
configuration. Voyons déjà ce que ça donne !
postgres=# SELECT to_tsvector('french', 'Chuck Norris a gagné la guerre du Golf, en 18 trous.');
to_tsvector
-------------------------------------------------------------------------
'18':10 'a':3 'chuck':1 'gagn':4 'golf':8 'guerr':6 'norr':2 'trous':11
(1 ligne)
postgres=#
Un vecteur est une liste de mots plus ou moins tronqués à leur racine, associés
à un nombre. Le processus pour passer d'un mot à sa racine est appelé
lemmatisation. Ainsi gagné
devient gagn
. Le nombre indique la place du mot dans le texte, cela permettra
de connaître la distance entre les mots par exemple. Les mots vides la
, du
et en
ont disparus. Malheureusement, trous
n'a pas perdu son s
, c'est une
erreur de lemmatisation.
Comparons avec la configuration par défaut :
postgres=# select to_tsvector('Chuck Norris a gagné la guerre du Golf, en 18 trous.');
to_tsvector
------------------------------------------------------------------------------------------
'18':10 'chuck':1 'du':7 'en':9 'gagné':4 'golf':8 'guerr':6 'la':5 'norri':2 'trous':11
(1 ligne)
Le résultat est très différent. Les mots vides sont toujours là. gagné
n'est
pas lemmatisé. La configuration est donc importante et doit faire l'objet d'un
soin particulier.
La recherche
Regardons maintenant la seconde fonction importante pour le FTS.
plainto_tsquery(configuration, text)
normalise une requête FTS au format
tsquery
.
postgres=# select plainto_tsquery('french', 'Chuck NORRIS gagne');
plainto_tsquery
---------------------------
'chuck' & 'norr' & 'gagn'
(1 ligne)
postgres=#
On retrouve des mots lemmatisés, avec des contraintes booléennes. Cette
recherche signifie contient les trois mots chuck, norris et gagne. La
recherche est appliquée avec l'opérateur @@
:
postgres=# SELECT column1 FROM textes WHERE plainto_tsquery('french', 'Chuck NORRIS gagner') @@ to_tsvector('french', column1);
column1
------------------------------------------------------
Chuck Norris a gagné la guerre du Golf, en 18 trous.
(1 ligne)
postgres=#
Bingo ! Notez que la requête contient gagner
à l'infinitif mais Postgres
trouve la correspondance avec le mot gagné
. Bravo, vous avez fait de la
recherche plein texte !
Côté performance, le code to_tsvector('french', column1)
provoque la
normalisation de toute la colonne. C'est une très mauvaise idée de
normaliser toute la table à chaque recherche ! C'est à l'écriture dans la base
qu'il faut normaliser le texte à chercher. Nous verrons les performances dans un
prochain article !
En conclusion, la recherche plein texte de Postgres est simple. Un développeur peut prototyper une recherche très rapidement ! N'hésitez pas à commenter pour orienter le sujet du prochain article !
Références
- Documentation Postgres de la recherche plein texte maintenu par Guillaume LELARGE de Dalibo, sur le site de l'association PostgreSQLFr.
- Comment fonctionne la recherche plein texte dans PostgreSQL ? par Adrien NAYRAT, de Dalibo également.