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
          }
        }
      }
    }
  }
}
Другие вопросы по тегам