Costruiamo un VERO motore di ricerca per il nostro sito con MATCH e AGAINST

In questa pillola illustrerò come creare una ricerca nel db che sappia ordinare i risultati relativamente alla pertinenza con la chiave. Nel corso della spiegazione, per riferirmi alla stringa di ricerca, utilizzerò la variabile $key.

Tutti (o quasi) sanno fare un ricerca utilizzando la direttiva LIKE. Questo metodo va bene per ricerche relativamente semplici. Ad esempio per cercare un cognome in una rubrica utilizzeremo una query del genere:

Codice PHP:
$sql “SELECT FROM rubrica WHERE cognome LIKE ‘$key’”
La questione si complica un tantino quando dobbiamo fare una ricerca in un testo e magari la stringa di ricerca che ci aspettiamo non è di una sola parola, ma si può fare ugualmente. Mettiamo che il nostro database contenga una raccolta di articoli di giornale.

Codice PHP:

$keys 
explode (“ “$key);  // creo un array con le varie parole inserite nel campo di ricerca
$sql “SELECT FROM articoli WHERE “;  //imposto l’inizio della query

$max count($keys);

for (
$a 0$a $max$a++)
{
$sql .= “contenuto LIKE ‘$keys[$a]’ OR // aggiungo una parte di query per ogni parola cercata
}

$sql .= “0”;  // annullo l’effetto dell’ultimo OR 
In questo modo abbiamo costruito la query di ricerca dinamicamente. Se avessimo inserito nel campo di ricerca “cane gatto topo” risulterebbe la seguente query:

Codice PHP:
$sql “SELECT FROM articoli WHERE contenuto LIKE ‘cane’ OR contenuto LIKE ‘gatto’ OR contenuto LIKE ‘topo’ OR 0”
Ho fatto tutta questa filippica per far capire i limiti di LIKE. Infatti la query che abbiamo creato ci restituirà gli articoli contenenti cane o gatto o topo ma ordinati per come li trova.

Quello che vogliamo, invece, è che i risultati siano ordinati per pertinenza. Quindi prima voglio gli articoli che contengono tutte le chiavi di ricerca (cane,gatto,topo) poi quelli che ne contengono solo due, e infine quelli che ne contengono uno solo. Esattamente come quando facciamo una ricerca con google.

Questa operazione non è possibile con LIKE.


Lavoreremo quindi le direttive MATCH e AGAINST. I vantaggi li scopriremo in seguito ma una cosa la si può dire subito. Le ricerche eseguite con queste direttive risultano oltre 10 volte più veloci, il motivo principale è che utilizzano un indice fulltext.

1. Indice fulltext
Se vogliamo utilizzare MATCH e AGAINST dobbiamo aggiungere un indice fulltext nei campi dove vogliamo poter eseguire le ricerche. L’indice fulltext può essere aggiunto unicamente nei campi di testo (VARCHAR, LONGTEXT, TEXT, ecc…).
Mettiamo che il nostro database di articoli sia strutturato in questo modo
Id – titolo – autore – contenuto
E diciamo che ci interesserà la ricerca nel campo titolo e ovviamente nel campo contenuto.

Come aggiungere gli indici fulltext al momento della creazione della tabella:

Codice PHP:
CREATE TABLE `articoli` ( 
`
idINTUNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`
autoreVARCHAR30 NOT NULL ,
`
titoloVARCHAR50 NOT NULL ,
`
contenutoLONGTEXT NOT NULL ,
FULLTEXT 
`
titolo` ,
`
contenuto
)
TYPE MYISAM
Come aggiungere gli indici se la tabella esiste già


Codice PHP:
ALTER TABLE `articoliADD FULLTEXT 
`
titolo`, 
`
contenuto`

O più semplicemente con PhpMyAdmin, quando si crea un nuovo campo, alla fine si può selezionare fulltext (testo completo).

Ora siamo pronti per la ricerca. La query più semplice che si può fare (ma anche la più inutile) è la seguente:

Codice PHP:

SELECT 
FROM articoli WHERE MATCH(titolo,contenutoAGAINST('cane'
Dicevo inutile perché per sfruttare appieno le caratteristiche della ricerca fulltext dobbiamo attivare il modo boleano. Inoltre dobbiamo tenere conto che MATCH e AGAINST restituiscono un valore di pertinenza con il risultato trovato, quindi come dicevo prima, possiamo ordinare il risultato.
Questo è il modello di query forse più utile:

Codice PHP:

SELECT 
*, MATCH(titolo,descrizioneAGAINST('cane gatto topo' IN BOOLEAN MODE) AS tot FROM articoli WHERE MATCH(titolo,descrizione
AGAINST ('cane gatto topo' IN BOOLEAN MODEORDER BY tot DESC 
Come vediamo, con il modo boleano, non abbiamo nemmeno dovuto preoccuparci di explodere la chiave di ricerca. Infatti “cane gatto topo” significa cane OR gatto OR topo (vedremo poco più avanti altri tipi di operatori). Inoltre grazie al valore di pertinenza restituito possiamo creare “tot” per mezzo del quale possiamo ordinare il risultato.
Ecco dunque che questa query funziona come un motore di ricerca tipo google.
E questo dovrebbe bastare.
Ma se avete delle necessità molto particolari gli operatori boleani ci permettono di fare delle ricerche molto raffinate:

 ‘cane gatto topo scoiattolo’ : Deve esserci almeno una di queste parole (OR) come abbiamo visto
 ‘+cane +gatto +topo’ : Devono esserci tutte queste parole (AND). In questo caso avrebbe poco senso ordinare per pertinenza perché tutti i risultati (se ce ne sono) avranno pertinenza totale
 ‘+cane gatto topo’ : Deve esserci cane eventualmente gatto eventualmente topo
 ‘+cane gatto –topo – rana’: Deve esserci cane eventualmente gatto ma NON topo e NON rana
 ‘”cane gatto topo”’ : Deve esserci esattamente “cane gatto topo” in questa sequenza

Esistono altre combinazioni anche molto complesse, ma vi rimando alla documentazione di MySql.

Spero di essere stato utile. E per ricompensarvi della fatica di avere letto questo mattone, vi posto la mia classe Search.


Codice PHP:

/**
 * Search
 *
 * @package lib
 * @author Tarchini Maurizio
 * @copyright 2008
 * @version 2.0
 * @access public
 */
 
class Search
{
    
#CONFIGURA
    #Parametri ricerca
    
var $fulltext "campi fulltext da analizzare separati da virgola senza spazi";
    var 
$table "tabella oggetto della ricerca";
    
#parametri db
    
var $host "localhost";
    var 
$password "db password";
    var 
$user "root";
    var 
$db "nome database";
    
#metodo score -> p in percentuale, f in frazione
    
var $pf "f";
    
#FINE CONFIGURAZIONE
    #NON EDITARE OLTRE QUESTA LINEA
    
var $key;
    var 
$conn;
    var 
$res;
    var 
$total;
    
        function 
Search($key)
        {
            
$this->key $key;
        }

        function 
DbConnectAndSelect()
        {
            
$this->conn = @mysql_connect($this->host$this->user$this->password) or die ("Impossibile stabilire una connessione con il server.
MySql risponde: " 
mysql_error() . "
Il codice errore é:" 
mysql_errno());
            
            @
mysql_select_db($this->db$this->conn) or die ("Impossibile connettersi al database $this->db.
MySql risponde: " 
mysql_error() . "
Il codice errore é:" 
mysql_errno());
        }

        function 
GetResource()
        {
            
$this->DbConnectAndSelect();
            
$sql "SELECT *, MATCH($this->fulltext) AGAINST('$this->key' IN BOOLEAN MODE) AS tot FROM $this->table WHERE MATCH($this->fulltext) AGAINST('$this->key' IN BOOLEAN MODE) ORDER BY tot DESC";
            
$this->res mysql_query($sql$this->conn);
            
        }
        
        function 
CalcScore($tot)
        {
            switch(
$this->pf)
            {
                case 
"f":
                
$key_array explode(" "$this->key);
                
$this->total count($key_array);
                return 
$tot " / " $this->total;
                break;
                case 
"p":
                
$key_array explode(" "$this->key);
                
$this->total count($key_array);
                
$output intval($tot $this->total 100) . "%";
                return 
$output;
                break;
                default:
                
$key_array explode(" "$this->key);
                
$this->total count($key_array);
                return 
$tot " / " $this->total;
                
            }
        }
        

Uso:

Vanno configurati i parametri all’inizio della classe

Uso semplice:

Codice PHP:

$search 
= new Search($key);
$search->GetResource();

questo punto disponiamo della risorsa necessaria per poter stampare la ricerca

 
while ($row mysql_fetch_array($search->res))
 {    
    echo 
$row['titolo'];
 } 
Se desideriamo avere anche la pertinenza (score):

Codice PHP:
$search = new Search($key);
$search->GetResource();
while (
$row mysql_fetch_array($search->res))
{
    echo 
$row['titolo'] . " score: " $search->CalcScore($row['tot']);    

E’ possible avere lo score in percentuale o in frazione.