Примерный поиск и сортировка результатов

Всем добрый вечер!
Есть документы, в которых хранится 2 поля: Имя и фамилия.
Нужно создать запрос, который выводил бы документы, В имени и/или фамилии содержится запрос.
Запросы могут выть вида: "Марина карпова", "м карп", "к марина" и т.п. и отсортировать их по релевантности. Как лучше это реализовать?
Пробовал через fuzzyquery, но результаты не совсем ожидаемы. Хотя, скорее всего, я не верно их настроил. Только начал изучать es, по этому все для меня в новинку. Если можно, подскажите, как лучше это сделать или же дайте ссылки, где можно почитать. Смотрел документацию, но там много всего, а мне нужно как можно скорее.
Буду благодарен за помощь.

Для поиска под подстроке лучше всего воспользоваться edgeNGram фильтром. Что касается релевантности, то тут много решений - все зависит от того, что вы под это релевантностью понимаете.

Мне нужно поиск как, скажем, в вк. Только пока только по 2-м полям - имя и фамилия. fuzzy для опечаток, но и корректное выставление релевантности в зависимости от совпадения запроса с данными в эс. Можно какой-то пример? Может, Вы есть в вк или skype, для оперативного общения?
Спасибо.

К сожалению, у меня не всегда есть возможность оперативно отвечать на вопросы на форуме, а русско-говорящее сообщество elasticsearch еще не достаточно велико для оказания эффективной самоподдержки. Поэтому для оперативного общения вам лучше обратиться сюда.

То есть, вам надо организовать поиск по полному совпадению, по совпадению начала слова и по полному совпадению с опечатками. Полные совпадения должны быть более релевантны, чем совпадения по части слова и совпадения с опечатками. Когда вы ищите, то информация о том, какая часть запроса имя, а какая часть запроса относится к фамилии отсутствует, и пользователь может везти одно, два или даже больше слов в запросе. Я правильно Вас понял? И еще вопрос - имена и фамилии написаны исключительно на русском?

Да, все верно.

имена и фамилии написаны исключительно на русском?

В идеале, может быть написано на любом языке.
Сейчас использую следующий запрос:
$search = array(
'query' => array(
'multi_match' => array(
'fields' => ['name', 'lName'],
'query' => urldecode($_GET['q']),
'fuzziness' => 'auto',
),
),
);
При помощи чего можно реализовать тот поиск, который Вы описали?

Да, Вы поняли меня совершенно верно. В идеале, данные могут быть написаны на любом языке.
Сейчас использую запрос:
$search = array(
'query' => array(
'multi_match' => array(
'fields' => ['name', 'lName'],
'query' => urldecode($_GET['q']).'*',
'fuzziness' => 'auto',
),
),
);

Как можно реализовать поиск в таком варианте, как Вы описали?

Ну тогда, как-то так. Вам надо будет поиграться с boost-ами поскольку я не знаю, что для вас важнее - полное совпадение по одному слову или не полное по двум и т. д. Но, надеюсь, основная идея из этого примера очевидна - ищем разными способами и потом выбираем вариант (разные multi_match), который более всего подошел (dis_max)

curl -XDELETE "localhost:9200/test?pretty"
curl -XPUT "localhost:9200/test?pretty" -d '{
  "settings": {
    "number_of_replicas": 0,
    "number_of_shards": 1,
    "analysis": {
      "analyzer": {
        "ngram_index_analyzer": {
          "tokenizer": "standard",
          "filter": ["lowercase", "ngram"]
        },
        "ngram_search_analyzer": {
          "tokenizer": "standard",
          "filter": ["lowercase"]
        }
      },
      "filter": {
        "ngram": {
          "type": "edgeNGram",
          "min_gram": 1,
          "max_gram": 50
        }
      }
    }
  },
  "mappings" : {
    "person" : {
      "properties" : {
        "first_name" : {
          "type" : "string",
          "fields": {
            "ngram": {
              "type" : "string",
              "analyzer": "ngram_index_analyzer",
              "search_analyzer": "ngram_search_analyzer"
            }
          }
        },
        "last_name" : {
          "type" : "string",
          "fields": {
            "ngram": {
              "type" : "string",
              "analyzer": "ngram_index_analyzer",
              "search_analyzer": "ngram_search_analyzer"
            }
          }
        }
      }
    }
  }
}'
curl -XPUT "localhost:9200/test/person/1?pretty" -d '{
  "first_name": "John",
  "last_name": "Smith"
}'
curl -XPUT "localhost:9200/test/person/2?pretty" -d '{
  "first_name": "Jill",
  "last_name": "Smith"
}'
curl -XPUT "localhost:9200/test/person/3?pretty" -d '{
  "first_name": "Johan",
  "last_name": "Smith"
}'
curl -XPUT "localhost:9200/test/person/4?pretty" -d '{
  "first_name": "John",
  "last_name": "Brown"
}'
curl -XPOST "localhost:9200/test/_refresh?pretty"
curl -XGET "localhost:9200/test/person/_search?pretty" -d '{
  "query": {
    "dis_max": {
      "queries": [
        {
          "multi_match" : {
            "query": "S. John",
            "fields": [ "first_name", "last_name" ],
            "type": "most_fields",
            "boost": 3.0
          }
        },
        {
          "multi_match": {
            "query": "S. John",
            "fields": [ "first_name", "last_name" ],
            "type": "most_fields",
            "fuzziness": "auto",
            "boost": 2.0
          }
        },
        {
          "multi_match": {
            "query": "S. John",
            "fields": [ "first_name.ngram", "last_name.ngram" ],
            "type": "most_fields",
            "boost": 1.0
          }
        }
      ]
    }
  }
}'

Удачи!

В целом, понял. Но не могу пока разобратся, в чем дело: передаю В индекс:
{ "settings": { "number_of_replicas": 0, "number_of_shards": 1, "analysis": { "analyzer": { "ngram_index_analyzer": { "tokenizer": "standart", "filter": [ "lowercase", "ngram" ] }, "ngram_search_analyzer": { "tokenizer": "standart", "filter": [ "lowercase" ] } }, "filter": { "ngram": { "type": "edgeNGram", "min_gram": 1, "max_gram": 50 } } } }, "mappings": { "user": { "properties": { "first_name": { "type": "string", "fields": { "ngram": { "type": "string", "analyzer": "ngram_index_analyzer", "search_analyzer": "ngram_search_analyzer" } } }, "last_name": { "type": "string", "fields": { "ngram": { "type": "string", "analyzer": "ngram_index_analyzer", "search_analyzer": "ngram_search_analyzer" } } } } } } }
Но получаю ошибку:
{"error":{"root_cause":[{"type":"index_creation_exception","reason":"failed to create index"}],"type":"illegal_argument_exception","reason":"Custom Analyzer [ngram_search_analyzer] failed to find tokenizer under name [standart]"},"status":400}

Большое спасибо, Игорь! Я нашел свою ошибку и все исправил. Не думал просто, что standart пишеться с 'd' на конце :slight_smile: и чисто автоматом написал. Сорри.

Добрый день. Возникает еще вопрос: Чем отличаются 1 и 3 запросы в поиске, который Вы предложили, Игорь?
В том плане, что как работает fields=field.ngram

Вместо того, чтобы индексировать поле целиком, edgngram отрывает от слова начало и индескирует его по кусочкам. То есть, при индексировании записи со словом марина, вместо добавления в индекс только слова марина, он добавляет слова м, ма, мар, мари, марин и марина и все эти слова указывают на эту запись. Таким образом вы можете найти эту запись по любому началу.

Подскажите еще, пожалуйста, как реализовать более высокий boosting для, скажем, заголовка, чем описания товара?

https://www.elastic.co/guide/en/elasticsearch/guide/current/multi-match-query.html#_boosting_individual_fields