Сортировка по началу запроса

Использую эластик как поиск вместо БД.
Не могу добиться правильной релевантности результатов и не очень понимаю в какую сторону копать. Нужна помошь в этом понимании.
Есть поле "наименование" и хочется видеть по нему релевантность ответов как при автокомплите, те сначала совпадение по всему, потом совпадение по началу, а после по вхождению. Причём могут быть одновременно много
разных фильтров, но именно по полю наименование нужна "автокомплитоподобная" сортировка.

Сделал поле "наименование" с типом текст со своим edge_ngram токенайзером, разбивающим от 1‑го до 10. Делаю match query и получаю результат, где подстрока находится в рандомных местах поля.
Пример как должно быть:
пишем слово Иван
В ответ
Иван
Иван и сыновья
Иванов
Автоматизированные системы ивановского

Как бы можно было добиться результата в SQL
ORDER BY name ILIKE concat(#{подстрока}, '%') DESC, name

Сейчас нет идей как можно добиться подобного результата, кроме как посылать первый запрос regexp "*подстрока", а после него второй, что явно костыли.

Узнал, что в эластике есть скоринг и он конечно значительно правит результат поиска в нужную сторону, но не работает идеально (

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

Ok, попробую описать с максимальной детализацией.

Маппинг самого поля от которого хочу нужной мне релевантности

Маппинг
"names": {
      "properties": {
        "entityCode": {
          "type": "keyword"
        },
        "localizedName": {
          "type": "nested",
          "properties": {
            "lang": {
              "type": "keyword"
            },
            "name": {
              "type": "text",
              "fields": {
                "sort": {
                  "type": "keyword"
                }
              },
              "analyzer": "partial_analyzer"
            }
          }
        }
      }
    }

Представляет собой структуру для хранения пар язык - наименование.

Использованный в нём аналайзер

Аналайзер
"analysis": {
    "analyzer": {
      "partial_analyzer": {
        "filter": [
          "lowercase"
        ],
        "type": "custom",
        "tokenizer": "partial_tokenizer"
      }
    },
    "tokenizer": {
      "partial_tokenizer": {
        "token_chars": [
          "letter",
          "digit"
        ],
        "type": "ngram",
        "min_gram": "1",
        "max_gram": "10"
      }
    }
  }

Построенный запрос на поиск в java выливается в такой вот json

Пример поискового запроса
{ 
   "from":0,
   "size":50,
   "query":{ 
      "bool":{ 
         "must":[ 
            { 
               "nested":{ 
                  "query":{ 
                     "bool":{ 
                        "must":[ 
                           { 
                              "match":{ 
                                 "names.localizedName.name":{ 
                                    "query":"арт",
                                    "operator":"AND",
                                    "prefix_length":0,
                                    "max_expansions":50,
                                    "fuzzy_transpositions":true,
                                    "lenient":false,
                                    "zero_terms_query":"NONE",
                                    "auto_generate_synonyms_phrase_query":true,
                                    "boost":1.0
                                 }
                              }
                           }
                        ],
                        "adjust_pure_negative":true,
                        "boost":1.0
                     }
                  },
                  "path":"names.localizedName",
                  "ignore_unmapped":false,
                  "score_mode":"avg",
                  "boost":1.0
               }
            }
         ],
         "adjust_pure_negative":true,
         "boost":1.0
      }
   }
}

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

АРТ"
АРТ+
АРТУР
Рябов Артур Артур
Клюкин Артем Артем
ТК Арт
АРТУР.
АРТЕМ
АРТУР
Артур
АРТ И КО

Первые два ок. Но почему строки Рябов и Клюкин вмешиваются? Почему АРТ и КО находится внизу?

Потому что у вас не edge ngram, a обычный ngram. У вас вот что индексирутся:

POST test/_analyze
{
  "text": ["Рябов Артур Артур"],
  "field": "localizedName.name"
}

{
  "tokens" : [
    {
      "token" : "р",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "ря",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "ряб",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "рябо",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "word",
      "position" : 3
    },
    {
      "token" : "рябов",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "word",
      "position" : 4
    },
    {
      "token" : "я",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "word",
      "position" : 5
    },
    {
      "token" : "яб",
      "start_offset" : 1,
      "end_offset" : 3,
      "type" : "word",
      "position" : 6
    },
    {
      "token" : "ябо",
      "start_offset" : 1,
      "end_offset" : 4,
      "type" : "word",
      "position" : 7
    },
    {
      "token" : "ябов",
      "start_offset" : 1,
      "end_offset" : 5,
      "type" : "word",
      "position" : 8
    },
    {
      "token" : "б",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "word",
      "position" : 9
    },
    {
      "token" : "бо",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "word",
      "position" : 10
    },
    {
      "token" : "бов",
      "start_offset" : 2,
      "end_offset" : 5,
      "type" : "word",
      "position" : 11
    },
    {
      "token" : "о",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "word",
      "position" : 12
    },
    {
      "token" : "ов",
      "start_offset" : 3,
      "end_offset" : 5,
      "type" : "word",
      "position" : 13
    },
    {
      "token" : "в",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "word",
      "position" : 14
    },
    {
      "token" : "а",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "word",
      "position" : 15
    },
    {
      "token" : "ар",
      "start_offset" : 6,
      "end_offset" : 8,
      "type" : "word",
      "position" : 16
    },
    {
      "token" : "арт",
      "start_offset" : 6,
      "end_offset" : 9,
      "type" : "word",
      "position" : 17
    },
    {
      "token" : "арту",
      "start_offset" : 6,
      "end_offset" : 10,
      "type" : "word",
      "position" : 18
    },
    {
      "token" : "артур",
      "start_offset" : 6,
      "end_offset" : 11,
      "type" : "word",
      "position" : 19
    },
    {
      "token" : "р",
      "start_offset" : 7,
      "end_offset" : 8,
      "type" : "word",
      "position" : 20
    },
    {
      "token" : "рт",
      "start_offset" : 7,
      "end_offset" : 9,
      "type" : "word",
      "position" : 21
    },
    {
      "token" : "рту",
      "start_offset" : 7,
      "end_offset" : 10,
      "type" : "word",
      "position" : 22
    },
    {
      "token" : "ртур",
      "start_offset" : 7,
      "end_offset" : 11,
      "type" : "word",
      "position" : 23
    },
    {
      "token" : "т",
      "start_offset" : 8,
      "end_offset" : 9,
      "type" : "word",
      "position" : 24
    },
    {
      "token" : "ту",
      "start_offset" : 8,
      "end_offset" : 10,
      "type" : "word",
      "position" : 25
    },
    {
      "token" : "тур",
      "start_offset" : 8,
      "end_offset" : 11,
      "type" : "word",
      "position" : 26
    },
    {
      "token" : "у",
      "start_offset" : 9,
      "end_offset" : 10,
      "type" : "word",
      "position" : 27
    },
    {
      "token" : "ур",
      "start_offset" : 9,
      "end_offset" : 11,
      "type" : "word",
      "position" : 28
    },
    {
      "token" : "р",
      "start_offset" : 10,
      "end_offset" : 11,
      "type" : "word",
      "position" : 29
    },
    {
      "token" : "а",
      "start_offset" : 12,
      "end_offset" : 13,
      "type" : "word",
      "position" : 30
    },
    {
      "token" : "ар",
      "start_offset" : 12,
      "end_offset" : 14,
      "type" : "word",
      "position" : 31
    },
    {
      "token" : "арт",
      "start_offset" : 12,
      "end_offset" : 15,
      "type" : "word",
      "position" : 32
    },
    {
      "token" : "арту",
      "start_offset" : 12,
      "end_offset" : 16,
      "type" : "word",
      "position" : 33
    },
    {
      "token" : "артур",
      "start_offset" : 12,
      "end_offset" : 17,
      "type" : "word",
      "position" : 34
    },
    {
      "token" : "р",
      "start_offset" : 13,
      "end_offset" : 14,
      "type" : "word",
      "position" : 35
    },
    {
      "token" : "рт",
      "start_offset" : 13,
      "end_offset" : 15,
      "type" : "word",
      "position" : 36
    },
    {
      "token" : "рту",
      "start_offset" : 13,
      "end_offset" : 16,
      "type" : "word",
      "position" : 37
    },
    {
      "token" : "ртур",
      "start_offset" : 13,
      "end_offset" : 17,
      "type" : "word",
      "position" : 38
    },
    {
      "token" : "т",
      "start_offset" : 14,
      "end_offset" : 15,
      "type" : "word",
      "position" : 39
    },
    {
      "token" : "ту",
      "start_offset" : 14,
      "end_offset" : 16,
      "type" : "word",
      "position" : 40
    },
    {
      "token" : "тур",
      "start_offset" : 14,
      "end_offset" : 17,
      "type" : "word",
      "position" : 41
    },
    {
      "token" : "у",
      "start_offset" : 15,
      "end_offset" : 16,
      "type" : "word",
      "position" : 42
    },
    {
      "token" : "ур",
      "start_offset" : 15,
      "end_offset" : 17,
      "type" : "word",
      "position" : 43
    },
    {
      "token" : "р",
      "start_offset" : 16,
      "end_offset" : 17,
      "type" : "word",
      "position" : 44
    }
  ]
}

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

Моя вина, ошибся с описанием (

Текущая конфигурация
{
  "codec": "best_compression",
  "analysis": {
    "analyzer": {
      "partial_analyzer": {
        "filter": [
          "lowercase"
        ],
        "type": "custom",
        "tokenizer": "partial_tokenizer"
      }
    },
    "tokenizer": {
      "partial_tokenizer": {
        "token_chars": [
          "letter",
          "digit"
        ],
        "type": "edge_ngram",
        "min_gram": "1",
        "max_gram": "10"
      }
    }
  }
}

Поведение сохраняется. Видимо, что-то не так с поисковым запросом. Я не очень понимаю что имеется в виду и где я использую анализатор (

При поиске по n-gram-ам обычно n-gram-ы только применяются к индексированию. Поиск осуществаялется по полной строке. В противном случае у вас будет доминровать префикс. То есть в качестве search_analyzer можно применить такойже анализатор только с keyword токенизатором.

Ваши результаты получены с dfs_query_then_fetch или без?

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

/_search?typed_keys=true&ignore_unavailable=false&expand_wildcards=open&allow_no_indices=true&search_type=query_then_fetch&batched_reduce_size=512

Я так понял в маппинг надо добавить ещё search_analyzer

Как то так?
  "names": {
  "properties": {
    "entityCode": {
      "type": "keyword"
    },
    "localizedName": {
      "type": "nested",
      "properties": {
        "lang": {
          "type": "keyword"
        },
        "name": {
          "type": "text",
          "fields": {
            "sort": {
              "type": "keyword"
            }
          },
          "analyzer": "partial_analyzer",
          "search_analyzer": "keyword"
        }
      }
    }
  }
}

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

Вот этот кусок search_type=query_then_fetch должен быть search_type=dfs_query_then_fetch в java это достигается так:

request.setSearchType(SearchType.DFS_QUERY_THEN_FETCH)

Проблема с "search_analyzer": "keyword" в том, что это не будет работать с пробелами пунктуацией и верхним регистром. Надо чтобы search analyzer делал все тоже, что и index кроме n-gram.

1 Like

А можно/как создать тимплейт сразу с двумя аналайзерами? Не нашёл никаких подтверждений/ примеров что их вообще можно много и не нашёл документации как бы это делать

Тимлейт

/_template/template_name?master_timeout=30s
{
"index_patterns": [
"название индекса"
],
"order": 0,
"version": 1,
"settings": {
"analysis": {
"analyzer": {
"partial_analyzer": {
"filter": [
"lowercase"
],
"type": "custom",
"tokenizer": "partial_tokenizer"
}
},
"tokenizer": {
"partial_tokenizer": {
"token_chars": [
"letter",
"digit"
],
"min_gram": "1",
"type": "edge_ngram",
"max_gram": "10"
}
}
},

        "codec": "best_compression"
    },
    "mappings": {"маппинг},
    "aliases": {}
}

Да сколько хотите:

"settings": {
  "analysis": {
    "analyzer": {
        "my_first_analyzer": { ..... },
        "my_second_analyzer": { ..... },
        "my_third_analyzer": { ..... }
    }
  }
}
1 Like

К сожалению, не вижу никаких изменений после этого(
Возможно всё ещё делаю что то не то и не так
Урл по которому зову

http://localhost:9200/index_name/_search?typed_keys=true&ignore_unavailable=false&expand_wildcards=open&allow_no_indices=true&search_type=dfs_query_then_fetch&batched_reduce_size=512
Запрос
{
"from": 0,
"size": 50,
"query": {
    "bool": {
        "must": [
            {
                "nested": {
                    "query": {
                        "bool": {
                            "must": [
                                {
                                    "match": {
                                        "names.localizedName.name": {
                                            "query": "арт",
                                            "operator": "AND",
                                            "prefix_length": 0,
                                            "max_expansions": 50,
                                            "fuzzy_transpositions": true,
                                            "lenient": false,
                                            "zero_terms_query": "NONE",
                                            "auto_generate_synonyms_phrase_query": true,
                                            "boost": 1
                                        }
                                    }
                                }
                            ],
                            "adjust_pure_negative": true,
                            "boost": 1
                        }
                    },
                    "path": "names.localizedName",
                    "ignore_unmapped": false,
                    "score_mode": "avg",
                    "boost": 1
                }
            }
        ],
        "adjust_pure_negative": true,
        "boost": 1
    }
}
}
Маппинг запрашиваемого поля
"names": {
      "properties": {
        "entityCode": {
          "type": "keyword"
        },
        "localizedName": {
          "type": "nested",
          "properties": {
            "lang": {
              "type": "keyword"
            },
            "name": {
              "type": "text",
              "fields": {
                "sort": {
                  "type": "keyword"
                }
              },
              "analyzer": "partial_analyzer",
              "search_analyzer": "search_keyword_analyzer"
            }
          }
        }
      }
    }
Аналайзеры
{
  "codec": "best_compression",
  "analysis": {
    "analyzer": {
      "partial_analyzer": {
        "filter": [
          "lowercase"
        ],
        "type": "custom",
        "tokenizer": "partial_tokenizer"
      },
      "search_keyword_analyzer": {
        "filter": [
          "lowercase"
        ],
        "type": "custom",
        "tokenizer": "partial_keyword_tokenizer"
      }
    },
    "tokenizer": {
      "partial_tokenizer": {
        "token_chars": [
          "letter",
          "digit"
        ],
        "type": "edge_ngram",
        "min_gram": "1",
        "max_gram": "10"
      },
      "partial_keyword_tokenizer": {
        "token_chars": [
          "letter",
          "digit"
        ],
        "type": "keyword"
      }
    }
  }
}

записей в индексе 100к
версия elasticsearch:6.8.1, бежит в докер контейнере

Начнем с того, что токенизатор keyword не работает с token_chars. Этот параметер не будет иметь никакого эфекта.

Чтобы разобраться, почему релевантность все-равно не такая как нужно, надо запустить запрос с флагом explain=true. Это нам даст подробную информацию о том, что происходит.

Я поигрался с вашими данными и у меня результат такой:

    "_score" : 0.066942096,
        "name" : "АРТ-"
    "_score" : 0.066942096,
        "name" : "АРТ+"
    "_score" : 0.058082115,
        "name" : "АРТУР"
    "_score" : 0.058082115,
        "name" : "ТК Арт"
    "_score" : 0.058082115,
        "name" : "АРТЕМ"
    "_score" : 0.058082115,
        "name" : "Артур"
    "_score" : 0.054477017,
        "name" : "АРТ И КО"
    "_score" : 0.053372756,
        "name" : "Рябов Артур Артур"
    "_score" : 0.05179782,
        "name" : "Клюкин Артем Артем"

То есть все как ожидается - короткие имена в начале, длинные потом. Если у вас порядок другой - то это возможно из-за IDF некоторых префиксов. Из вывода explain должно быть понятно. Вот скрипт, который я использовал:

DELETE test

PUT test
{
  "settings": {
    "codec": "best_compression",
    "analysis": {
      "analyzer": {
        "partial_analyzer": {
          "filter": [
            "lowercase"
          ],
          "type": "custom",
          "tokenizer": "partial_tokenizer"
        },
        "search_keyword_analyzer": {
          "filter": [
            "lowercase"
          ],
          "type": "custom",
          "tokenizer": "partial_keyword_tokenizer"
        }
      },
      "tokenizer": {
        "partial_tokenizer": {
          "token_chars": [
            "letter",
            "digit"
          ],
          "type": "edge_ngram",
          "min_gram": "1",
          "max_gram": "10"
        },
        "partial_keyword_tokenizer": {
          "token_chars": [
            "letter",
            "digit"
          ],
          "type": "keyword"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "entityCode": {
        "type": "keyword"
      },
      "localizedName": {
        "type": "nested",
        "properties": {
          "lang": {
            "type": "keyword"
          },
          "name": {
            "type": "text",
            "fields": {
              "sort": {
                "type": "keyword"
              }
            },
            "analyzer": "partial_analyzer",
            "search_analyzer": "search_keyword_analyzer"
          }
        }
      }
    }
  }
}


POST test/_doc/1
{
  "localizedName": {
    "name": "АРТ-"
  }
}


POST test/_doc/2
{
  "localizedName": {
    "name": "АРТ+"
  }
}


POST test/_doc/3
{
  "localizedName": {
    "name": "АРТУР"
  }
}

POST test/_doc/4
{
  "localizedName": {
    "name": "Рябов Артур Артур"
  }
}

POST test/_doc/5
{
  "localizedName": {
    "name": "Клюкин Артем Артем"
  }
}

POST test/_doc/6
{
  "localizedName": {
    "name": "ТК Арт"
  }
}


POST test/_doc/7
{
  "localizedName": {
    "name": "АРТЕМ"
  }
}


POST test/_doc/8
{
  "localizedName": {
    "name": "Артур"
  }
}

POST test/_doc/9
{
  "localizedName": {
    "name": "АРТ И КО"
  }
}


POST test/_analyze
{
  "text": ["Рябов++"],
  "analyzer": "search_keyword_analyzer"
}

POST test/_search
{
  "from": 0,
  "size": 50,
  "query": {
    "bool": {
      "must": [
        {
          "nested": {
            "query": {
              "bool": {
                "must": [
                  {
                    "match": {
                      "localizedName.name": {
                        "query": "арт",
                        "operator": "AND",
                        "prefix_length": 0,
                        "max_expansions": 50,
                        "fuzzy_transpositions": true,
                        "lenient": false,
                        "zero_terms_query": "NONE",
                        "auto_generate_synonyms_phrase_query": true,
                        "boost": 1
                      }
                    }
                  }
                ],
                "adjust_pure_negative": true,
                "boost": 1
              }
            },
            "path": "localizedName",
            "ignore_unmapped": false,
            "score_mode": "avg",
            "boost": 1
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1
    }
  }
}

Создал новый максимально лёгкий индекс с минимум полей

Запрос
{
"explain": true,
"from": 0,
"size": 25,
"query": {
	"bool": {
		"must": [
			{
				"nested": {
					"query": {
						"bool": {
							"must": [
								{
									"match": {
										"names.localizedName.name": {
											"query": "арт",
											"operator": "AND",
											"prefix_length": 0,
											"max_expansions": 50,
											"fuzzy_transpositions": true,
											"lenient": false,
											"zero_terms_query": "NONE",
											"auto_generate_synonyms_phrase_query": true,
											"boost": 1.0
										}
									}
								}
							],
							"adjust_pure_negative": true,
							"boost": 1.0
						}
					},
					"path": "names.localizedName",
					"ignore_unmapped": false,
					"score_mode": "avg",
					"boost": 1.0
				}
			}
		],
		"adjust_pure_negative": true,
		"boost": 1.0
	}
}

}

explain

Все работает точно, как вы запросили. Во всех ответах есть совпадение токинов. Ответы отсортированы по размеру анализированой стротки (tfNorm).

Я думаю проблема не в том, как что elastic неправильно обрабатывает ваш запрос, а в том что то что вы ожидаете от вашего запроса не соответсвует действительности.

Может, наченем с начала? Вы не могли бы объяснить, что в вашем понимении "правильная релевантность результатов". И тогда, на основе этой информации, мы попробуем создать маппинг и запрос, который бы лучше соответсвовал вашему пониманию?

Вполне возможно я и не говорю что конкретно скоринг непременно побеждает тут всё, он просто даёт самый точный результат, а хочется точнее и не в кучу запросов а в один)
У меня есть форма с большим количеством разных запросов, в том числе среди них есть запрос по имени.
Мне хочется что бы поведение при условии поиска по имени было подобно автокомплиту.
Вводим букву а получаем

  1. Всё что совпадает те А
  2. Всё что начинается Арём Артур Афанасий
  3. Поиск по вхождению

Есть ещё нюанс, заключающийся в виде большого количества разных спец символов и использований разных языков включая китайский

Если бы это был поиск на бд это решалось бы сортировкой типо
ORDER BY name ILIKE concat(#{подстрока}, '%') DESC, name

В текущей конфигурации например на запрос арт первый результат ТК Арт и это не очень подходит (

Это происходит из-за вот этого куска:

          "token_chars": [
            "letter",
            "digit"
          ],

он ввод разбивает по всем символам, которые не буквы и не цифры

То есть ТК Арт превращается в следующее

POST test/_analyze
{
  "text": ["ТК Арт"],
  "analyzer": "partial_analyzer"
}

{
  "tokens" : [
    {
      "token" : "т",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "тк",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "а",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "ар",
      "start_offset" : 3,
      "end_offset" : 5,
      "type" : "word",
      "position" : 3
    },
    {
      "token" : "арт",
      "start_offset" : 3,
      "end_offset" : 6,
      "type" : "word",
      "position" : 4
    }
  ]
}

Если вы уберете это разбиение, то получиться вот что:

{
  "tokens" : [
    {
      "token" : "т",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "тк",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "тк ",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "тк а",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "word",
      "position" : 3
    },
    {
      "token" : "тк ар",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "word",
      "position" : 4
    },
    {
      "token" : "тк арт",
      "start_offset" : 0,
      "end_offset" : 6,
      "type" : "word",
      "position" : 5
    }
  ]
}

и это совпадение вообще не появиться в результатах

1 Like

Не уверен что это вообще реально сделать одним запросом(
Проблема в том, что хочется что бы ТК Арт тоже был, но он был не в топе списка, а после всего что начинается с буквы А

А в чем проблема? Просто ищем и так и так и бустим точное совпадение.

DELETE test


PUT test
{
  "settings": {
    "analysis": {
      "tokenizer": {
        "word_tokenizer": {
          "type": "char_group",
          "tokenize_on_chars": [
            "whitespace",
            "punctuation"
          ]
        }
      }, 
      "filter": {
        "edge_ngram_1_10": {
          "type": "edge_ngram",
          "min_gram": "1",
          "max_gram": "10"
        }
      }, 
      "analyzer": {
        "prefix_index_analyzer": {
          "type": "custom",
          "tokenizer": "keyword",
          "filter": [
            "lowercase",
            "edge_ngram_1_10"
          ]
        },
        "prefix_search_analyzer": {
          "type": "custom",
          "tokenizer": "keyword",
          "filter": [
            "lowercase"
          ]
        },
        "word_prefix_index_analyzer": {
          "type": "custom",
          "tokenizer": "word_tokenizer",
          "filter": [
            "lowercase",
            "edge_ngram_1_10"
          ]
        },
        "word_prefix_search_analyzer": {
          "type": "custom",
          "tokenizer": "word_tokenizer",
          "filter": [
            "lowercase"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "entityCode": {
        "type": "keyword"
      },
      "localizedName": {
        "type": "nested",
        "properties": {
          "lang": {
            "type": "keyword"
          },
          "name": {
            "type": "text",
            "fields": {
              "sort": {
                "type": "keyword"
              },
              "word": {
                "type": "text",
                "analyzer": "word_prefix_index_analyzer",
                "search_analyzer": "word_prefix_search_analyzer"     
              }
            },
            "analyzer": "prefix_index_analyzer",
            "search_analyzer": "prefix_search_analyzer"
          }
        }
      }
    }
  }
}

POST test/_bulk
{ "index" : {  "_id" : "1" } }
{"localizedName": {"name": "АРТ-"} }
{ "index" : {  "_id" : "2" } }
{"localizedName": {"name": "АРТ+"} }
{ "index" : {  "_id" : "3" } }
{"localizedName": {"name": "АРТУР"} }
{ "index" : {  "_id" : "4" } }
{"localizedName": {"name": "Рябов Артур Артур"} }
{ "index" : {  "_id" : "5" } }
{"localizedName": {"name": "Клюкин Артем Артем"} }
{ "index" : {  "_id" : "6" } }
{"localizedName": {"name": "ТК Арт"} }
{ "index" : {  "_id" : "7" } }
{"localizedName": {"name": "АРТЕМ"} }
{ "index" : {  "_id" : "8" } }
{"localizedName": {"name": "Артур"} }
{ "index" : {  "_id" : "9" } }
{"localizedName": {"name": "АРТ И КО"} }


POST test/_analyze
{
  "text": ["ТК Арт"],
  "analyzer": "partial_analyzer"
}



POST test/_search?search_type=dfs_query_then_fetch
{
  "from": 0,
  "size": 50,
  "query": {
    "bool": {
      "must": [
        {
          "nested": {
            "query": {
              "bool": {
                "should": [
                  {
                    "match": {
                      "localizedName.name": {
                        "query": "арт",
                        "boost": 10
                      }
                    }
                  },
                   {
                    "match": {
                      "localizedName.name.word": {
                        "query": "арт"
                      }
                    }
                  }
                ]
              }
            },
            "path": "localizedName",
            "ignore_unmapped": false,
            "score_mode": "avg",
            "boost": 1
          }
        }
      ]
    }
  }
}

1 Like

Воо спасибо! Погонял запросы и выглядит прям хорошо