Страницы

Поиск по вопросам

четверг, 9 апреля 2020 г.

Как осуществить поиск похожего текста в MySQL?

#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

Комментариев нет:

Отправить комментарий