Introduzione
La ricerca Full Text non è altro che la ricerca per parola; tale modalità’ di ricerca si differenzia in modo sostanziale dalla ricerca per stringa o sottostringa dove il match con testo è del tipo “*<termine_cercato>*”, quella che solitamente si trova implementata in molte applicazioni. E’ altresì diversa anche da una ricerca per parola esatta dove il match e’ del tipo “ <termine cercato> “ e derivate, cioè una ricerca per stringa in cui vengono posti 2 spazi bianchi prima e dopo la parola o uno spazio solo se il match si trova a fine o inizio frase.
Il lessema
La ricerca per parola si basa sul concetto di lessema.
Un lessema è, in lessicologia strutturale, l'unità minima che costituisce il lessico di una lingua. Dunque, a ogni lessema di una lingua può corrispondere la sua registrazione in un dizionario sotto forma di lemma. (fonte: wikipedia)
Per semplificare molto le cose potremmo dire che il lessema sia la radice significativa della parola. Da qui ne deriva che la ricerca per parola e’ direttamente correlata alla lingua in quanto una stessa parola in 2 lingue differenti avrà 2 lessemi diversi.
Creazione della struttura del database
Abbiamo a disposizione una tabella <periodici> ad esempio con i campi
key
titolo
sottotitolo
ente_autore
luogo_pubbl
editore
issn
e vogliamo implementare una ricerca per parola su più campi esempio titolo, sottotitolo e luogo di pubblicazione.
Creiamo nella tabella un campo tsvector:
demo=# alter table periodici add column fulltext_ita tsvector;
Carichiamo i dati dei record della tabella:
demo=# update periodici set fulltext_ita = to_tsvector('italian',titolo || ’ ’ || sottotitolo || ’ ’ || luogo_pubbl);
Indicizziamo la tabella:
demo=# create index fulltext_ita_idx on public.periodici using gin (fulltext_ita);
A questo punto avremo un campo vettoriale indicizzato che contiene per ogni riga tutti i lessemi in italiano delle parole contenute nei campi che abbiamo selezionato. A seguito un esempio di come verrebbe popolato un campo FullText di un libro del Mulino a cui avessimo inserito nel campo i valori: Titolo, Autore, Sottotitolo, Descrizione/Abstract
TITOLO: LUCIANO VIOLANTE
AUTORE: Insegna Creonte
SOTTOTITOLO: Tre errori nell'esercizio del potere
DESCRIZIONE : Questo libro nasce dalla lunga esperienza politica dell’autore che ha attraversato fasi particolarmente intense della recente storia repubblicana. Dal dopo Moro alla fine della guerra fredda, da Tangentopoli a Maastricht, dai crimini dei terrorismi alle stragi di mafia, dalla scomparsa di un intero ceto politico all’affermazione di ceti politici del tutto nuovi, Violante individua quegli errori che sono apparsi più gravi degli altri: aprire un conflitto che non si è capaci di governare, sopravvalutare le proprie capacità, essere arroganti. Hanno la loro radice comune nella illusione della onnipotenza, che è il morbo dell’attività politica e che Creonte, nella lettura dell’Antigone come tragedia di un potere che si autodistrugge, incarna in maniera esemplare.
FULLTEXT_ITA: 'afferm':58 'altri':75 'antigon':114 'appars':71 'aprir':76 'arrog':91 'attiv':106 'attravers':22 'autodistrugg':122 'autor':19 'capac':83,89 'cet':55,60 'comun':96 'conflitt':78 'creont':4,110 'crimin':43 'dop':31 'error':6,68 'esempl':126 'eserciz':8 'esperient':16 'esser':90 'fas':23 'fin':34 'fredd':37 'govern':85 'grav':73 'guerr':36 'illusion':98 'incarn':123 'individu':66 'insegn':3 'intens':25 'inter':54 'lettur':112 'libr':12 'luc':1 'lung':15 'maastricht':41 'maf':49 'manier':125 'mor':32 'morb':104 'nasc':13 'nuov':64 'onnipotent':100 'particolar':24 'polit':17,56,61,107 'pot':10,119 'propr':88 'quegl':67 'radic':95 'recent':27 'repubblican':29 'scompars':51 'sopravvalut':86 'stor':28 'strag':47 'tangentopol':39 'terror':45 'traged':116 'tre':5 'violant':2,65
Ad ogni parola vengono associati uno o più numeri che indicano la posizione della parola nel testo.
La ricerca semplice per parola
Per la ricerca viene utilizzata la funzione di PostgresSQL to_tsquery per trasformare la stringa di ricerca nel lemma relativo, anch’esso ovviamente nella stessa lingua, e l’operatore @@ per la ricerca della parola nel vettore.
demo=# select key,titolo from periodici where fulltext_ita @@ to_tsquery('italian','storico');
La ricerca su 2 o più parole in AND:
demo=# select key,titolo from periodici where fulltext_ita @@ to_tsquery('italian','economia & politica);
Notiamo i lessemi come sono diversi se applicati alla lingua sbagliata:
demo=# select to_tsquery('english','economia & politica’);
-[ RECORD 1 ]---------------------
to_tsquery | 'economia' & 'polit'
demo=# select to_tsquery('italian','economia & politica');
-[ RECORD 1 ]------------------
to_tsquery | 'econom' & 'polit'
Un’altro operatore utile e’ <-> che permette di vincolare 2 lemmi adiacenti:
demo=# select key,titolo,sottotitolo from periodici where fulltext_ita @@ to_tsquery('italian','economia & italiana');
demo=# select key,titolo,sottotitolo from periodici where fulltext_ita @@ (to_tsquery('italian','economia’) <-> to_tsquery('italian','italiana'));
Le WILDCARD
Si possono fare ricerche con l'operatore star con la seguente sintassi:
demo=# select key,titolo from periodici where fulltext_ita @@ to_tsquery('italian','eco & italiana');
demo=# select key,titolo from periodici where fulltext_ita @@ to_tsquery('italian','eco:* & italiana');
Le STOPWORDS
Le stopwords vengono eliminate ma a seconda se presenti o no variano i valori di ranking:
demo=# select to_tsvector('italian', 'nel mezzo del cammin di nostra vita');
to_tsvector
-----------------------------
'cammin':4 'mezz':2 'vit':7
demo=# select to_tsvector('italian', 'mezzo cammin nostra vita');
to_tsvector
-----------------------------
'cammin':2 'mezz':1 'vit':4
Per cui se andiamo a valutare il ranking su una query si vede che se eliminate alla fonte il ranking aumentano in quanto in percentuale essendoci meno parole più termini fanno il match nel campo fulltext:
demo=# SELECT ts_rank_cd (to_tsvector('italian', 'nel mezzo del cammin di nostra vita'), to_tsquery('italian','cammino & vita'));
ts_rank_cd
------------
0.0333333
demo=# select ts_rank_cd (to_tsvector('italian', 'mezzo cammin nostra vita'), to_tsquery('italian','cammino & vita'));
ts_rank_cd
------------
0.05
Ordinamento per Ranking
Esempio di query ordinata per ranking:
demo=# select key,titolo,sottotitolo,ts_rank(fulltext_ita,to_tsquery('italian','eco:* & italiana')) from periodici where fulltext _ita @@ to_tsquery('italian','eco:* & italiana') order by ts_rank desc;
Praticità
L’utilizzare direttamente PostgreSQL per la ricerca invece di software esterni tipo Lucene o altri permette, una volta implementato il sistema, di risparmiare in manutenzione, e risorse macchina in quanto i dati restano all’interno del database. Le stesse operazioni di insert/delete/update di righe della tabella hanno un riscontro istantaneo sul campo di ricerca.
Esempio di inserimento di un nuovo record:
demo=# insert into periodici (key,titolo,sottotitolo,luogo_pubbl,ente_autore,editore,fulltext_ita) values ($key+1,’Rivista Il Mulino’,’’,’Bologna’,’’,’Il Mulino’,to_tsvector('italian',titolo || ’ ’ || sottotitolo || ’ ’ || luogo_pubbl));
demo=# reindex fulltext_ita;
Senza poi dover modificare il codice dell'applicazione si può configurare un trigger che si azioni ogni volta che ci sia una insert o update di un record che popoli il campo fulltext_ita e ne faccia la reindex.
Cosa si può fare di più?
Davvero molte implementazioni, la più interessante è forse il controllo sugli errori di ortografia immessi nella stringa di ricerca tramite suggerimenti e l’utilizzo di dizionari.
Documentazione completa
https://www.postgresql.org/docs/10/textsearch.html