En el trabajo diario como desarrollador de aplicaciones, es común llegar a necesitar extender la funcionalidad existente en tu manejador de bases de datos para que en tus cadenas sql puedas usar tal funcionalidad como si fuera nativa del manejador.

Me explico mejor con un ejemplo: Supongamos que requiero calcular la “distancia” entre dos palabras con la métrica de levenshtein y esto deseo calcularlo para cada túpla en mi tabla “Personas” aplicado a los atributos apPat y apMat. Mi cadena sql luciría mas o menos así:

SELECT id, apPat, apMat, levenshtein(apPat, apMat) as distancia FROM Personas;

Arrojando un resultado como el siguiente:

+—-+———+———-+———–+

| id | apPat   | apMat    | distancia |

+—-+———+———-+———–+

| 1 | Arellano | Arellani | 1         |

| 2 | Arevalo  | Orevale  | 2         |

| 3 | Benitez  | Venitez  | 2         |

| 4 | Cardona  | Cardona  | 0         |

| 5 | Corona   | Lopez    | 6         |

+—-+———+———-+———–+

5 rows in set (0.07 sec)

Pero, cómo lograrlo?

Bueno, encontré dos formas. Una fácil, pero con cierto castigo en el desempeño de la ejecución de la consulta. Y uno difícil (y que NO sé cómo llevar a cabo en Windows, sólo lo efectué en LINUX) pero con un desempeño excelente.

Primero, el primero:

Desde tu consola de MySQL y habiendo ya creado y seleccionado la base de datos ( y la tabla “Personas” con algunos datos de prueba) llevar a cabo lo siguiente:

mysql> delimiter //

mysql> 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;

//

 

 

mysql> delimiter ;

Ahora ya podemos ejecutar el query que mencionaba originalmente, obteniendo el resultado descrito, y hasta queries como:

SELECT * from Personas where levenshtein(apPat, apMat)<2;

PARTE II

La forma “difícil” es usando C, compilando el código ya declarándolo en mySQL. Va más o menos así:

Con el código en C (que puedes obtener del tar de la liga siguiente, o puedes pedírmelo y lo desempolvo de mi privado/software/tars ) hice lo siguiente:

%> sudo yum install mysqld gcc-c++

%> wget http://joshdrew.com/mysql_levenshtein_udf-1.0.tar.gz

%> tar zxf mysql_levenshtein_udf-1.0.tar.gz

%> gcc -shared -o mysqllevenshtein.so mysqllevenshtein.cc -I/usr/include/mysql/

%> sudo cp mysqllevenshtein.so /usr/lib/

y en MySQL:

mysql> use miBaseDeDatos;

mysql> CREATE FUNCTION levenshtein RETURNS INT SONAME ‘mysqllevenshtein.so’;

Y ya quedó. El desempeño es 100% mejor, ya que baja de 0.07 a 0.00. (La verdad, no es un 0 absoluto, pero con ello puedo ver que tardó menos de 0.00 y con eso, es suficiente para mi.)

Hasta pronto!

Goose

Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)

© 2019 Goose Workshop Suffusion theme by Sayontan Sinha