#mysql #база_данных #текст #выборка
Дано: таблица с полями id и txt, где txt - небольшая статья, пост или комментарий,
содержащий HTML, примерный объем записи - 2-10 кб, всего записей 100к-1млн.
Нужно извлечь группы записей, в которых текст приблизительно похож, т.е. совпадать
процентов на 80-90, т.к. идентично равных строк в БД нет.
Позволяют ли существующие механизмы БД осуществить такую выборку, и как это сделать?
UPD: (очень близко) есть ли аналог пхп-шного similar_text() в mysql? Скорость выполнения
запроса не имеет значение.
Ответы
Ответ 1
Как было сказано выше, можно попробовать расстояние Левенштейна. Еще один пример
реализации для mysql тут: http://www.artfulsoftware.com/infotree/qrytip.php?id=552
Можно сделать адаптированный вариант с этого примера:
CREATE FUNCTION levenshtein( s1 text, s2 text )
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(10240);
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;
CREATE FUNCTION levenshtein_ratio( s1 text, s2 text )
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE s1_len, s2_len, max_len INT;
SET s1_len = LENGTH(s1), s2_len = LENGTH(s2);
IF s1_len > s2_len THEN
SET max_len = s1_len;
ELSE
SET max_len = s2_len;
END IF;
RETURN ROUND((1 - LEVENSHTEIN(s1, s2) / max_len) * 100);
END;
Выбрать попарно похожие строки так:
select t1.id, t1.txt, t2.id, t2.txt
from table1 t1
join table2 t2 on t2.id <> t1.id and levenshtein_ratio(t1.txt, t2.txt) > 80
Как разбить результат выборки на группы, придется подумать.
По работе, если кратко, то алгоритм считает количество замен/добавлений символов
в текст, чтобы получить полностью схожие записи. Но главная проблема - алгоритм осуществляет
посимволный перебор, что является довольно затратным.
А теперь если учесть, что длина текста 2-10 кб и до 1 млн. записей, то база будет
просто умирать от такой "аналитики" (а не забывайте, что главная задача базы - осуществлять
хранение и доступ к данным, но никак не реализовывать сложные вычислительные алгоритмы).
Поэтому я бы рекомендовал, пока не поздно, подумать над тем, чтобы вынести реализацию
за пределы базы. Можно найти множество "быстрых" реализации вычисления расстояния Левенштейна
практически на всех языках. При реализации предложил бы делать какую-то предварительную
фильтрацию, например по длине текста, т.е. если длина не совпадает на 80%, то сравнение
даже не проводить, т.к. даже на производительных машинах сравнение таких объемов текста
уже будет заниматься ощутимое время.
Ответ 2
Вы можете решить данную задачу с помощью использования хранимых процедур.
Например так:
CREATE PROCEDURE dbo.spSimil_FirstNameLastName
@str1 nvarchar(max),
@threshold float
AS
SET NOCOUNT ON
SELECT *
FROM (SELECT dbo.fnSimil(@str1, Person.Person.FirstName + N' ' +
Person.Person.LastName) AS Simil, * FROM Person.Person) AS T
WHERE T.Simil >= @threshold
ORDER BY T.Simil DESC;
Процедура вызывается следующим образом:
EXEC dbo.spSimil_FirstNameLastName N'John Adams', 0.75
Подробнее можно узнать тут: http://www.accessmvp.com/tomvanstiphout/simil.htm
Для решения вашей задачи можно попробовать прибегнуть к расстояние Левенштейна:
ссылка на реализацию для mysql: https://github.com/ifsnop/damlev