Elasticsearch PHP совпадение с самым длинным префиксом
В настоящее время я использую FOSElasticaBundle в Symfony2, и мне трудно пытаться создать поиск, соответствующий самому длинному префиксу.
Мне известны 100 примеров, которые есть в Интернете для выполнения поиска, подобного автозаполнению. Однако моя проблема немного другая.
В типе поиска с автозаполнением база данных содержит самую длинную буквенно-цифровую строку (длиной символов), а пользователь просто предоставляет самую короткую часть, скажем, пользовательские типы "jho" и Elasticsearch могут легко предоставить "Jhon, Jhonny, Jhonas".
Моя проблема в обратном направлении, я хотел бы предоставить самую длинную буквенно-цифровую строку, и я хочу, чтобы Elasticsearch предоставил мне самое большое совпадение в базе данных.
Например: я мог бы предоставить "123456789", и моя база данных может иметь [12,123,14,156,16,7,1234,1,67,8,9,123456,0], в этом случае самый длинный совпадение префикса в базе данных для номер, который предоставил пользователь, "123456".
Я только начинаю с Elasticsearch, поэтому у меня нет близко к рабочим настройкам или что-то еще.
Если есть какая-либо информация, которая не ясна или отсутствует, сообщите мне, и я предоставлю более подробную информацию.
Обновление 1 (с использованием второго обновления Val)
Индекс: Скачать 1800+ индексов
Settings:
curl -XPUT localhost:9200/tests -d '{
"settings": {
"analysis": {
"analyzer": {
"edge_ngram_analyzer": {
"tokenizer": "edge_ngram_tokenizer",
"filter": [ "lowercase" ]
}
},
"tokenizer": {
"edge_ngram_tokenizer": {
"type": "edgeNGram",
"min_gram": "2",
"max_gram": "25"
}
}
}
},
"mappings": {
"test": {
"properties": {
"my_string": {
"type": "string",
"fields": {
"prefix": {
"type": "string",
"analyzer": "edge_ngram_analyzer"
}
}
}
}
}
}
}'
Query:
curl -XPOST localhost:9200/tests/test/_search?pretty=true -d '{
"size": 1,
"sort": {
"_script": {
"script": "doc.my_string.value.length()",
"type": "number",
"order": "desc"
},
"_score": "desc"
},
"query": {
"filtered": {
"query": {
"match": {
"my_string.prefix": "8092232423"
}
},
"filter": {
"script": {
"script": "doc.my_string.value.length() <= maxlength",
"params": {
"maxlength": 10
}
}
}
}
}
}'
With this configuration the query returns the following results:
{
"took" : 61,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 1754,
"max_score" : null,
"hits" : [ {
"_index" : "tests",
"_type" : "test",
"_id" : "AU8LqQo4FbTZPxBtq3-Q",
"_score" : 0.13441172,
"_source":{"my_string":"80928870"},
"sort" : [ 8.0, 0.13441172 ]
} ]
}
}
Бонусный вопрос
Я хотел бы предоставить массив чисел для этого поиска и получить соответствующий префикс для каждого эффективного способа без необходимости каждый раз выполнять запрос
1 ответ
Вот мой взгляд на это.
По сути, нам нужно нарезать и нарезать кубиками поле (называемое my_string
ниже) во время индексации с edgeNGram
токенизатор (называется edge_ngram_tokenizer
ниже). Таким образом, строка как 123456789
будет помечен на 12
, 123
, 1234
, 12345
, 123456
, 1234567
, 12345678
, 123456789
и все токены будут проиндексированы и доступны для поиска.
Итак, давайте создадим tests
индекс, пользовательский анализатор называется edge_ngram_analyzer
анализатор и test
отображение, содержащее одно строковое поле с именем my_string
, Вы заметите, что my_string
поле является мультиполем, объявляющим prefixes
подполе, которое будет содержать все префиксы токенов.
curl -XPUT localhost:9200/tests -d '{
"settings": {
"analysis": {
"analyzer": {
"edge_ngram_analyzer": {
"tokenizer": "edge_ngram_tokenizer",
"filter": [ "lowercase" ]
}
},
"tokenizer": {
"edge_ngram_tokenizer": {
"type": "edgeNGram",
"min_gram": "2",
"max_gram": "25"
}
}
}
},
"mappings": {
"test": {
"properties": {
"my_string": {
"type": "string",
"fields": {
"prefixes": {
"type": "string",
"index_analyzer": "edge_ngram_analyzer"
}
}
}
}
}
}
}
Тогда давайте индексировать несколько test
документы с использованием _bulk
API:
curl -XPOST localhost:9200/tests/test/_bulk -d '
{"index":{}}
{"my_string":"12"}
{"index":{}}
{"my_string":"1234"}
{"index":{}}
{"my_string":"1234567890"}
{"index":{}}
{"my_string":"abcd"}
{"index":{}}
{"my_string":"abcdefgh"}
{"index":{}}
{"my_string":"123456789abcd"}
{"index":{}}
{"my_string":"abcd123456789"}
'
Особенность, которую я нашел, заключалась в том, что результат сопоставления мог быть длиннее или короче входной строки. Чтобы достичь этого, мы должны объединить два запроса: один ищет более короткие совпадения, а другой - более длинные. Итак match
запрос найдет документы с более короткими "префиксами", соответствующими вводу и query_string
запрос (с edge_ngram_analyzer
применяется к входной строке!) будет искать "префиксы" длиннее входной строки. Оба заключены в bool/should
и отсортированный по убыванию длины строки (т. е. самый длинный сначала) сделает свое дело.
Давайте сделаем несколько запросов и посмотрим, что развернется:
Этот запрос вернет один документ с наибольшим совпадением для "123456789", то есть "123456789abcd". В этом случае результат длиннее, чем вход.
curl -XPOST localhost:9200/tests/test/_search -d '{
"size": 1,
"sort": {
"_script": {
"script": "doc.my_string.value.length()",
"type": "number",
"order": "desc"
}
},
"query": {
"bool": {
"should": [
{
"match": {
"my_string.prefixes": "123456789"
}
},
{
"query_string": {
"query": "123456789",
"default_field": "my_string.prefixes",
"analyzer": "edge_ngram_analyzer"
}
}
]
}
}
}'
Второй запрос вернет один документ с наибольшим совпадением для "123456789abcdef", то есть "123456789abcd". В этом случае результат короче, чем ввод.
curl -XPOST localhost:9200/tests/test/_search -d '{
"size": 1,
"sort": {
"_script": {
"script": "doc.my_string.value.length()",
"type": "number",
"order": "desc"
}
},
"query": {
"bool": {
"should": [
{
"match": {
"my_string.prefixes": "123456789abcdef"
}
},
{
"query_string": {
"query": "123456789abcdef",
"default_field": "my_string.prefixes",
"analyzer": "edge_ngram_analyzer"
}
}
]
}
}
}'
Я надеюсь, что это охватывает. Дайте мне знать, если нет.
Что касается вашего бонусного вопроса, я бы просто предложил использовать _msearch
API и отправка всех запросов одновременно.
ОБНОВЛЕНИЕ: Наконец, убедитесь, что сценарии включены в вашем elasticsearch.yml
файл с использованием следующего:
# if you have ES <1.6
script.disable_dynamic: false
# if you have ES >=1.6
script.inline: on
ОБНОВЛЕНИЕ 2 Я оставляю вышеупомянутое, поскольку случай использования мог бы удовлетворить чьи-то потребности. Теперь, поскольку вам нужны только "более короткие" префиксы (имеет смысл!!), нам нужно немного изменить отображение и запрос.
Отображение будет выглядеть так:
{
"settings": {
"analysis": {
"analyzer": {
"edge_ngram_analyzer": {
"tokenizer": "edge_ngram_tokenizer",
"filter": [
"lowercase"
]
}
},
"tokenizer": {
"edge_ngram_tokenizer": {
"type": "edgeNGram",
"min_gram": "2",
"max_gram": "25"
}
}
}
},
"mappings": {
"test": {
"properties": {
"my_string": {
"type": "string",
"fields": {
"prefixes": {
"type": "string",
"analyzer": "edge_ngram_analyzer" <--- only change
}
}
}
}
}
}
}
И запрос теперь будет немного другим, но всегда будет возвращать только самый длинный префикс, но короче или равной длине входной строки. Пожалуйста, попробуйте. Я советую переиндексировать ваши данные, чтобы убедиться, что все настроено правильно.
{
"size": 1,
"sort": {
"_script": {
"script": "doc.my_string.value.length()",
"type": "number",
"order": "desc"
},
"_score": "desc" <----- also add this line
},
"query": {
"filtered": {
"query": {
"match": {
"my_string.prefixes": "123" <--- input string
}
},
"filter": {
"script": {
"script": "doc.my_string.value.length() <= maxlength",
"params": {
"maxlength": 3 <---- this needs to be set to the length of the input string
}
}
}
}
}
}