web-dev-qa-db-fra.com

Levenshtein: MySQL + PHP

$Word = strtolower($_GET['term']); 

$lev = 0;

$q = mysql_query("SELECT `term` FROM `words`"); 
while($r = mysql_fetch_assoc($q)) 
{ 
    $r['term'] = strtolower($r['term']); 

    $lev = levenshtein($Word, $r['term']);

    if($lev >= 0 && $lev < 5)
    {
        $Word = $r['term'];
    }
}

Comment puis-je déplacer tout cela dans une seule requête? Je ne veux pas avoir à interroger tous les termes et faire le filtrage en PHP.

31
user317005

Vous avez besoin d'un fonction levenshtein dans MySQL et interrogez comme

$Word = mysql_real_escape_string($Word);
mysql_qery("SELECT `term` FROM `words` WHERE levenshtein('$Word', `term`) BETWEEN 0 AND 4");
59
rik

Il existe deux façons d'implémenter une fonction Levenshtein dans MySQL. La première consiste à créer une FONCTION MÉMORISÉE qui fonctionne un peu comme une TRANSACTION MÉMORISÉE, sauf qu'elle a des entrées et une sortie distinctes. C'est bien pour les petits ensembles de données, mais un peu lent sur tout ce qui approche plusieurs milliers de lignes.

CREATE FUNCTION levenshtein( s1 VARCHAR(255), s2 VARCHAR(255) )
RETURNS INT
DETERMINISTIC

BEGIN
DECLARE s1_len, s2_len, i, j, c, c_temp, cost INT;
DECLARE s1_char CHAR;
-- max strlen=255
DECLARE cv0, cv1 VARBINARY(256);
SET s1_len = CHAR_LENGTH(s1), s2_len = CHAR_LENGTH(s2), cv1 = 0x00, j = 1, i = 1, c = 0;
IF s1 = s2 THEN
  RETURN 0;
ELSEIF s1_len = 0 THEN
  RETURN s2_len;
ELSEIF s2_len = 0 THEN
  RETURN s1_len;
ELSE
  WHILE j <= s2_len DO
    SET cv1 = CONCAT(cv1, UNHEX(HEX(j))), j = j + 1;
  END WHILE;
  WHILE i <= s1_len DO
    SET s1_char = SUBSTRING(s1, i, 1), c = i, cv0 = UNHEX(HEX(i)), j = 1;
    WHILE j <= s2_len DO
    SET c = c + 1;
    IF s1_char = SUBSTRING(s2, j, 1) THEN
      SET cost = 0; ELSE SET cost = 1;
    END IF;
    SET c_temp = CONV(HEX(SUBSTRING(cv1, j, 1)), 16, 10) + cost;
    IF c > c_temp THEN SET c = c_temp; END IF;
      SET c_temp = CONV(HEX(SUBSTRING(cv1, j+1, 1)), 16, 10) + 1;
      IF c > c_temp THEN
        SET c = c_temp;
      END IF;
      SET cv0 = CONCAT(cv0, UNHEX(HEX(c))), j = j + 1;
    END WHILE;
    SET cv1 = cv0, i = i + 1;
  END WHILE;
END IF;

RETURN c;

END//

Stockez le code ci-dessus dans un fichier .sql et importez-le dans votre base de données comme suit:

source /tmp/mysql_udf.sql

La deuxième méthode consiste à implémenter une fonction définie par l'utilisateur en C/C++ et à la lier à MySQL en tant que bibliothèque partagée (fichier * .so). Cette méthode utilise également une fonction stockée pour appeler la bibliothèque, ce qui signifie que la requête réelle pour cette méthode ou la première peut être identique (à condition que les entrées des deux fonctions soient identiques). Vous pouvez en savoir plus sur cette méthode ici: http://samjlevy.com/mysql-levenshtein-and-damerau-levenshtein-udfs/

Avec l'une ou l'autre de ces méthodes, votre requête ressemblerait à quelque chose comme:

SELECT term FROM words WHERE levenshtein(term, 'term') < 5;

N'oubliez pas non plus que la valeur du "seuil" doit changer par rapport à la longueur d'origine du mot. Il vaut mieux y penser en termes de valeur en pourcentage, c'est-à-dire la moitié de votre mot = 50%, la moitié de `` terme '' = 2.

11
Gordon Freeman

Si vous avez une énorme base de données, vous pouvez d'abord filtrer les mots à l'aide de SOUNDEX:

$Word = strtolower(mysql_real_escape_string($_GET['term']));

$rs = mysql_query("SELECT LOWER(`term`) FROM `words` WHERE SOUNDEX(term) = SOUNDEX(" . $Word . ")");

while ($row = mysql_fetch_assoc($rs)) { 

    $lev = levenshtein($Word, $row['term']);

    ....

}

Si vous avez suffisamment de temps pour jouer avec une extension ou une procédure C, vous pouvez obtenir de meilleures performances, mais le filtrage des enregistrements sur mysql avant d'appliquer le vrai levenshtein accélérera les choses presque sans effort.

8
carlosvini

Si vous traitez des ensembles de données très volumineux, j'ai constaté qu'il est beaucoup plus efficace de gérer les opérations Levenshtein et le tri en PHP que dans MySQL. Par exemple, requête d'environ 1000 enregistrements:

MySQL (~ 0.0050s) -> PHP Levenshtein (~ 1.300s)

vs.

MySQL Levenshtein (> = 5.000s) -> PHP (~ 0.250s)

Il existe également de nombreuses autres options pour optimiser les moteurs de recherche, mais si vous souhaitez utiliser Levenshtein, sachez simplement les données que vous allez gérer et les latences que vous souhaitez.

6
John Rausch

Je vous suggère d'inclure l'appel du levenshtein (lien: http://www.artfulsoftware.com/infotree/queries.php#552 ) dans votre requête.

Vous devez utiliser mysqli_query ($ q) car mysql_query ($ q) est obsolète et pourrait être supprimé dans les futures versions de php!

$Word = mysql_real_escape_string($Word);
$query = "SELECT `term` FROM `words` WHERE levenshtein('$Word', `term`)   BETWEEN 0 AND 4";
mysqli_qery($query);
1
Coder55

Vous pouvez rendre ce code un peu plus net mais @profitphp a raison, vous ne pouvez pas le faire dans MySQL sans une bibliothèque levenstein.

 $ Word = strtolower ($ _ GET ['term']); 
 
 $ Q = mysql_uqery ("SELECT LOWER (` term`) FROM `words`"); 
 
 while ($ r = mysql_fetch_assoc ($ q)) {
 
 $ lev = levenshtein ($ Word, $ r ['term'])) 
 
 .... 
 
} 
1
mvbl fst

Je le fais dans Oracle en implémentant l'algorithme en PL/SQL dans une fonction qui peut être appelée.

0
Randy