Многополюсный, многословный, совпадение без query_string

Я хотел бы иметь возможность сопоставить многопользовательский поиск с несколькими полями, где каждое искомое слово содержится в любом для полей, любой комбинации. Уловкой я бы хотел избежать использования query_string.

curl -X POST "http://localhost:9200/index/document/1" -d '{"id":1,"firstname":"john","middlename":"clark","lastname":"smith"}'
curl -X POST "http://localhost:9200/index/document/2" -d '{"id":2,"firstname":"john","middlename":"paladini","lastname":"miranda"}'

Я бы хотел, чтобы поиск "John Smith" соответствовал только документу 1. Следующий запрос делает то, что мне нужно, но я бы предпочел избежать использования query_string в случае, если пользователь передает "ИЛИ", "И" и любой другой расширенные параметры.

curl -X GET 'http://localhost:9200/index/_search?per_page=10&pretty' -d '{
  "query": {
    "query_string": {
      "query": "john smith",
      "default_operator": "AND",
      "fields": [
        "firstname",
        "lastname",
        "middlename"
      ]
    }
  }
}'

Ответ 1

Вам нужен запрос с несколькими совпадениями, но он работает не совсем так, как вы хотели бы.

Сравните вывод проверки для multi_match и query_string.

multi_match (с оператором and) гарантирует, что ВСЕ термины существуют хотя бы в одном поле:

curl -XGET 'http://127.0.0.1:9200/_validate/query?pretty=1&explain=true'  -d '
{
   "multi_match" : {
      "operator" : "and",
      "fields" : [
         "firstname",
         "lastname"
      ],
      "query" : "john smith"
   }
}
'

# {
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 1,
#       "total" : 1
#    },
#    "explanations" : [
#       {
#          "index" : "test",
#          "explanation" : "((+lastname:john +lastname:smith) | (+firstname:john +firstname:smith))",
#          "valid" : true
#       }
#    ],
#    "valid" : true
# }

Хотя query_string (с default_operator AND) проверит, что КАЖДЫЙ термин существует хотя бы в одном поле:

curl -XGET 'http://127.0.0.1:9200/_validate/query?pretty=1&explain=true'  -d '
{
   "query_string" : {
      "fields" : [
         "firstname",
         "lastname"
      ],
      "query" : "john smith",
      "default_operator" : "AND"
   }
}
'

# {
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 1,
#       "total" : 1
#    },
#    "explanations" : [
#       {
#          "index" : "test",
#          "explanation" : "+(firstname:john | lastname:john) +(firstname:smith | lastname:smith)",
#          "valid" : true
#       }
#    ],
#    "valid" : true
# }

Итак, у вас есть несколько вариантов для достижения того, что вам нужно:

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

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

  3. Используйте index_name в вашем отображении для полей имени, чтобы индексировать их данные в одно поле, которое затем можно использовать для поиска. (например, ваше собственное поле all):

Следующим образом:

curl -XPUT 'http://127.0.0.1:9200/test/?pretty=1'  -d '
{
   "mappings" : {
      "test" : {
         "properties" : {
            "firstname" : {
               "index_name" : "name",
               "type" : "string"
            },
            "lastname" : {
               "index_name" : "name",
               "type" : "string"
            }
         }
      }
   }
}
'

curl -XPOST 'http://127.0.0.1:9200/test/test?pretty=1'  -d '
{
   "firstname" : "john",
   "lastname" : "smith"
}
'

curl -XGET 'http://127.0.0.1:9200/test/test/_search?pretty=1'  -d '
{
   "query" : {
      "match" : {
         "name" : {
            "operator" : "and",
            "query" : "john smith"
         }
      }
   }
}
'

# {
#    "hits" : {
#       "hits" : [
#          {
#             "_source" : {
#                "firstname" : "john",
#                "lastname" : "smith"
#             },
#             "_score" : 0.2712221,
#             "_index" : "test",
#             "_id" : "VJFU_RWbRNaeHF9wNM8fRA",
#             "_type" : "test"
#          }
#       ],
#       "max_score" : 0.2712221,
#       "total" : 1
#    },
#    "timed_out" : false,
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 5,
#       "total" : 5
#    },
#    "took" : 33
# }

Обратите внимание, что firstname и lastname больше не доступны для поиска независимо. Данные для обоих полей были проиндексированы в name.

Вы можете использовать multi-fields с параметром path, чтобы сделать их доступными для поиска как независимо, так и вместе, следующим образом:

curl -XPUT 'http://127.0.0.1:9200/test/?pretty=1'  -d '
{
   "mappings" : {
      "test" : {
         "properties" : {
            "firstname" : {
               "fields" : {
                  "firstname" : {
                     "type" : "string"
                  },
                  "any_name" : {
                     "type" : "string"
                  }
               },
               "path" : "just_name",
               "type" : "multi_field"
            },
            "lastname" : {
               "fields" : {
                  "any_name" : {
                     "type" : "string"
                  },
                  "lastname" : {
                     "type" : "string"
                  }
               },
               "path" : "just_name",
               "type" : "multi_field"
            }
         }
      }
   }
}
'

curl -XPOST 'http://127.0.0.1:9200/test/test?pretty=1'  -d '
{
   "firstname" : "john",
   "lastname" : "smith"
}
'

Поиск в поле any_name работает:

curl -XGET 'http://127.0.0.1:9200/test/test/_search?pretty=1'  -d '
{
   "query" : {
      "match" : {
         "any_name" : {
            "operator" : "and",
            "query" : "john smith"
         }
      }
   }
}
'

# {
#    "hits" : {
#       "hits" : [
#          {
#             "_source" : {
#                "firstname" : "john",
#                "lastname" : "smith"
#             },
#             "_score" : 0.2712221,
#             "_index" : "test",
#             "_id" : "Xf9qqKt0TpCuyLWioNh-iQ",
#             "_type" : "test"
#          }
#       ],
#       "max_score" : 0.2712221,
#       "total" : 1
#    },
#    "timed_out" : false,
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 5,
#       "total" : 5
#    },
#    "took" : 11
# }

Поиск firstname для john AND smith не работает:

curl -XGET 'http://127.0.0.1:9200/test/test/_search?pretty=1'  -d '
{
   "query" : {
      "match" : {
         "firstname" : {
            "operator" : "and",
            "query" : "john smith"
         }
      }
   }
}
'

# {
#    "hits" : {
#       "hits" : [],
#       "max_score" : null,
#       "total" : 0
#    },
#    "timed_out" : false,
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 5,
#       "total" : 5
#    },
#    "took" : 2
# }

Но поиск firstname только john работает правильно:

curl -XGET 'http://127.0.0.1:9200/test/test/_search?pretty=1'  -d '
{
   "query" : {
      "match" : {
         "firstname" : {
            "operator" : "and",
            "query" : "john"
         }
      }
   }
}
'

# {
#    "hits" : {
#       "hits" : [
#          {
#             "_source" : {
#                "firstname" : "john",
#                "lastname" : "smith"
#             },
#             "_score" : 0.30685282,
#             "_index" : "test",
#             "_id" : "Xf9qqKt0TpCuyLWioNh-iQ",
#             "_type" : "test"
#          }
#       ],
#       "max_score" : 0.30685282,
#       "total" : 1
#    },
#    "timed_out" : false,
#    "_shards" : {
#       "failed" : 0,
#       "successful" : 5,
#       "total" : 5
#    },
#    "took" : 3
# }

Ответ 2

Я бы предпочел избежать использования query_string в случае, если пользователь передает "ИЛИ", "И" и любой другой расширенный параметр.

По моему опыту, экранирование специальных символов с помощью обратной косой черты - простое и эффективное решение. Список можно найти в документации http://lucene.apache.org/core/4_5_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package_description, плюс AND/OR/NOT/TO.

Ответ 3

Я думаю, что запрос "match" - это то, что вы ищете:

"Семейство запросов соответствия не проходит процесс" разбора запроса ". Оно не поддерживает префиксы имени полей, подстановочные знаки или другие" продвинутые "функции. По этой причине вероятность его сбоя очень мала/не существует, и он обеспечивает отличное поведение, когда дело доходит до простого анализа и запуска этого текста в качестве поведения запроса (обычно это то, что делает текстовое окно поиска)"

http://www.elasticsearch.org/guide/reference/query-dsl/match-query.html

Ответ 4

В настоящее время вы можете использовать cross_fields типа в multi_match

GET /_validate/query?explain
{
    "query": {
        "multi_match": {
            "query":       "peter smith",
            "type":        "cross_fields", 
            "operator":    "and",
            "fields":      [ "firstname", "lastname", "middlename" ]
        }
    }
}

В кросс-полях используется ориентированный на срок подход. Он рассматривает все поля как одно большое поле и ищет каждый термин в любом поле.

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

Чтобы тип запроса cross_fields работал оптимально, все поля должны иметь один и тот же анализатор. Поля, которые разделяют анализатор, сгруппированы как смешанные поля.

Если вы включите поля с другой цепочкой анализа, они будут добавлены в запрос так же, как и для best_fields. Например, если мы добавили поле заголовка к предыдущему запросу (при условии, что он использует другой анализатор), объяснение будет следующим:

(+title: peter +title: кузнец) (+blended ("peter", поля: [first_name, last_name]) +blended ("кузнец", поля: [first_name, last_name]))