Пересечение по тэгам

Здравствуйте!
Дано: в каждой аккаунт-карточке которую я храню в ЭС, есть поле "тэги", в котором лежит набор ключевых слов для каждого аккаунта. Это string.
Мне нужно пробежаться по всей бд и найти процент совпадения этого массива с каждым аккаунтом (то есть сколько слов совпало - и процент от общего количества в массиве). Можно неточный поиск (например, до 5 первых символов). (Пишу на java, но видела что-то похожее в php - similar...).
Есть ли похожий анализатор в Elasticsearch?

А можно пример с данными, запросом и желаемым рузультатом?

Вот базовая часть мэппинга, искать пересечения нужно будет по полям features, tags (пока для теста делаю двумя отдельными запросами, так как конечный результат будет еще и по локации фильтровать, работаю впервые с Эластик, боюсь запутаться).
Суть в том, чтобы поставить фильтр на количество совпадений по тэгам. Напр., получать какой-то score, сколько совпало, а уже в скрипте вычислять процент от длины стринга. В идеале - делать расчет процента совпадения прямо во время фильтра, конечно.

Чтобы выдача была больше (юзеры будут писать что попало в любом случае), думаю указать fuzzyness 1-2. Насколько поняла из туториала, мой stringForSearch будет сплиттован по пробелам - и каждое слово будет проверяться отдельно. Но соблюдается ли порядок слов в этом случае? (т.е. если тэги лежат в базе в другом порядке, найдутся ли совпадения? или ищет согласно порядку в исходном стринге?)
Извините, если вопросы довольно поверхностные. Это мой первый опыт работы с этой базой.

По поводу процентной планки для фильтра - нашла вариант c параметром ".minimumShouldMatch("...%"), если генерировать Query через java class. Но я пишу вручную для аннотации @Query и репозитория.

    {
        "posts": {
            "mappings": {
                "properties": {
                    "_class": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "features": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "email": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "flag": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "id": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "location": {
                        "type": "geo_point"
                    },
                    "tags": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    },
                    "type": {
                        "type": "text",
                        "fields": {
                            "keyword": {
                                "type": "keyword",
                                "ignore_above": 256
                            }
                        }
                    }
                }
            }
        }
    }

Что значит "искать пересечения"? Вы не могли бы показать на примере?

Постараюсь объяснить:

  1. Получаю из es пост по id, беру из него строку=массив тэгов (слова разделены пробелами). Судя по документации, можно пользоваться string без трансформации в массив.

String arrayToSearch = customRepo.findById("id").getTags()...;

(Напр., "хороший вкусный свежий необычный дизайн")

  1. В запросе (@query) мне нужно передать эту строку для поиска совпадений.

  2. Es должен перебирать в индексе каждый пост, брать из него tags (такого же формата) и сравнивать с arrayToSearch (содержится ли каждое слово по отдельности из arrayToSearch в Поле tags у перебираемого поста). Программно я бы сделала, как вариант, через set.retainAll() и дальше бы считала процент. Но тогда теряется вообще смысл в использовании движка Es.

  3. Моя цель - получить процент содержания arrayToSearch в tags каждого поста. Например, arrayToSearch.length=10, 3 слова совпали (НЕ по порядку, а по отдельности) с tags. Остальные 7 не совпали. Значит, я должна получить 30%. Под этим я подразумеваю пересечение массивов/строк.

  4. В плане фильтрации мне достаточно неточного поиска (например, "добряк" должен показывать совпадение с "добрый", "доброта", "добротный"). Если верно понимаю, для этого мне нужно установить fuzzy.

Что можете сказать? Есть ли похожие анализаторы? В каком направлении стоит копать?

Спасибо!

Вообще, этим занимается запрос more_like_this. Однако, это запрос рассчитывает не процент совпадений а их релевантность, то есть он предпочитает совпадение по редким тэгам совпадениям по часто встречающимся, что, как-правило, улучшает качество результатов.

Если MLT для вас не работает, и вам действительно надо процент совпадений, то есть много других способов. Можно, например, просто поместить все слова в отдельные запросы match, завернуть их в constant_score, и поместить в элементы should, тогда score всего запроса будет равняться количеству совпавших слов. А можно все поместить в один match и контролировать расчет score с помощью скриптов. Но если вы только начинаете работать с elasticsearch, я бы порекомендовал туда пока не соваться.

В плане неточного поиска, нужно настроить анализатор. Лучше всего добавить фильтр hunspell, или, если важна скорость больше чем точность, добавить фильтр stemmer.

1 Like

Спасибо! Не сразу увидела Ваш ответ, и пока ждала - немного поменяла тактику: ищу поиск всех постов по проценту совпадения строки. Вот такой простенький вариант сработал (на случай, если кому-то тоже пригодится для подобной задачи):

  "query": {
        "query_string": {
            "fields": [
                "title"
            ],
            "query": "this that thus",
            "minimum_should_match": 2
        }
    }

Ваши идеи тоже изучу и попробую, спасибо большое!
Еще в процессе возник вопрос о применении нескольких фильтров с тенденцией на сужение списка для поиска. То есть можно ли в параметре matchAll, например, задать фильтрацию по градации:

  • сначала выбирает только те, что подходят по гендеру (например),
  • из них по локации в заданной дистанции,
  • из них по совпадению тегов (то, что выше написала: по количеству совпавших слов из заданной строки).
    То есть цель - чтобы каждый фильтр не прогонять, конечно, через весь миллион постов.
    Вижу пока в документации только варианты с несколькими фильтрами через запятую - это работает на поэтапное сужение?
    Спасибо!

Лучше заменить query_string на match, потому что query_string пытается распарсить вашу строку как запрос и могут возникать всякие странные проблемы. MatchAll ничего не фильтрует и параметров (кроме буста) не имеет, фильтрацию лучше задавать через запрос bool. Туда же можно добавить фильтрацию по дистанции через geo_distance.

Язык запросов Elasticsearch и подход к поиску весьма уникален. Поэтому я бы порекомендовал почитать книжку или взять курсы. Можно, конечно, пробиться путем проб и ошибок, но я думаю, с книжкой или курсами будет проще.

1 Like

Спасибо вам большое!