

   Pour implémenter la recherche plein texte, une fonction doit permettre la
   création d'un tsvector à partir d'un document et la création
   d'un tsquery à 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. PostgreSQL fournit un
   support pour toutes ces fonctions.
  
    PostgreSQL fournit la fonction
    to_tsvector pour convertir un document vers le type de
    données tsvector.
   
    to_tsvector([ config regconfig, ] document text) returns tsvector
   
    to_tsvector analyse un document texte et le convertit
    en jetons, réduit les jetons en des lexèmes et renvoie un
    tsvector qui liste les lexèmes 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 :
    
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
    
    Dans l'exemple ci-dessus, nous voyons que le tsvector
    résultant ne contient pas les mots a, on
    et it, le mot rats est devenu
    rat et le signe de ponctuation - a
    été ignoré.
   
    En interne, la fonction to_tsvector appelle un analyseur
    qui casse le texte en jetons et affecte un type à chaque jeton. Pour chaque
    jeton, une liste de dictionnaires (Section 12.6)
    est consultée, liste pouvant varier suivant le type de jeton. Le premier
    dictionnaire qui reconnaît le jeton émet un ou
    plusieurs lexèmes pour représenter le jeton. Par
    exemple, rats devient rat, car un des
    dictionnaires sait que le mot rats est la forme plurielle
    de rat. Certains mots sont reconnus comme des
    termes courants (Section 12.6.1),
    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
    a, on et it. 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 -,
    car il n'existe aucun dictionnaire affecté à ce type de jeton
    (Space symbols), 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ée (Section 12.7). 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
    english pour l'anglais.
   
    La fonction setweight peut être utilisée pour ajouter
    un label aux entrées d'un tsvector avec un
    poids donné. Ce poids consiste en une lettre :
    A, B, C ou
    D. 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.
   
    Comme to_tsvector(NULL) renvoie
    NULL, il est recommandé d'utiliser
    coalesce quand un champ peut être NULL. Voici la
    méthode recommandée pour créer un tsvector à partir d'un
    document structuré :
    
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');
    
    Ici, nous avons utilisé setweight pour ajouter un label
    à la source de chaque lexème dans le tsvector final, puis
    assemblé les valeurs tsvector en utilisant l'opérateur de
    concaténation des tsvector, ||.  (La Section 12.4.1 donne des détails sur ces
    opérations.)
   
    PostgreSQL fournit les fonctions
    to_tsquery, plainto_tsquery,
    phraseto_tsquery et websearch_to_tsquery pour
    convertir une requête dans le type de données tsquery.
    to_tsquery offre un accès à d'autres fonctionnalités
    que plainto_tsquery et phraseto_tsquery,
    mais est moins indulgent sur ses arguments.
    websearch_to_tsquery est une version simplifiée de
    to_tsquery avec une syntaxe différente, similaire à
    celle utilisée par les moteurs web de recherche.
   
    to_tsquery([ config regconfig, ] querytext text) returns tsquery
   
    to_tsquery crée une valeur tsquery à
    partir de querytext qui doit contenir un
    ensemble de jetons individuels séparés par les opérateurs tsquery
    & (AND), | (OR) et
    ! (NOT), et l'opérateur de recherche de phrase
    <-> (FOLLOWED BY), possiblement groupés en
    utilisant des parenthèses. En d'autres
    termes, les arguments de to_tsquery doivent déjà suivre
    les règles générales pour un tsquery comme décrit dans la Section 8.11.2. La différence est que, alors qu'un
    tsquery basique prend les jetons bruts,
    to_tsquery normalise chaque jeton en un lexème 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 :
    
SELECT to_tsquery('english', 'The & Fat & Rats');
  to_tsquery
---------------
 'fat' & 'rat'
    
    Comme une entrée tsquery basique, des poids peuvent être
    attachés à chaque lexème à restreindre pour établir une correspondance
    avec seulement des lexèmes tsvector de ces poids. Par
    exemple :
    
SELECT to_tsquery('english', 'Fat | Rats:AB');
    to_tsquery
------------------
 'fat' | 'rat':AB
    
    De plus, * peut être attaché à un lexème pour demander
    la correspondance d'un préfixe :
    
SELECT to_tsquery('supern:*A & star:A*B');
        to_tsquery
--------------------------
 'supern':*A & 'star':*AB
    
    Un tel lexème correspondra à tout mot dans un tsvector qui
    commence par la chaîne indiquée.
   
    to_tsquery 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 supernovae
     stars : sn :
    
SELECT to_tsquery('''supernovae stars'' & !crab');
  to_tsquery
---------------
 'sn' & !'crab'
    
    Sans guillemets, to_tsquery génère une erreur de
    syntaxe pour les jetons qui ne sont pas séparés par un opérateur AND, ou FOLLOWED BY.
   
    plainto_tsquery([ config regconfig, ] querytext text) returns tsquery
   
    plainto_tsquery transforme le texte non formaté
    querytext en tsquery. Le texte est
    analysé et normalisé un peu comme pour to_tsvector,
    ensuite l'opérateur tsquery & (AND) est inséré entre
    les mots restants.
   
Exemple :
 SELECT plainto_tsquery('english', 'The Fat Rats');
 plainto_tsquery
-----------------
 'fat' & 'rat'
    
    Notez que plainto_tsquery ne reconnaîtra pas un
    opérateur tsquery, des labels de poids en entrée
    ou des labels de correspondance de préfixe :
    
SELECT plainto_tsquery('english', 'The Fat & Rats:C');
   plainto_tsquery
---------------------
 'fat' & 'rat' & 'c'
    Ici, tous les symboles de ponctuation ont été annulés, car ce sont des symboles espace.
phraseto_tsquery([configregconfig, ]querytexttext) returnstsquery
    phraseto_tsquery se comporte largement comme
    plainto_tsquery, sauf qu'elle insère l'opérateur
    <-> (FOLLOWED BY) entre les mots restants plutôt
    que l'opérateur & (AND). De plus, les termes
    courants ne sont pas simplement écartés, mais sont comptabilisés par
    l'utilisation d'opérateurs
    < plutôt que
    d'opérateurs N><->. Cette fonction est utile quand
    on recherche des séquences exactes de lexèmes, puisque l'opérateur
    FOLLOWED BY vérifie l'ordre des lexèmes et pas seulement la présence de
    tous les lexèmes.
   
Exemple :
SELECT phraseto_tsquery('english', 'The Fat Rats');
 phraseto_tsquery
------------------
 'fat' <-> 'rat'
    
    Comme plainto_tsquery, la fonction
    phraseto_tsquery ne reconnait ni les opérateurs
    tsquery, ni les labels de poids, ni les labels de
    correspondance de préfixe dans ses arguments :
    
SELECT phraseto_tsquery('english', 'The Fat & Rats:C');
      phraseto_tsquery
-----------------------------
 'fat' <-> 'rat' <-> 'c'
    
websearch_to_tsquery([configregconfig, ]querytexttext) returnstsquery
    websearch_to_tsquery crée une valeur
    tsquery à partir de querytext en
    utilisant une syntaxe différente dans laquelle un texte simple non formaté
    est une requête valide. Contrairement à
    plainto_tsquery et
    phraseto_tsquery, elle reconnaît aussi certains
    opérateurs. De plus, cette fonction ne doit jamais lever d'erreurs de
    syntaxe, ce qui permet de l'utiliser pour la recherche à partir d'entrées
    brutes fournies par un utilisateur. La syntaxe suivante est
    supportée :
    
       unquoted text : du texte en dehors de
       guillemets sera converti en des termes séparés par des opérateurs
       &, comme traité par
       plainto_tsquery.
      
       "quoted text" : du texte à l'intérieur de
       guillemets sera converti en termes séparés par des opérateurs
       <->, comme traité par
       phraseto_tsquery.
      
       OR : un OU logique sera converti vers
       l'opérateur |.
      
       - : l'opérateur logique NON sera converti vers
       l'opérateur !.
      
Exemples :
SELECT websearch_to_tsquery('english', 'The fat rats');
 websearch_to_tsquery
----------------------
 'fat' & 'rat'
(1 row)
SELECT websearch_to_tsquery('english', '"supernovae stars" -crab');
       websearch_to_tsquery
----------------------------------
 'supernova' <-> 'star' & !'crab'
(1 row)
SELECT websearch_to_tsquery('english', '"sad cat" or "fat rat"');
       websearch_to_tsquery
-----------------------------------
 'sad' <-> 'cat' | 'fat' <-> 'rat'
(1 row)
SELECT websearch_to_tsquery('english', 'signal -"segmentation fault"');
         websearch_to_tsquery
---------------------------------------
 'signal' & !( 'segment' <-> 'fault' )
(1 row)
SELECT websearch_to_tsquery('english', '""" )( dummy \\ query <->');
 websearch_to_tsquery
----------------------
 'dummi' & 'queri'
(1 row)
    
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. PostgreSQL fournit deux fonctions prédéfinies de score, prenant 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 leurs résultats avec des facteurs supplémentaires pour remplir un besoin spécifique.
Les deux fonctions de score actuellement disponibles sont :
ts_rank([ weights float4[], ] vector tsvector, query tsquery [, normalization integer ]) returns float4
      Calcule un score sur les vecteurs en se basant sur la fréquence des lexèmes correspondant à la recherche.
ts_rank_cd([ weights float4[], ] vector tsvector, query tsquery [, normalization integer ]) returns float4
      
        Cette fonction calcule le score de la densité de
         couverture pour le vecteur du document et la requête donnés,
        comme décrit dans l'article de Clarke, Cormack et Tudhope,
        « Relevance Ranking for One to Three Term Queries », article
        paru dans le journal « Information Processing and
         Management » en 1999.  La densité de couverture est similaire au
        classement effectué par ts_rank, à la différence
        que la proximité de correspondance des lexèmes les uns par rapport aux
        autres est prise en compte.
       
        Cette fonction a besoin d'information sur la position des lexèmes pour
        effectuer son travail. Par conséquent, elle ignore les lexèmes
        « stripés » dans le tsvector. S'il n'y a aucun
        lexème « non stripé » en entrée, le résultat sera zéro.
        (Voir Section 12.4.1 pour plus
        d'informations sur la fonction strip et les
        informations de position dans les tsvector.)
       
    Pour ces deux fonctions, l'argument optionnel des poids 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 :
    
{poids-D, poids-C, poids-B, poids-A}
    
    Si aucun poids n'est fourni,
    alors ces valeurs par défaut sont utilisées :
    
{0.1, 0.2, 0.4, 1.0}
    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.
    Comme un document plus long a plus de chances 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 normalization, 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
    | (par exemple, 2|4).
    
0 (valeur par défaut) ignore la longueur du document
1 divise le score par 1 + le logarithme de la longueur du document
2 divise le score par la longueur du document
       4 divise le score par la moyenne harmonique de la distance entre les mots
       (ceci est implémenté seulement par ts_rank_cd)
      
8 divise le score par le nombre de mots uniques dans le document
16 divise le score par 1 + le logarithme du nombre de mots uniques dans le document
32 divise le score par lui-même + 1
Si plus d'un bit de drapeau est indiqué, les transformations sont appliquées dans l'ordre indiqué.
    Il est important de noter que les fonctions de score n'utilisent aucune
    information globale, donc il est impossible de produire une normalisation de
    1% ou 100%, comme c'est parfois demandé. L'option de normalisation 32
    (score/(score+1)) peut s'appliquer pour échelonner
    tous les scores dans une échelle de zéro à un, mais, bien sûr, c'est
    une petite modification cosmétique, donc l'ordre des résultats ne changera
    pas.
   
Voici un exemple qui sélectionne seulement les dix correspondances de meilleur score :
SELECT title, ts_rank_cd(textsearch, query) AS rank
FROM apod, to_tsquery('neutrino|(dark & matter)') query
WHERE query @@ textsearch
ORDER BY rank DESC
LIMIT 10;
                     title                     |   rank
-----------------------------------------------+----------
 Neutrinos in the Sun                          |      3.1
 The Sudbury Neutrino Detector                 |      2.4
 A MACHO View of Galactic Dark Matter          |  2.01317
 Hot Gas and Dark Matter                       |  1.91171
 The Virgo Cluster: Hot Plasma and Dark Matter |  1.90953
 Rafting for Solar Neutrinos                   |      1.9
 NGC 4650A: Strange Galaxy and Dark Matter     |  1.85774
 Hot Gas and Dark Matter                       |   1.6123
 Ice Fishing for Cosmic Neutrinos              |      1.6
 Weak Lensing Distorts the Universe            | 0.818218
    Voici le même exemple en utilisant un score normalisé :
SELECT title, ts_rank_cd(textsearch, query, 32 /* rank/(rank+1) */ ) AS rank
FROM apod, to_tsquery('neutrino|(dark & matter)') query
WHERE  query @@ textsearch
ORDER BY rank DESC
LIMIT 10;
                     title                     |        rank
-----------------------------------------------+-------------------
 Neutrinos in the Sun                          | 0.756097569485493
 The Sudbury Neutrino Detector                 | 0.705882361190954
 A MACHO View of Galactic Dark Matter          | 0.668123210574724
 Hot Gas and Dark Matter                       |  0.65655958650282
 The Virgo Cluster: Hot Plasma and Dark Matter | 0.656301290640973
 Rafting for Solar Neutrinos                   | 0.655172410958162
 NGC 4650A: Strange Galaxy and Dark Matter     | 0.650072921219637
 Hot Gas and Dark Matter                       | 0.617195790024749
 Ice Fishing for Cosmic Neutrinos              | 0.615384618911517
 Weak Lensing Distorts the Universe            | 0.450010798361481
    
    Le calcul du score peut consommer beaucoup de ressources, car il demande
    de consulter le tsvector de chaque document correspondant, ce
    qui est très consommateur en entrées/sorties et du coup lent. Malheureusement,
    c'est presque impossible à éviter, car les requêtes intéressantes ont un
    grand nombre de correspondances.
   
    Pour présenter les résultats d'une recherche, il est préférable d'afficher
    une partie de chaque document et en quoi cette partie concerne la requête.
    Habituellement, les moteurs de recherche affichent des fragments du document
    avec des marques pour les termes recherchés.
    PostgreSQL fournit une fonction
    ts_headline qui implémente cette fonctionnalité.
   
    ts_headline([ config regconfig, ] document text, query tsquery [, options text ]) returns text
   
    ts_headline accepte un document avec une requête et
    renvoie un résumé du document.
    Les termes de la requête sont surlignés dans les extractions. La
    configuration à utiliser pour analyser le document peut être précisée par
    config ; si config
    est omis, le paramètre default_text_search_config est
    utilisé.
   
    Si une chaîne options est spécifiée, elle doit
    consister en une liste de une ou plusieurs paires
    option=valeur
    séparées par des virgules. Les options disponibles sont :
    
       MaxWords, MinWords
       (entiers) : ces nombres déterminent les limites minimum et maximum
       des résumés à afficher. Les valeurs par défaut sont respectivement 35
       et 15.
      
       ShortWord (entier) : les mots de cette
       longueur et les mots plus petits seront supprimés au début et à la fin
       d'un résumé, sauf si ce sont des mots de la recherche. La valeur par
       défaut est de trois pour éliminer les articles anglais communs.
      
       HighlightAll (booléen) : si
       true, le document complet sera utilisé comme résumé,
       en ignorant les trois paramètres précédents. La valeur par défaut est
       false.
      
       MaxFragments (entier) : nombre maximum de
       fragments de texte à afficher. La valeur par défaut, 0, sélectionne une
       méthode de génération de résumés non basés sur des fragments. Une
       valeur positive sélectionne la génération de résumé basée sur les
       fragments (voir ci-dessous).
      
       StartSel, StopSel
       (chaînes) : les chaînes qui permettent de délimiter les mots de la
       requête apparaissant dans le document pour les distinguer des autres
       mots du résumé. Les valeurs par défaut sont
       « <b> » et
       « </b> », qui sont convenables pour
       une sortie HTML.
      
       FragmentDelimiter (chaîne) : quand plus d'un
       fragment est affiché, alors les fragments seront séparés par ce
       délimiteur. La valeur par défaut est «  ...
         ».
      
Les noms de ces options s'utilisent avec ou sans casse. Vous devez mettre les chaînes de caractères entre guillemets doubles si elles contiennent des espaces ou des virgules.
    Pour la génération des résumés qui ne sont pas basés sur des fragments,
    ts_headline repère les correspondances pour la
    requete donnée et en choisit
    une seule à afficher, en préférant les correspondances qui ont le plus
    grand nombre de mots provenant de la requête dans la longueur autorisée
    pour le résumé. Pour la génération des résumés basés sur les fragments,
    ts_headline repère les correspondances pour la
    requête et divise chaque correspondance en « fragments » d'au
    plus MaxWords mots chacun, en préférant les fragments
    avec le plus de mots provenant de la requête, et si possible des fragments
    se prolongeant pour inclure les mots autour. Le mode basé sur les
    fragments est donc plus utile quand la requête correspond à de grosses
    sections du document ou quand il est préférable d'afficher plusieurs
    correspondances. Dans les deux modes, si aucune correspondance n'est
    identifiée, alors un seul fragment des MinWords
    premiers mots du document sera affiché.
   
Par exemple :
SELECT ts_headline('english',
  'The most common type of search
is to find all documents containing given query terms
and return them in order of their similarity to the
query.',
  to_tsquery('english', 'query & similarity'));
                        ts_headline
------------------------------------------------------------
 containing given <b>query</b> terms                       +
 and return them in order of their <b>similarity</b> to the+
 <b>query</b>.
SELECT ts_headline('english',
  'Search terms may occur
many times in a document,
requiring ranking of the search matches to decide which
occurrences to display in the result.',
  to_tsquery('english', 'search & term'),
  'MaxFragments=10, MaxWords=7, MinWords=3, StartSel=<<, StopSel=>>');
                        ts_headline
------------------------------------------------------------
 <<Search>> <<terms>> may occur                            +
 many times ... ranking of the <<search>> matches to decide
    
    ts_headline utilise le document original, pas un résumé
    tsvector, donc elle peut être lente et doit être utilisée
    avec parcimonie et attention.