Сохранение порядка терминов в запросе ElasticSearch
Возможно ли в ElasticSearch сформировать запрос, который бы сохранил порядок терминов?
Простым примером будет индексирование этих документов с использованием стандартного анализатора:
- Вы знаете, для поиска
- Вы знаете, поиск
- Знай поиск для тебя
Я мог бы запросить +you +search
и это вернуло бы мне все документы, включая третий.
Что если бы я хотел получить только те документы, которые имеют условия в этом конкретном порядке? Могу ли я сформировать запрос, который бы сделал это для меня?
Учитывая, что это возможно для фраз, просто цитируя текст: "you know"
(получить 1-й и 2-й документы) мне кажется, что должен быть способ сохранить порядок для нескольких терминов, которые не являются смежными.
В приведенном выше простом примере я мог бы использовать поиск по близости, но это не охватывает более сложные случаи.
3 ответа
Вы могли бы использовать span_near
запрос, он имеет in_order
параметр.
{
"query": {
"span_near": {
"clauses": [
{
"span_term": {
"field": "you"
}
},
{
"span_term": {
"field": "search"
}
}
],
"slop": 2,
"in_order": true
}
}
}
Соответствие фраз не гарантирует порядок;-). Если вы укажете достаточно склонов, например 2, например, "hello world" будет соответствовать "world hello". Но это не обязательно плохо, потому что обычно поиски более актуальны, если два термина "близки" друг к другу и их порядок не имеет значения. И я не думаю, что авторы этой функции думали о совпадении слов, которые находятся на расстоянии 1000 пометок друг от друга.
Есть решение, которое я мог бы найти, чтобы сохранить порядок, хотя и не простое: использование скриптов. Вот один пример:
POST /my_index/my_type/_bulk
{ "index": { "_id": 1 }}
{ "title": "hello world" }
{ "index": { "_id": 2 }}
{ "title": "world hello" }
{ "index": { "_id": 3 }}
{ "title": "hello term1 term2 term3 term4 world" }
POST my_index/_search
{
"query": {
"filtered": {
"query": {
"match": {
"title": {
"query": "hello world",
"slop": 5,
"type": "phrase"
}
}
},
"filter": {
"script": {
"script": "term1Pos=0;term2Pos=0;term1Info = _index['title'].get('hello',_POSITIONS);term2Info = _index['title'].get('world',_POSITIONS); for(pos in term1Info){term1Pos=pos.position;}; for(pos in term2Info){term2Pos=pos.position;}; return term1Pos<term2Pos;",
"params": {}
}
}
}
}
}
Чтобы сделать сам скрипт более читабельным, я переписываю здесь с отступами:
term1Pos = 0;
term2Pos = 0;
term1Info = _index['title'].get('hello',_POSITIONS);
term2Info = _index['title'].get('world',_POSITIONS);
for(pos in term1Info) {
term1Pos = pos.position;
};
for(pos in term2Info) {
term2Pos = pos.position;
};
return term1Pos < term2Pos;
Выше приведен запрос, который ищет слово "hello world" со значением 5, которое в приведенных выше документах будет соответствовать всем им. Но скрипт-фильтр гарантирует, что позиция в документе со словом "привет" ниже, чем позиция в документе для слова "мир". Таким образом, независимо от того, сколько слотов мы установили в запросе, тот факт, что позиции располагаются одна за другой, обеспечивает порядок.
Это раздел в документации, который проливает некоторый свет на вещи, используемые в приведенном выше сценарии.
Это именно то, что match_phrase
запрос (см. здесь) делает.
Он проверяет положение терминов, помимо их присутствия.
Например, эти документы:
POST test/values
{
"test": "Hello World"
}
POST test/values
{
"test": "Hello nice World"
}
POST test/values
{
"test": "World, I don't say hello"
}
все будет найдено с основным match
запрос:
POST test/_search
{
"query": {
"match": {
"test": "Hello World"
}
}
}
Но используя match_phrase
, будет возвращен только первый документ:
POST test/_search
{
"query": {
"match_phrase": {
"test": "Hello World"
}
}
}
{
...
"hits": {
"total": 1,
"max_score": 2.3953633,
"hits": [
{
"_index": "test",
"_type": "values",
"_id": "qFZAKYOTQh2AuqplLQdHcA",
"_score": 2.3953633,
"_source": {
"test": "Hello World"
}
}
]
}
}
В вашем случае вы хотите согласиться на некоторое расстояние между вашими условиями. Это может быть достигнуто с помощью slop
параметр, который указывает, насколько далеко вы позволяете вашим терминам быть один от другого:
POST test/_search
{
"query": {
"match": {
"test": {
"query": "Hello world",
"slop":1,
"type": "phrase"
}
}
}
}
С этим последним запросом вы также найдете второй документ:
{
...
"hits": {
"total": 2,
"max_score": 0.38356602,
"hits": [
{
"_index": "test",
"_type": "values",
"_id": "7mhBJgm5QaO2_aXOrTB_BA",
"_score": 0.38356602,
"_source": {
"test": "Hello World"
}
},
{
"_index": "test",
"_type": "values",
"_id": "VKdUJSZFQNCFrxKk_hWz4A",
"_score": 0.2169777,
"_source": {
"test": "Hello nice World"
}
}
]
}
}
Вы можете найти целую главу об этом сценарии использования в окончательном руководстве.