Postgres word_similarity не сравнивает слова

"Возвращает число, указывающее, насколько первая строка похожа на наиболее похожее слово второй строки. Функция ищет во второй строке наиболее похожее слово, а не наиболее похожую подстроку. Диапазон результата равен нулю (что указывает на то, что два строки полностью не похожи) на единицу (указывающую, что первая строка идентична одному из слов второй строки)».

Это определение word_similarity(a,b), насколько я понимаю, оно будет искать СЛОВО a внутри текста b, разбивая b на слова и получая оценку слова с наивысшим совпадением.

Однако я вижу некоторые несоответствия, когда соответствие слов на самом деле не соответствует словам, похоже, что все триграммы перемешаны и сравнены?

Пример:

select word_similarity('sage', 'message sag')

Возвращает 1, очевидно, что ни «сообщение», ни «провисание» не должны совпадать с «мудрецом», но если мы объединим возможные триграммы из «сообщения провисания», мы обнаружим, что все триграммы из «мудреца» совпадут, но это не так. действительно, что должно произойти, так как описание функции говорит о слове за словом... Это потому, что оба слова рядом друг с другом?

Следующее вернет 0,6 балла:

select word_similarity('sage', 'message test sag') 

Изменить: Fiddle, чтобы поиграть с http://sqlfiddle.com/#!17/b4bab/1


person Cristiano Coelho    schedule 27.10.2017    source источник
comment
Я вообще не знаю эту функцию, но sage это подстрока message test sag, если не ошибаюсь.   -  person Tim Biegeleisen    schedule 27.10.2017
comment
Да, но слово_сходство утверждает, что оно проверяется на СЛОВА. Кроме того, word_similarity('мудрец', 'сообщение') --› 0.6 word_similarity('мудрец', 'сообщение s') --› 0.8 word_similarity('мудрец', 'сообщение провисание') --› 1.0 word_similarity('мудрец ', 'message test sag') --› 0.6 выглядит странно и неправильно, вообще не сравнивая триграммы слов.   -  person Cristiano Coelho    schedule 27.10.2017
comment
Что возвращает word_similarity('sage', 'message sag sag')?   -  person YowE3K    schedule 27.10.2017
comment
Также возвращает 1. sqlfiddle.com/#!17/b4bab/1   -  person Cristiano Coelho    schedule 27.10.2017


Ответы (1)


Функция не соответствует описанию

Связанная тема в списке рассылки pgsql-bugs.

Алгоритм подобия подстрок описан автор сравнивает массивы триграмм строки запроса и текста. Проблема в том, что массив триграмм оптимизируется (удаляются повторяющиеся триграммы) и теряет информацию об отдельных словах текста.

Запрос иллюстрирует проблему:

with data(t) as (
values
    ('message'),
    ('message s'),
    ('message sag'),
    ('message sag sag'),
    ('message sag sage')
)

select 
    t as "text", 
    show_trgm(t) as "text trigrams", 
    show_trgm('sage') as "string trigrams", 
    cardinality(array_intersect(show_trgm(t), show_trgm('sage'))) as "common trgms"
from data;

       text       |                       text trigrams                       |       string trigrams       | common trgms 
------------------+-----------------------------------------------------------+-----------------------------+--------------
 message          | {"  m"," me",age,ess,"ge ",mes,sag,ssa}                   | {"  s"," sa",age,"ge ",sag} |            3
 message s        | {"  m","  s"," me"," s ",age,ess,"ge ",mes,sag,ssa}       | {"  s"," sa",age,"ge ",sag} |            4
 message sag      | {"  m","  s"," me"," sa","ag ",age,ess,"ge ",mes,sag,ssa} | {"  s"," sa",age,"ge ",sag} |            5
 message sag sag  | {"  m","  s"," me"," sa","ag ",age,ess,"ge ",mes,sag,ssa} | {"  s"," sa",age,"ge ",sag} |            5
 message sag sage | {"  m","  s"," me"," sa","ag ",age,ess,"ge ",mes,sag,ssa} | {"  s"," sa",age,"ge ",sag} |            5
(5 rows)    

Массивы триграмм в последних трех строках одинаковы и содержат все триграммы строки запроса.

Очевидно, что реализация не соответствует описанию функции (описание было изменено в более поздних выпусках документации):

Возвращает число, указывающее, насколько первая строка похожа на наиболее похожее слово второй строки. Функция ищет во второй строке наиболее похожее слово, а не наиболее похожую подстроку.


Моя функция, используемая в приведенном выше запросе:

create or replace function public.array_intersect(anyarray, anyarray)
returns anyarray language sql immutable
as $$
    select case 
        when $1 is null then $2
        else
            array(
                select unnest($1)
                intersect
                select unnest($2)
            )
        end;
$$;

Обходной путь

Вы можете легко написать свою собственную функцию, чтобы получить более ожидаемые результаты:

create or replace function my_word_similarity(text, text)
returns real language sql immutable as $$
    select max(similarity($1, word))
    from regexp_split_to_table($2, '[^[:alnum:]]') word
$$;

Сравнивать:

with data(t) as (
values
    ('message'),
    ('message s'),
    ('message sag'),
    ('message sag sag'),
    ('message sag sage')
)

select t, word_similarity('sage', t), my_word_similarity('sage', t)
from data;

        t         | word_similarity | my_word_similarity
------------------+-----------------+--------------------
 message          |             0.6 |                0.3
 message s        |             0.8 |                0.3
 message sag      |               1 |                0.5
 message sag sag  |               1 |                0.5
 message sag sage |               1 |                  1
(5 rows)

Новая функция в Postgres 11+

В Postgres 11+ появилась новая функция strict_word_similarity() что дает результаты, ожидаемые автором вопроса:

with data(t) as (
values
    ('message'),
    ('message s'),
    ('message sag'),
    ('message sag sag'),
    ('message sag sage')
)

select t, word_similarity('sage', t), strict_word_similarity('sage', t)
from data;

        t         | word_similarity | strict_word_similarity
------------------+-----------------+------------------------
 message          |             0.6 |                    0.3
 message s        |             0.8 |             0.36363637
 message sag      |               1 |                    0.5
 message sag sag  |               1 |                    0.5
 message sag sage |               1 |                      1
(5 rows)
person klin    schedule 27.10.2017
comment
Это хорошее решение, я ожидаю, что исходная функция будет работать таким образом, я надеялся найти какое-то объяснение ее поведения, даже когда сравнение 1 слова с другим word_similarity возвращает значение, отличное от сходства, где они должны возвращать одно и то же значение, если обе строки имеют 1 слово... Я также ожидаю, что производительность будет хуже при использовании пользовательской функции вместо собственной функции/расширения? - person Cristiano Coelho; 27.10.2017
comment
В моем специальном тесте пользовательская функция работает до 2 раз медленнее, чем исходная. Это хорошая цена за правильные результаты? В любом случае, я думаю, что проблема может быть отмечена как ошибка. - person klin; 27.10.2017
comment
Да, я полагаю, это не так уж и плохо, я просто отказываюсь думать, что это действительно ошибка, поскольку расширение отсутствует уже довольно давно, но, к сожалению, нет никакой полезной информации о поведении функции. - person Cristiano Coelho; 27.10.2017
comment
Я давно пользуюсь pg_trgm и считаю, что это хороший продукт. Однако word_similarity() — относительно новая функция (из Postgres 9.6). Честно говоря, я не знал о его существовании до вчерашнего дня. - person klin; 27.10.2017
comment
word_similarity также тесно связана с операторами %› и ‹% для выполнения одного и того же типа поиска по слову/тексту, используя преимущества индексов gin/gist, и, вероятно, страдает от той же проблемы, которую сложнее обойти. Ваша работа хороша, я надеюсь, вы не возражаете, если я оставлю вопрос открытым на некоторое время на случай, если кто-то знает, что не так с функцией. - person Cristiano Coelho; 27.10.2017
comment
Это конечно нормально, мне тоже интересно. - person klin; 27.10.2017
comment
Сообщается как об ошибке и, похоже, это все-таки ошибка! postgresql-archive.org/ - person Cristiano Coelho; 29.10.2017
comment
если у меня есть индекс джина в каком-то столбце, может ли новая функция my_word_similarity воспользоваться этим? я не думаю, но я хочу убедиться - person Michal; 08.06.2018
comment
Теперь это исправлено в PostgreSQL 11 с использованием новой функции strict_word_similarity(). Исправление основано на этом сообщении SO, представленном @CristianoCoelho. - person Matt; 04.12.2019