Traditionnellement, les
Pendant longtemps, le moteur de recherche
Le principe est de couper le texte en
essais=> SELECT to_tsvector('les gros chats mangent les rats maigres');
to_tsvector
----------------------------------------------------------
'le':1,5 'rat':6 'chat':3 'gros':2 'maigr':7 'mangent':4
(1 row)
Ici, elle a analysé la phrase, trouvé six mots (dont un répété) et
réduit (normalisé) ceux qu'elles pouvaient. Ce processus dépend de la
On cherche ensuite dans ces éléments lexicaux avec la fonction de
correspondance,
essais=> SELECT to_tsquery('chat|rat');
to_tsquery
----------------
'chat' | 'rat'
(1 row)
Le langage des requêtes (vous avez sans doute déjà deviné que
En combinant requête, vecteur de mots et opérateur de
correspondance, on peut faire une recherche complète :
essais=> SELECT to_tsvector('les gros chats mangent les rats maigres')
@@ to_tsquery('chat|rat');
?column?
----------
t
(1 row)
essais=> SELECT to_tsvector('on ne parle pas de ces animaux ici')
@@ to_tsquery('chat|rat');
?column?
----------
f
(1 row)
On a trouvé un résultat dans le premier cas et zéro dans le
second.
Bien sûr, taper de telles requêtes à la main est plutôt pénible, on a donc intérêt à créer ses propres fonctions.
L'analyse d'une phrase en mots et la normalisation de ces derniers
dépend de la langue. Il y a un paramètre à
essais=> \dF
List of text search configurations
Schema | Name | Description
------------+------------+---------------------------------------
...
pg_catalog | finnish | configuration for finnish language
pg_catalog | french | configuration for french language
pg_catalog | german | configuration for german language
...
essais=> show default_text_search_config;
default_text_search_config
----------------------------
pg_catalog.french
(1 row)
Prenons maintenant un exemple réel,
un moteur de recherche dans la liste des
-- Un RFC a un numéro, un titre et le corps, du texte brut
CREATE TABLE Rfcs (id SERIAL,
inserted TIMESTAMP default now(),
num INTEGER,
title TEXT,
body TEXT);
-- Créer son propre type facilite l'utilisation de la fonction search()
CREATE TYPE Results AS (num INTEGER, title TEXT,
body TEXT, rank REAL);
-- Les RFC sont en anglais donc on utilise systématiquement la
-- configuration 'english'
CREATE FUNCTION search (TEXT) RETURNS SETOF Results AS
'SELECT num, title,
body, ts_rank_cd(to_tsvector(''english'', title || body),
to_tsquery(''english'',$1))
FROM Rfcs
WHERE to_tsvector(''english'', title || body) @@ to_tsquery(''english'',$1)
ORDER BY ts_rank_cd(to_tsvector(''english'', title || body),
to_tsquery(''english'',$1)) DESC;'
LANGUAGE SQL;
La fonction
essais=> SELECT num,title,rank FROM search('dnssec') ORDER BY rank DESC;
num | title | rank
------+----------------------------------------------------+------
4035 | Protocol Modifications for the DNS Security ... | 10.1
3130 | Notes from the State-Of-The-Technology: DNSSEC | 7.4
4641 | DNSSEC Operational Practices | 7.1
...
La recherche peut être plus compliquée, par exemple si on cherche les
RFC qui parlent de
essais=> SELECT num,title,rank FROM search('dnssec & ldap') ORDER BY rank DESC;
num | title | rank
------+-----------------------------------------------+-------------
5000 | Internet Official Protocol Standards | 0.111842
4238 | Voice Message Routing Service | 0.0170405
4513 | LDAP: Authentication Methods and Security ... | 0.00547112
...
Chercher dans la totalité de la base à chaque fois peut être assez
long. Les moteurs de recherche emploient typiquement
l'
CREATE INDEX rfcs_idx ON Rfcs USING gin(to_tsvector('english', title || body));
Avec ces index, les recherches sont bien plus rapides. En effet, sans
index, il faut balayer toute la table comme l'indique
essais=> EXPLAIN SELECT id,num FROM Rfcs WHERE to_tsvector('english', title || body) @@ to_tsquery('dnssec');
QUERY PLAN
-----------------------------------------------------------------------------------
Seq Scan on rfcs (cost=0.00..186.11 rows=5 width=8)
Filter: (to_tsvector('english'::regconfig, body) @@ to_tsquery('dnssec'::text))
(2 rows)
(
essais=> EXPLAIN SELECT id,num FROM Rfcs WHERE to_tsvector('english', title || body) @@ to_tsquery('dnssec');
QUERY PLAN
---------------------------------------------------------------------------------------------
Bitmap Heap Scan on rfcs (cost=40.70..57.34 rows=5 width=8)
Recheck Cond: (to_tsvector('english'::regconfig, body) @@ to_tsquery('dnssec'::text))
-> Bitmap Index Scan on rfcs_idx (cost=0.00..40.70 rows=5 width=0)
Index Cond: (to_tsvector('english'::regconfig, body) @@ to_tsquery('dnssec'::text))
(4 rows)
Le programme qui prend l'ensemble des RFC et les met dans la base
est
Sur le même sujet et dans la même langue, on peut lire le très détaillé « Recherche plein texte avec PostgreSQL 8.3 ».