forked from gleu/pgdocs_fr
-
Notifications
You must be signed in to change notification settings - Fork 0
/
textsearch.xml
3768 lines (3237 loc) · 142 KB
/
textsearch.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="UTF-8"?>
<!-- Dernière modification
le $Date$
par $Author$
révision $Revision$ -->
<chapter id="textsearch">
<title id="textsearch-title">Recherche plein texte</title>
<indexterm zone="textsearch">
<primary>recherche plein texte</primary>
</indexterm>
<indexterm zone="textsearch">
<primary>recherche de texte</primary>
</indexterm>
<sect1 id="textsearch-intro">
<title>Introduction</title>
<para>
La recherche plein texte (ou plus simplement la <firstterm>recherche de
texte</firstterm>) permet de sélectionner des
<firstterm>documents</firstterm> en langage naturel qui satisfont une
<firstterm>requête</firstterm> et, en option, de les trier par intérêt
suivant cette requête. Le type le plus fréquent de recherche concerne la
récupération de tous les documents contenant les <firstterm>termes de
recherche</firstterm> indiqués et de les renvoyer dans un ordre dépendant
de leur <firstterm>similarité</firstterm> par rapport à la requête. Les
notions de
<varname>requête</varname> et de <varname>similarité</varname> peuvent
beaucoup varier et dépendent de l'application réelle. La recherche
la plus simple considère une <varname>requête</varname> comme un
ensemble de mots et la <varname>similarité</varname> comme la fréquence
des mots de la requête dans le document.
</para>
<para>
Les opérateurs de recherche plein texte existent depuis longtemps dans
les bases de données. <productname>PostgreSQL</productname> dispose des
opérateurs <literal>~</literal>, <literal>~*</literal>,
<literal>LIKE</literal> et <literal>ILIKE</literal> pour les types de
données texte, mais il lui manque un grand nombre de propriétés essentielles
requises par les systèmes d'information modernes :
</para>
<itemizedlist spacing="compact" mark="bullet">
<listitem>
<para>
Aucun support linguistique, même pour l'anglais. Les expressions
rationnelles ne sont pas suffisantes car elles ne peuvent pas gérer
facilement les mots dérivées, par exemple <literal>satisfait</literal> et
<literal>satisfaire</literal>. Vous pouvez laisser passer des documents
qui contiennent <literal>satisfait</literal> bien que vous souhaiteriez
quand même les trouver avec une recherche sur
<literal>satisfaire</literal>.
Il est possible d'utiliser <literal>OR</literal> pour rechercher plusieurs
formes dérivées mais cela devient complexe et augmente le risque d'erreur
(certains mots peuvent avoir des centaines de variantes).
</para>
</listitem>
<listitem>
<para>
Ils ne fournissent aucun classement (score) des résultats de la recherche,
ce qui les rend inefficaces quand des centaines de documents correspondants
sont trouvés.
</para>
</listitem>
<listitem>
<para>
Ils ont tendance à être lent car les index sont peu supportés, donc ils
doivent traiter tous les documents à chaque recherche.
</para>
</listitem>
</itemizedlist>
<para>
L'indexage pour la recherche plein texte permet au document d'être
<emphasis>pré-traité</emphasis> et qu'un index de ce pré-traitement soit
sauvegardé pour une recherche ultérieure plus rapide. Le pré-traitement
inclut :
</para>
<itemizedlist mark="none">
<listitem>
<para>
<emphasis>Analyse des documents en <firstterm>jetons</firstterm></emphasis>.
Il est utile d'identifier les différentes classes de jetons, c'est-à-dire
nombres, mots, mots complexes, adresses email, pour qu'ils puissent être
traités différemment. En principe, les classes de jeton dépendent de
l'application mais, dans la plupart des cas, utiliser un ensemble prédéfinie
de classes est adéquat.
<productname>PostgreSQL</productname> utilise un
<firstterm>analyseur</firstterm> pour réaliser cette étape. Un analyseur
standard est fourni, mais des analyseurs personnalisés peuvent être écrits
pour des besoins spécifiques.
</para>
</listitem>
<listitem>
<para>
<emphasis>Conversion des jetons en <firstterm>lexemes</firstterm></emphasis>.
Un lexeme est une chaîne, identique à un jeton, mais elle a été
<firstterm>normalisée</firstterm> pour que différentes formes du même mot
soient découvertes. Par exemple, la normalisation inclut pratiquement toujours
le remplacement des majuscules par des minuscules, ainsi que la suppression
des suffixes (comme <literal>s</literal> ou <literal>es</literal> en anglais).
Ceci permet aux recherches de trouver les variantes du même mot, sans avoir
besoin de saisir toutes les variantes possibles. De plus, cette étape
élimine typiquement les <firstterm>termes courants</firstterm>, qui sont
des mots si courants qu'il est inutile de les rechercher. Donc, les
jetons sont des fragments bruts du document alors que les lexemes sont des
mots supposés utiles pour l'indexage et la recherche.
<productname>PostgreSQL</productname> utilise des
<firstterm>dictionnaires</firstterm> pour réaliser cette étape.
Différents dictionnaires standards sont fournis et des dictionnaires
personnalisés peuvent être créés pour des besoins spécifiques.
</para>
</listitem>
<listitem>
<para>
<emphasis>Stockage des documents pré-traités pour optimiser la recherche
</emphasis>. Chaque document peut être représenté comme un
tableau trié de lexemes normalisés. Avec ces lexemes, il est souvent
souhaitable de stocker des informations de position à utiliser pour
obtenir un <firstterm>score de proximité</firstterm>, pour qu'un document
qui contient une région plus <quote>dense</quote> des mots de la requête
se voit affecté un score plus important qu'un document qui en a moins.
</para>
</listitem>
</itemizedlist>
<para>
Les dictionnaires autorisent un contrôle fin de la normalisation des jetons.
Avec des dictionnaires appropriés, vous pouvez :
</para>
<itemizedlist spacing="compact" mark="bullet">
<listitem>
<para>
Définir les termes courants qui ne doivent pas être indexés.
</para>
</listitem>
<listitem>
<para>
Établir une liste des synonymes pour un simple mot en utilisant
<application>Ispell</application>.
</para>
</listitem>
<listitem>
<para>
Établir une correspondance entre des phrases et un simple mot en utilisant
un thésaurus.
</para>
</listitem>
<listitem>
<para>
Établir une correspondance entre différentes variations d'un mot et une
forme canonique en utilisant un dictionnaire <application>Ispell</application>.
</para>
</listitem>
<listitem>
<para>
Établir une correspondance entre différentes variations d'un mot et une
forme canonique en utilisant les règles du stemmer
<application>Snowball</application>.
</para>
</listitem>
</itemizedlist>
<para>
Un type de données <type>tsvector</type> est fourni pour stocker les documents
pré-traités, avec un type <type>tsquery</type> pour représenter les requêtes
traitées (<xref linkend="datatype-textsearch"/>). Il existe beaucoup de
fonctions et d'opérateurs disponibles pour ces types de données
(<xref linkend="functions-textsearch"/>), le plus important étant l'opérateur
de correspondance <literal>@@</literal>, dont nous parlons dans la
<xref linkend="textsearch-matching"/>. Les recherches plein texte peuvent
être accélérées en utilisant des index (<xref
linkend="textsearch-indexes"/>).
</para>
<sect2 id="textsearch-document">
<title>Qu'est-ce qu'un document ?</title>
<indexterm zone="textsearch-document">
<primary>document</primary>
<secondary>recherche de texte</secondary>
</indexterm>
<para>
Un <firstterm>document</firstterm> est l'unité de recherche dans un système
de recherche plein texte, par exemple un article de magazine ou un
message email. Le moteur de recherche plein texte doit être capable
d'analyser des documents et de stocker les associations de lexemes (mots
clés) avec les documents parents. Ensuite, ces associations seront utilisées
pour rechercher les documents contenant des mots de la requête.
</para>
<para>
Pour les recherches dans <productname>PostgreSQL</productname>, un
document est habituellement un champ texte à l'intérieur d'une ligne d'une
table de la base ou une combinaison (concaténation) de champs, parfois
stockés dans différentes tables ou obtenus dynamiquement. En d'autres
termes, un document peut être construit à partir de différentes parties
pour l'indexage et il peut ne pas être stocké quelque part. Par
exemple :
<programlisting>
SELECT titre || ' ' || auteur || ' ' || resume || ' ' || corps AS document
FROM messages
WHERE mid = 12;
SELECT m.titre || ' ' || m.auteur || ' ' || m.resume || ' ' || d.corps AS document
FROM messages m, docs d
WHERE mid = did AND mid = 12;
</programlisting>
</para>
<note>
<para>
En fait, dans ces exemples de requêtes, <function>coalesce</function>
devrait être utilisé pour empêcher un résultat <literal>NULL</literal> pour
le document entier à cause d'une seule colonne <literal>NULL</literal>.
</para>
</note>
<para>
Une autre possibilité est de stocker les documents dans de simples fichiers
texte du système de fichiers. Dans ce cas, la base est utilisée pour
stocker l'index de recherche plein texte et pour exécuter les recherches, et
un identifiant unique est utilisé pour retrouver le document sur le
système de fichiers. Néanmoins, retrouver les fichiers en dehors de la base
demande les droits d'un superutilisateur ou le support de fonctions spéciales,
donc c'est habituellement moins facile que de conserver les données dans
<productname>PostgreSQL</productname>. De plus, tout conserver dans la base
permet un accès simple aux méta-données du document pour aider l'indexage
et l'affichage.
</para>
<para>
Dans le but de la recherche plein texte, chaque document doit être réduit
au format de pré-traitement, <type>tsvector</type>. La recherche et le
calcul du score sont réalisés entièrement à partir de la représentation
<type>tsvector</type>
d'un document — le texte original n'a besoin d'être retrouvé que
lorsque le document a été sélectionné pour être montré à l'utilisateur.
Nous utilisons souvent <type>tsvector</type> pour le document mais, bien
sûr, il ne s'agit que d'une représentation compacte du document complet.
</para>
</sect2>
<sect2 id="textsearch-matching">
<title>Correspondance de base d'un texte</title>
<para>
La recherche plein texte dans <productname>PostgreSQL</productname> est
basée sur l'opérateur de correspondance <literal>@@</literal>, qui renvoie
<literal>true</literal> si un <type>tsvector</type> (document) correspond
à un <type>tsquery</type> (requête). Peu importe le type de données indiqué
en premier :
<programlisting>
SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector @@ 'cat & rat'::tsquery;
?column?
----------
t
SELECT 'fat & cow'::tsquery @@ 'a fat cat sat on a mat and ate a fat rat'::tsvector;
?column?
----------
f
</programlisting>
</para>
<para>
Comme le suggère l'exemple ci-dessus, un <type>tsquery</type> n'est pas un
simple texte brut, pas plus qu'un <type>tsvector</type> ne l'est. Un
<type>tsquery</type> contient des termes de recherche qui doivent déjà être
des lexemes normalisés, et peut combiner plusieurs termes en utilisant les
opérateurs AND, OR et NOT. (Pour les détails, voir la <xref
linkend="datatype-textsearch"/>.) Les fonctions <function>to_tsquery</function>
et <function>plainto_tsquery</function> sont utiles pour convertir un texte
écrit par un utilisateur dans un <type>tsquery</type> correct, par exemple
en normalisant les mots apparaissant dans le texte. De façon similaire,
<function>to_tsvector</function> est utilisé pour analyser et normaliser un
document. Donc, en pratique, une correspondance de recherche ressemblerait
plutôt à ceci :
<programlisting>
SELECT to_tsvector('fat cats ate fat rats') @@ to_tsquery('fat & rat');
?column?
----------
t
</programlisting>
Observez que cette correspondance ne réussit pas si elle est écrite
ainsi :
<programlisting>
SELECT 'fat cats ate fat rats'::tsvector @@ to_tsquery('fat & rat');
?column?
----------
f
</programlisting>
car ici aucune normalisation du mot <literal>rats</literal> n'interviendra.
Les éléments d'un <type>tsvector</type> sont des lexemes, qui sont
supposés déjà normalisés, donc <literal>rats</literal> ne correspond pas à
<literal>rat</literal>.
</para>
<para>
L'opérateur <literal>@@</literal> supporte aussi une entrée de type
<type>text</type>, permettant l'oubli de conversions explicites de text vers
<type>tsvector</type> ou <type>tsquery</type> dans les cas simples. Les
variantes disponibles sont :
<programlisting>
tsvector @@ tsquery
tsquery @@ tsvector
text @@ tsquery
text @@ text
</programlisting>
</para>
<para>
Nous avons déjà vu les deux premières. La forme
<type>text</type> <literal>@@</literal> <type>tsquery</type> est
équivalent à <literal>to_tsvector(x) @@ y</literal>.
La forme <type>text</type> <literal>@@</literal> <type>text</type>
est équivalente à <literal>to_tsvector(x) @@ plainto_tsquery(y)</literal>.
</para>
</sect2>
<sect2 id="textsearch-intro-configurations">
<title>Configurations</title>
<para>
Les exemples ci-dessus ne sont que des exemples simples de recherche plein
texte. Comme mentionné précédemment, la recherche plein texte permet de
faire beaucoup plus : ignorer l'indexation de certains mots (termes
courants), traiter les synonymes et utiliser une analyse sophistiquée,
c'est-à-dire une analyse basée sur plus qu'un espace blanc. Ces
fonctionnalités sont contrôlées par les <firstterm>configurations de
recherche plein texte</firstterm>. <productname>PostgreSQL</productname>
arrive avec des configurations prédéfinies pour de nombreux langages et
vous pouvez facilement créer vos propres configurations (la commande
<command>\dF</command> de <application>psql</application> affiche toutes
les configurations disponibles).
</para>
<para>
Lors de l'installation, une configuration appropriée est sélectionnée et
<xref linkend="guc-default-text-search-config"/> est configuré dans
<filename>postgresql.conf</filename> pour qu'elle soit utilisée par défaut.
Si vous utilisez la même configuration de recherche plein texte pour le
cluster entier, vous pouvez utiliser la valeur de
<filename>postgresql.conf</filename>. Pour utiliser différentes configurations
dans le cluster mais avec la même configuration pour une base, utilisez
<command>ALTER DATABASE ... SET</command>. Sinon, vous pouvez configurer
<varname>default_text_search_config</varname> dans chaque session.
</para>
<para>
Chaque fonction de recherche plein texte qui dépend d'une configuration a
un argument <type>regconfig</type> en option, pour que la configuration
utilisée puisse être précisée explicitement.
<varname>default_text_search_config</varname> est seulement utilisé quand
cet argument est omis.
</para>
<para>
Pour rendre plus facile la construction de configurations de recherche
plein texte, une configuration est construite à partir d'objets de la
base de données. La recherche plein texte de
<productname>PostgreSQL</productname> fournit quatre types d'objets
relatifs à la configuration :
</para>
<itemizedlist spacing="compact" mark="bullet">
<listitem>
<para>
Les <firstterm>analyseurs de recherche plein texte</firstterm> cassent les
documents en jetons et classifient chaque jeton (par exemple, un mot ou
un nombre).
</para>
</listitem>
<listitem>
<para>
Les <firstterm>dictionnaires de recherche plein texte</firstterm>
convertissent les jetons en une forme normalisée et rejettent les termes
courants.
</para>
</listitem>
<listitem>
<para>
Les <firstterm>modèles de recherche plein texte</firstterm> fournissent
les fonctions nécessaires aux dictionnaires. (Un dictionnaire spécifie
uniquement un modèle et un ensemble de paramètres pour ce modèle.)
</para>
</listitem>
<listitem>
<para>
Les <firstterm>configurations de recherche plein texte</firstterm>
sélectionnent un analyseur et un ensemble de dictionnaires à utiliser
pour normaliser les jetons produit par l'analyseur.
</para>
</listitem>
</itemizedlist>
<para>
Les analyseurs de recherche plein texte et les modèles sont construits à
partir de fonctions bas niveau écrites en C ; du coup, le
développement de nouveaux analyseurs ou modèles nécessite des connaissances
en langage C, et les droits superutilisateur pour les installer dans une
base de données. (Il y a des exemples d'analyseurs et de modèles en addon
dans la partie <filename>contrib/</filename> de la distribution
<productname>PostgreSQL</productname>.) Comme les dictionnaires et les
configurations utilisent des paramètres et se connectent aux analyseurs et
modèles, aucun droit spécial n'est nécessaire pour créer un nouveau
dictionnaire ou une nouvelle configuration. Les exemples de création de
dictionnaires et de configurations personnalisés seront présentés plus tard
dans ce chapitre.
</para>
</sect2>
</sect1>
<sect1 id="textsearch-tables">
<title>Tables et index</title>
<para>
Les exemples de la section précédente illustrent la correspondance plein
texte en utilisant des chaînes simples. Cette section montre comment
rechercher les données de la table, parfois en utilisant des index.
</para>
<sect2 id="textsearch-tables-search">
<title>Rechercher dans une table</title>
<para>
Il est possible de faire des recherches plein texte sans index. Une requête
qui ne fait qu'afficher le champ <structname>title</structname> de chaque
ligne contenant le mot <literal>friend</literal> dans son champ
<structfield>body</structfield> ressemble à ceci :
<programlisting>
SELECT title
FROM pgweb
WHERE to_tsvector('english', body) @@ to_tsquery('english', 'friend');
</programlisting>
Ceci trouve aussi les mots relatifs comme <literal>friends</literal>
et <literal>friendly</literal> car ils ont tous la même racine, le même
lexeme normalisé.
</para>
<para>
La requête ci-dessus spécifie que la configuration
<literal>english</literal> doit être utilisée pour analyser et normaliser
les chaînes. Nous pouvons aussi omettre les paramètres de
configuration :
<programlisting>
SELECT title
FROM pgweb
WHERE to_tsvector(body) @@ to_tsquery('friend');
</programlisting>
Cette requête utilisera l'ensemble de configuration indiqué par <xref
linkend="guc-default-text-search-config"/>.
</para>
<para>
Un exemple plus complexe est de sélectionner les dix documents les plus
récents qui contiennent les mots <literal>create</literal> et
<literal>table</literal> dans les champs <structname>title</structname> ou
<structname>body</structname> :
<programlisting>
SELECT title
FROM pgweb
WHERE to_tsvector(title || ' ' || body) @@ to_tsquery('create & table')
ORDER BY last_mod_date DESC LIMIT 10;
</programlisting>
Pour plus de clareté, nous omettons la fonction <function>coalesce</function>
qui est nécessaire pour trouver les lignes contenant <literal>NULL</literal>
dans un des deux champs.
</para>
<para>
Bien que ces requêtes fonctionnent sans index, la plupart des applications
trouvent cette approche trop lente, sauf peut-être pour des recherches
occasionnelles. Une utilisation pratique de la recherche plein texte réclame
habituellement la création d'un index.
</para>
</sect2>
<sect2 id="textsearch-tables-index">
<title>Créer des index</title>
<para>
Nous pouvons créer un index <acronym>GIN</acronym> (<xref
linkend="textsearch-indexes"/>) pour accélérer les recherches plein
texte :
<programlisting>
CREATE INDEX pgweb_idx ON pgweb USING gin(to_tsvector('english', body));
</programlisting>
Notez que la version à deux arguments de <function>to_tsvector</function>
est utilisée. Seules les fonctions de recherche plein texte qui spécifient
un nom de configuration peuvent être utilisées dans les index sur des
expressions
(<xref linkend="indexes-expressional"/>). Ceci est dû au fait que le contenu
de l'index ne doit pas être affecté par <xref
linkend="guc-default-text-search-config"/>. Dans le cas contraire, le
contenu de l'index peut devenir incohérent parce que différentes entrées
pourraient contenir des <type>tsvector</type> créés avec différentes
configurations de recherche plein texte et qu'il ne serait plus possible de
deviner à quelle configuration fait référence une entrée. Il serait
impossible de sauvegarder et restaurer correctement un tel index.
</para>
<para>
Comme la version à deux arguments de <function>to_tsvector</function> a
été utilisée dans l'index ci-dessus, seule une référence de la requête
qui utilise la version à deux arguments de <function>to_tsvector</function>
avec le même nom de configuration utilise cet index. C'est-à-dire que
<literal>WHERE to_tsvector('english', body) @@ 'a & b'</literal> peut
utiliser l'index, mais <literal>WHERE to_tsvector(body) @@ 'a & b'</literal>
ne le peut pas. Ceci nous assure qu'un index est seulement utilisé avec la
même configuration que celle utilisée pour créer les entrées de l'index.
</para>
<para>
Il est possible de configurer des index avec des expressions plus complexes
où le nom de la configuration est indiqué dans une autre colonne. Par
exemple :
<programlisting>
CREATE INDEX pgweb_idx ON pgweb USING gin(to_tsvector(config_name, body));
</programlisting>
où <literal>config_name</literal> est une colonne de la table
<literal>pgweb</literal>. Ceci permet l'utilisation de configuration
mixe dans le même index tout en enregistrant la configuration utilisée
pour chaque entrée d'index. Ceci est utile dans le cas d'une bibliothèque
de documents dans différentes langues. Encore une fois, les requêtes
voulant utiliser l'index doivent être écrites pour correspondre à
l'index, donc
<literal>WHERE to_tsvector(config_name, body) @@ 'a & b'</literal>.
</para>
<para>
Les index peuvent même concaténer des colonnes :
<programlisting>
CREATE INDEX pgweb_idx ON pgweb USING gin(to_tsvector('english', title || ' ' || body));
</programlisting>
</para>
<para>
Une autre approche revient à créer une colonne <type>tsvector</type>
séparée pour contenir le résultat de <function>to_tsvector</function>. Cet
exemple est une concaténation de <literal>title</literal> et
<literal>body</literal>, en utilisant <function>coalesce</function> pour
s'assurer qu'un champ est toujours indexé même si l'autre vaut
<literal>NULL</literal> :
<programlisting>
ALTER TABLE pgweb ADD COLUMN textsearchable_index_col tsvector;
UPDATE pgweb SET textsearchable_index_col =
to_tsvector('english', coalesce(title,'') || ' ' || coalesce(body,''));
</programlisting>
Puis nous créons un index <acronym>GIN</acronym> pour accélérer la
recherche :
<programlisting>
CREATE INDEX textsearch_idx ON pgweb USING gin(textsearchable_index_col);
</programlisting>
Maintenant, nous sommes prêt pour des recherches plein texte rapides :
<programlisting>
SELECT title
FROM pgweb
WHERE textsearchable_index_col @@ to_tsquery('create & table')
ORDER BY last_mod_date DESC LIMIT 10;
</programlisting>
</para>
<para>
Lors de l'utilisation d'une colonne séparée pour stocker la représentation
<type>tsvector</type>, il est nécessaire d'ajouter un trigger pour
obtenir une colonne <type>tsvector</type> à jour à tout moment suivant les
modifications de <literal>title</literal> et <literal>body</literal>. La
<xref linkend="textsearch-update-triggers"/> explique comment le faire.
</para>
<para>
Un avantage de l'approche de la colonne séparée sur un index par
expression est qu'il n'est pas nécessaire de spécifier explicitement la
configuration de recherche plein texte dans les requêtes pour utiliser
l'index. Comme indiqué dans l'exemple ci-dessus, la requête peut dépendre
de <varname>default_text_search_config</varname>. Un autre avantage est que
les recherches seront plus rapides car il n'est plus nécessaire de refaire
des appels à <function>to_tsvector</function> pour vérifier la
correspondance de l'index. (Ceci est plus important lors de l'utilisation
d'un index GiST par rapport à un index GIN ; voir la <xref
linkend="textsearch-indexes"/>.)
Néanmoins, l'approche de l'index par expression est plus simple à
configurer et elle réclame moins d'espace disque car la représentation
<type>tsvector</type> n'est pas réellement stockée.
</para>
</sect2>
</sect1>
<sect1 id="textsearch-controls">
<title>Contrôler la recherche plein texte</title>
<para>
Pour implémenter la recherche plein texte, une fonction doit permettre la
création d'un <type>tsvector</type> à partir d'un document et la création
d'un <type>tsquery</type> à partir de la requête d'un utilisateur. De plus,
nous avons besoin de renvoyer les résultats dans un ordre utile, donc nous
avons besoin d'une fonction de comparaison des documents suivant leur
adéquation à la recherche. Il est aussi important de pouvoir afficher
joliment les résultats. <productname>PostgreSQL</productname> fournit un
support pour toutes ces fonctions.
</para>
<sect2 id="textsearch-parsing-documents">
<title>Analyser des documents</title>
<para>
<productname>PostgreSQL</productname> fournit la fonction
<function>to_tsvector</function> pour convertir un document vers le type de
données <type>tsvector</type>.
</para>
<indexterm>
<primary>to_tsvector</primary>
</indexterm>
<synopsis>
to_tsvector(<optional> <replaceable class="parameter">config</replaceable> <type>regconfig</type>, </optional> <replaceable class="parameter">document</replaceable> <type>text</type>) returns <type>tsvector</type>
</synopsis>
<para>
<function>to_tsvector</function> analyse un document texte et le convertit
en jetons, réduit les jetons en des lexemes et renvoie un
<type>tsvector</type> qui liste les lexemes avec leur position dans le
document. Ce dernier est traité suivant la configuration de recherche
plein texte spécifiée ou celle par défaut. Voici un exemple simple :
<programlisting>
SELECT to_tsvector('english', 'a fat cat sat on a mat - it ate a fat rats');
to_tsvector
-----------------------------------------------------
'ate':9 'cat':3 'fat':2,11 'mat':7 'rat':12 'sat':4
</programlisting>
</para>
<para>
Dans l'exemple ci-dessus, nous voyons que le <type>tsvector</type>
résultant ne contient pas les mots <literal>a</literal>, <literal>on</literal>
et <literal>it</literal>, le mot <literal>rats</literal> est devenu
<literal>rat</literal> et le signe de ponctuation <literal>-</literal> a
été ignoré.
</para>
<para>
En interne, la fonction <function>to_tsvector</function> appelle un analyseur
qui casse le texte en jetons et affecte un type à chaque jeton. Pour chaque
jeton, une liste de dictionnaires (<xref linkend="textsearch-dictionaries"/>)
est consultée, liste pouvant varier suivant le type de jeton. Le premier
dictionnaire qui <firstterm>reconnaît</firstterm> le jeton émet un ou
plusieurs <firstterm>lexemes</firstterm> pour représenter le jeton. Par
exemple, <literal>rats</literal> devient <literal>rat</literal> car un des
dictionnaires sait que le mot <literal>rats</literal> est la forme pluriel
de <literal>rat</literal>. Certains mots sont reconnus comme des
<firstterm>termes courants</firstterm> (<xref linkend="textsearch-stopwords"/>),
ce qui fait qu'ils sont ignorés car ils surviennent trop fréquemment pour
être utile dans une recherche. Dans notre exemple, il s'agissait de
<literal>a</literal>, <literal>on</literal> et <literal>it</literal>. Si
aucun dictionnaire de la liste ne reconnaît le jeton, il est aussi ignoré.
Dans cet exemple, il s'agit du signe de ponctuation <literal>-</literal>
car il n'existe aucun dictionnaire affecté à ce type de jeton
(<literal>Space symbols</literal>), ce qui signifie que les jetons espace
ne seront jamais indexés. Le choix de l'analyseur, des dictionnaires et des
types de jetons à indexer est déterminé par la configuration de recherche
plein texte sélectionné (<xref linkend="textsearch-configuration"/>). Il est
possible d'avoir plusieurs configurations pour la même base, et des
configurations prédéfinies sont disponibles pour différentes langues. Dans
notre exemple, nous avons utilisé la configuration par défaut, à savoir
<literal>english</literal> pour l'anglais.
</para>
<para>
La fonction <function>setweight</function> peut être utilisé pour ajouter
un label aux entrées d'un <type>tsvector</type> avec un
<firstterm>poids</firstterm> donné. Ce poids consiste en une lettre :
<literal>A</literal>, <literal>B</literal>, <literal>C</literal> ou
<literal>D</literal>. Elle est utilisée typiquement pour marquer les entrées
provenant de différentes parties d'un document, comme le titre et le corps.
Plus tard, cette information peut être utilisée pour modifier le score des
résultats.
</para>
<para>
Comme <function>to_tsvector</function>(<literal>NULL</literal>) renvoie
<literal>NULL</literal>, il est recommandé d'utiliser
<function>coalesce</function> quand un champ peut être NULL. Voici la
méthode recommandée pour créer un <type>tsvector</type> à partir d'un
document structuré :
<programlisting>
UPDATE tt SET ti =
setweight(to_tsvector(coalesce(title,'')), 'A') ||
setweight(to_tsvector(coalesce(keyword,'')), 'B') ||
setweight(to_tsvector(coalesce(abstract,'')), 'C') ||
setweight(to_tsvector(coalesce(body,'')), 'D');
</programlisting>
Ici nous avons utilisé <function>setweight</function> pour ajouter un label
au source de chaque lexeme dans le <type>tsvector</type> final, puis
assemblé les valeurs <type>tsvector</type> en utilisant l'opérateur de
concaténation des <type>tsvector</type>, <literal>||</literal>. (La <xref
linkend="textsearch-manipulate-tsvector"/> donne des détails sur ces
opérations.)
</para>
</sect2>
<sect2 id="textsearch-parsing-queries">
<title>Analyser des requêtes</title>
<para>
<productname>PostgreSQL</productname> fournit les fonctions
<function>to_tsquery</function> et <function>plainto_tsquery</function> pour
convertir une requête dans le type de données <type>tsquery</type>.
<function>to_tsquery</function> offre un accès à d'autres fonctionnalités
que <function>plainto_tsquery</function> mais est moins indulgent sur ses
arguments.
</para>
<indexterm>
<primary>to_tsquery</primary>
</indexterm>
<synopsis>
to_tsquery(<optional> <replaceable class="parameter">config</replaceable> <type>regconfig</type>, </optional> <replaceable class="parameter">querytext</replaceable> <type>text</type>) returns <type>tsquery</type>
</synopsis>
<para>
<function>to_tsquery</function> crée une valeur <type>tsquery</type> à
partir de <replaceable>querytext</replaceable> qui doit contenir un
ensemble de jetons
individuels séparés par les opérateurs booléens <literal>&</literal>
(AND), <literal>|</literal> (OR) et <literal>!</literal> (NOT). Ces
opérateurs peuvent être groupés en utilisant des parenthèses. En d'autres
termes, les arguments de <function>to_tsquery</function> doivent déjà suivre
les règles générales pour un <type>tsquery</type> comme décrit dans la <xref
linkend="datatype-textsearch"/>. La différence est que, alors qu'un
<type>tsquery</type> basique prend les jetons bruts,
<function>to_tsquery</function> normalise chaque jeton en un lexeme en
utilisant la configuration spécifiée ou par défaut, et annule tout jeton qui
est un terme courant d'après la configuration. Par exemple :
<programlisting>
SELECT to_tsquery('english', 'The & Fat & Rats');
to_tsquery
---------------
'fat' & 'rat'
</programlisting>
Comme une entrée <type>tsquery</type> basique, des poids peuvent être
attachés à chaque lexeme à restreindre pour établir une correspondance
avec seulement des lexemes <type>tsvector</type> de ces poids. Par
exemple :
<programlisting>
SELECT to_tsquery('english', 'Fat | Rats:AB');
to_tsquery
------------------
'fat' | 'rat':AB
</programlisting>
<function>to_tsquery</function> peut aussi accepter des phrases avec des
guillemets simples. C'est utile quand la configuration inclut un
dictionnaire
thésaurus qui peut se déclencher sur de telles phrases. Dans l'exemple
ci-dessous, un thésaurus contient la règle <literal>supernovae
stars : sn</literal> :
<programlisting>
SELECT to_tsquery('''supernovae stars'' & !crab');
to_tsquery
---------------
'sn' & !'crab'
</programlisting>
sans guillemets, <function>to_tsquery</function> génère une erreur de
syntaxe pour les jetons qui ne sont pas séparés par un opérateur AND ou OR.
</para>
<indexterm>
<primary>plainto_tsquery</primary>
</indexterm>
<synopsis>
plainto_tsquery(<optional> <replaceable class="parameter">config</replaceable> <type>regconfig</type>, </optional> <replaceable class="parameter">querytext</replaceable> <type>text</type>) returns <type>tsquery</type>
</synopsis>
<para>
<function>plainto_tsquery</function> transforme le texte non formaté
<replaceable>querytext</replaceable> en <type>tsquery</type>. Le texte est
analysé et normalisé un peu comme pour <function>to_tsvector</function>,
ensuite l'opérateur booléen <literal>&</literal> (AND) est inséré entre
les mots restants.
</para>
<para>
Exemple :
<programlisting>
SELECT plainto_tsquery('english', 'The Fat Rats');
plainto_tsquery
-----------------
'fat' & 'rat'
</programlisting>
Notez que <function>plainto_tsquery</function> ne peut pas reconnaître un
opérateur booléen ou des labels de poids en entrée :
<programlisting>
SELECT plainto_tsquery('english', 'The Fat & Rats:C');
plainto_tsquery
---------------------
'fat' & 'rat' & 'c'
</programlisting>
Ici, tous les symboles de ponctuation ont été annulés car ce sont des
symboles espace.
</para>
</sect2>
<sect2 id="textsearch-ranking">
<title>Ajouter un score aux résultats d'une recherche</title>
<para>
Les tentatives de score pour mesurer l'adéquation des documents se font
par rapport à une certaine requête. Donc, quand il y a beaucoup de
correspondances, les meilleurs doivent être montrés en premier.
<productname>PostgreSQL</productname> fournit deux fonctions prédéfinies de
score, prennant en compte l'information lexicale, la proximité et la
structure ; en fait, elles considèrent le nombre de fois où les termes
de la requête apparaissent dans le document, la proximité des termes de la
recherche avec ceux de la requête et l'importance du passage du document
où se trouvent les termes du document. Néanmoins, le concept d'adéquation
pourrait demander plus d'informations pour calculer le score, par exemple
la date et l'heure de modification du document. Les fonctions internes de
calcul de score sont seulement des exemples. Vous pouvez écrire vos propres
fonctions de score et/ou combiner leur résultats avec des facteurs
supplémentaires pour remplir un besoin spécifique.
</para>
<para>
Les deux fonctions de score actuellement disponibles sont :
<variablelist>
<varlistentry>
<term>
<indexterm>
<primary>ts_rank</primary>
</indexterm>
<synopsis>
ts_rank(<optional> <replaceable class="parameter">weights</replaceable> <type>float4[]</type>, </optional> <replaceable class="parameter">vector</replaceable> <type>tsvector</type>, <replaceable class="parameter">query</replaceable> <type>tsquery</type> <optional>, <replaceable class="parameter">normalization</replaceable> <type>integer</type> </optional>) returns <type>float4</type>
</synopsis>
</term>
<listitem>
<para>
Fonction de score standard.<!-- TODO document this better -->
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<indexterm>
<primary>ts_rank_cd</primary>
</indexterm>
<synopsis>
ts_rank_cd(<optional> <replaceable class="parameter">weights</replaceable> <type>float4[]</type>, </optional> <replaceable class="parameter">vector</replaceable> <type>tsvector</type>, <replaceable class="parameter">query</replaceable> <type>tsquery</type> <optional>, <replaceable class="parameter">normalization</replaceable> <type>integer</type> </optional>) returns <type>float4</type>
</synopsis>
</term>
<listitem>
<para>
Cette fonction calcule le score de la <firstterm>densité de
couverture</firstterm> pour le vecteur du document et la requête donnés,
comme décrit dans l'article de Clarke, Cormack et Tudhope,
<quote>Relevance Ranking for One to Three Term Queries</quote>, article
paru dans le journal <quote>Information Processing and
Management</quote> en 1999.
</para>
<para>
Cette fonction nécessite des informations de position. Du coup, elle
ne fonctionne pas sur des valeurs <type>tsvector</type>
<quote>strippées</quote> — elle renvoie toujours zéro.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
<para>
Pour ces deux fonctions, l'argument optionnel des <replaceable
class="parameter">poids</replaceable> offre la possibilité d'impacter
certains mots plus ou moins suivant la façon dont ils sont marqués. Le
tableau de poids indique à quel point chaque catégorie de mots est marquée.
Dans l'ordre :
<programlisting>
{poids-D, poids-C, poids-B, poids-A}
</programlisting>
Si aucun <replaceable class="parameter">poids</replaceable> n'est fourni,
alors ces valeurs par défaut sont utilisées :
<programlisting>
{0.1, 0.2, 0.4, 1.0}
</programlisting>
Typiquement, les poids sont utilisés pour marquer les mots compris dans
des aires spéciales du document, comme le titre ou le résumé initial, pour
qu'ils puissent être traités avec plus ou moins d'importance que les mots
dans le corps du document.
</para>
<para>
Comme un document plus long a plus de chance de contenir un terme de la
requête, il est raisonnable de prendre en compte la taille du document,
par exemple un document de cent mots contenant cinq fois un mot de la
requête est probablement plus intéressant qu'un document de mille mots
contenant lui-aussi cinq fois un mot de la requête. Les deux fonctions de
score prennent une option <replaceable>normalization</replaceable>, de type
integer, qui précise si la longueur du document doit impacter son score.
L'option contrôle plusieurs comportements, donc il s'agit d'un masque de
bits : vous pouvez spécifier un ou plusieurs comportements en utilisant
<literal>|</literal> (par exemple, <literal>2|4</literal>).
<itemizedlist spacing="compact" mark="bullet">
<listitem>
<para>
0 (valeur par défaut) ignore la longueur du document
</para>
</listitem>
<listitem>
<para>
1 divise le score par 1 + le logarithme de la longueur du document
</para>
</listitem>
<listitem>
<para>
2 divise le score par la longueur du document
</para>
</listitem>
<listitem>