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` (
`id` INT( 5 ) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`autore` VARCHAR( 30 ) NOT NULL ,
`titolo` VARCHAR( 50 ) NOT NULL ,
`contenuto` LONGTEXT NOT NULL ,
FULLTEXT (
`titolo` ,
`contenuto`
)
) TYPE = MYISAM;
Come aggiungere gli indici se la tabella esiste già
Codice PHP:
ALTER TABLE `articoli` ADD 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,contenuto) AGAINST('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,descrizione) AGAINST('cane gatto topo' IN BOOLEAN MODE) AS tot FROM articoli WHERE MATCH(titolo,descrizione)
AGAINST ('cane gatto topo' IN BOOLEAN MODE) ORDER 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.