Link Search Menu Expand Document Documentation Menu

多字段匹配查询

多字段匹配操作的功能类似于 匹配 操作。您可以使用 multi_match 查询来搜索多个字段。

^ 符号“提升”特定字段。提升是乘数,用于使某个字段中的匹配比其他字段中的匹配权重更高。在以下示例中,标题字段中“wind”的匹配对 _score 的影响是情节字段中匹配的四倍:

GET _search
{
  "query": {
    "multi_match": {
      "query": "wind",
      "fields": ["title^4", "plot"]
    }
  }
}

结果是,像《起风了》和《乱世佳人》这样的电影排在搜索结果的前面,而像《龙卷风》这样情节摘要中可能包含“wind”的电影则排在后面。

您可以在字段名中使用通配符。例如,以下查询将搜索 speaker 字段以及所有以 play_ 开头的字段,例如 play_nameplay_title

GET _search
{
  "query": {
    "multi_match": {
      "query": "hamlet",
      "fields": ["speaker", "play_*"]
    }
  }
}

如果您不提供 fields 参数,multi_match 查询将搜索 index.query.Default_field 设置中指定的字段,该设置默认为 *。默认行为是提取映射中所有符合 词元级查询 条件的字段,过滤元数据字段,并结合所有提取的字段来构建查询。

查询中子句的最大数量在 indices.query.bool.max_clause_count 设置中定义,该设置默认为 1,024。

多字段匹配查询类型

OpenSearch 支持以下多字段匹配查询类型,它们在查询内部执行方式上有所不同:

  • best_fields (默认): 返回匹配任何字段的文档。使用最佳匹配字段的 _score
  • most_fields: 返回匹配任何字段的文档。使用每个匹配字段的组合分数。
  • cross_fields: 将所有字段视为一个字段。使用相同的 analyzer 处理字段,并匹配任何字段中的单词。
  • phrase: 对每个字段运行 match_phrase 查询。使用最佳匹配字段的 _score
  • phrase_prefix: 对每个字段运行 match_phrase_prefix 查询。使用最佳匹配字段的 _score
  • bool_prefix: 对每个字段运行 match_bool_prefix 查询。使用每个匹配字段的组合分数。

最佳字段

如果您正在搜索表示某个概念的两个词,您会希望这两个词相邻的结果得分更高。

例如,考虑一个包含以下科学文章的索引:

PUT /articles/_doc/1
{
  "title": "Aurora borealis",
  "description": "Northern lights, or aurora borealis, explained"
}

PUT /articles/_doc/2
{
  "title": "Sun deprivation in the Northern countries",
  "description": "Using fluorescent lights for therapy"
}

您可以搜索标题或描述中包含 northern lights 的文章:

GET articles/_search
{
  "query": {
    "multi_match" : {
      "query": "northern lights",
      "type": "best_fields",
      "fields": [ "title", "description" ],
      "tie_breaker": 0.3
    }
  }
}

前面的查询将作为以下 dis_max 查询执行,每个字段都带有一个 match 查询:

GET /articles/_search
{
  "query": {
    "dis_max": {
      "queries": [
        { "match": { "title": "northern lights" }},
        { "match": { "description": "northern lights" }}
      ],
      "tie_breaker": 0.3
    }
  }
}

结果包含两个文档,但文档 1 的得分更高,因为两个词都在 description 字段中。

{
  "took": 30,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 0.84407747,
    "hits": [
      {
        "_index": "articles",
        "_id": "1",
        "_score": 0.84407747,
        "_source": {
          "title": "Aurora borealis",
          "description": "Northern lights, or aurora borealis, explained"
        }
      },
      {
        "_index": "articles",
        "_id": "2",
        "_score": 0.6322521,
        "_source": {
          "title": "Sun deprivation in the Northern countries",
          "description": "Using fluorescent lights for therapy"
        }
      }
    ]
  }
}

best_fields 查询使用最佳匹配字段的分数。如果您指定了 tie_breaker,则分数将使用以下算法计算:

取最佳匹配字段的分数,并加上(tie_breaker * _score)以获得所有其他匹配字段的分数。

大多数字段

对包含以不同方式分析的相同文本的多个字段使用 most_fields 查询。例如,原始字段可能包含使用 standard 分析器分析的文本,而另一个字段可能包含使用执行词干提取的 english 分析器分析的相同文本。

PUT /articles
{
  "mappings": {
    "properties": {
      "title": { 
        "type": "text",
        "fields": {
          "english": { 
            "type": "text",
            "analyzer": "english"
          }
        }
      }
    }
  }
}

考虑以下两个在 articles 索引中被索引的文档:

PUT /articles/_doc/1
{
  "title": "Buttered toasts"
}

PUT /articles/_doc/2
{
  "title": "Buttering a toast"
}

standard 分析器将标题 Buttered toast 分析为 [buttered, toasts],将标题 Buttering a toast 分析为 [buttering, a, toast]。另一方面,english 分析器由于词干提取,为两个标题生成了相同的词元列表 [butter, toast]。

您可以使用 most_fields 查询来返回尽可能多的文档:

GET /articles/_search
{
  "query": {
    "multi_match": {
      "query": "buttered toast",
      "fields": [ 
        "title",
        "title.english"
      ],
      "type": "most_fields" 
    }
  }
}

前面的查询将作为以下布尔查询执行:

GET articles/_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "title": "buttered toasts" }},
        { "match": { "title.english": "buttered toasts" }}
      ]
    }
  }
}

为了计算相关性得分,将文档所有 match 子句的得分相加,然后将结果除以 match 子句的数量。

包含 title.english 字段会检索与词干化词元匹配的第二个文档。

{
  "took": 9,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 1.4418206,
    "hits": [
      {
        "_index": "articles",
        "_id": "1",
        "_score": 1.4418206,
        "_source": {
          "title": "Buttered toasts"
        }
      },
      {
        "_index": "articles",
        "_id": "2",
        "_score": 0.09304003,
        "_source": {
          "title": "Buttering a toast"
        }
      }
    ]
  }
}

由于第一个文档的 titletitle.english 字段都匹配,因此它具有更高的相关性得分。

操作符和最小匹配数

best_fieldsmost_fields 查询在字段基础上生成匹配查询(每个字段一个)。因此,minimum_should_matchoperator 参数应用于每个字段,这通常不是所需行为。

例如,考虑一个包含以下文档的 customers 索引:

PUT customers/_doc/1 
{
  "first_name": "John",
  "last_name": "Doe"
}

PUT customers/_doc/2 
{
  "first_name": "Jane",
  "last_name": "Doe"
}

如果您在 customers 索引中搜索 John Doe,您可能会构建以下查询:

GET customers/_validate/query?explain
{
  "query": {
    "multi_match" : {
      "query": "John Doe",
      "type": "best_fields",
      "fields": [ "first_name", "last_name" ],
      "operator": "and" 
    }
  }
}

此查询中 and 操作符的目的是查找同时匹配 JohnDoe 的文档。然而,该查询不返回任何结果。您可以通过运行 Validate API 来了解查询是如何执行的。

GET customers/_validate/query?explain
{
  "query": {
    "multi_match" : {
      "query":      "John Doe",
      "type":       "best_fields",
      "fields":     [ "first_name", "last_name" ],
      "operator":   "and" 
    }
  }
}

从响应中可以看出,查询试图将 JohnDoe 同时匹配到 first_namelast_name 字段。

{
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "valid": true,
  "explanations": [
    {
      "index": "customers",
      "valid": true,
      "explanation": "((+first_name:john +first_name:doe) | (+last_name:john +last_name:doe))"
    }
  ]
}

因为两个字段都不包含这两个词,所以没有返回任何结果。

更好的跨字段搜索替代方案是使用 cross_fields 查询。与以字段为中心的 best_fieldsmost_fields 查询不同,cross_fields 查询是以词条为中心的。

交叉字段(Cross fields)

使用 cross_fields 查询在多个字段中搜索数据。例如,如果一个索引包含客户数据,客户的名字和姓氏存储在不同的字段中。然而,当您搜索 John Doe 时,您希望接收到 Johnfirst_name 字段中而 Doelast_name 字段中的文档。

most_fields 查询在这种情况下不起作用,原因如下:

  • operatorminimum_should_match 参数是基于字段应用的,而不是基于词条应用的。
  • first_namelast_name 字段中的词条频率可能导致意外结果。例如,如果某人的名字恰好是 Doe,那么包含此名字的文档将被认为是更好的匹配,因为此名字不会出现在其他任何文档中。

cross_fields 查询将查询字符串分析成独立的词条,然后像在一个字段中一样,在任何字段中搜索这些词条。

以下是针对 John Doecross_fields 查询:

GET /customers/_search
{
  "query": {
    "multi_match" : {
      "query": "John Doe",
      "type": "cross_fields",
      "fields": [ "first_name", "last_name" ],
      "operator": "and"
    }
  }
}

响应中包含唯一一个同时包含 JohnDoe 的文档。

{
  "took": 19,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 0.8754687,
    "hits": [
      {
        "_index": "customers",
        "_id": "1",
        "_score": 0.8754687,
        "_source": {
          "first_name": "John",
          "last_name": "Doe"
        }
      }
    ]
  }
}

您可以使用 Validate API 操作来深入了解上述查询是如何执行的。

GET /customers/_validate/query?explain
{
  "query": {
    "multi_match" : {
      "query": "John Doe",
      "type": "cross_fields",
      "fields": [ "first_name", "last_name" ],
      "operator": "and"
    }
  }
}

从响应中可以看出,查询正在至少一个字段中搜索所有词条。

{
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "valid": true,
  "explanations": [
    {
      "index": "customers",
      "valid": true,
      "explanation": "+blended(terms:[last_name:john, first_name:john]) +blended(terms:[last_name:doe, first_name:doe])"
    }
  ]
}

因此,融合所有字段的词条频率通过修正差异解决了词条频率不同的问题。

cross_fields 查询通常只在 boost 为 1 的短字符串字段上才有用。在其他情况下,由于提升(boosts)、词条频率和长度归一化对评分的贡献方式,评分无法产生有意义的词条统计混合效果。

fuzziness 参数不支持 cross_fields 查询。

分析

cross_fields 查询仅作为词条中心查询作用于使用相同分析器的字段。使用相同分析器的字段被分组,这些组再通过布尔查询组合起来。

例如,考虑一个索引,其中 first_namelast_name 字段使用默认的 standard 分析器进行分析,而它们的 .edge 子字段则使用 edge n-gram 分析器进行分析。

响应
PUT customers
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "my_tokenizer"
        }
      },
      "tokenizer": {
        "my_tokenizer": {
          "type": "edge_ngram",
          "min_gram": 2,
          "max_gram": 10
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "first_name": { 
        "type": "text",
        "fields": {
          "edge": { 
            "type": "text",
            "analyzer": "my_analyzer"
          }
        }
      },
      "last_name": { 
        "type": "text",
        "fields": {
          "edge": { 
            "type": "text",
            "analyzer": "my_analyzer"
          }
        }
      }
    }
  }
}

您在 customers 索引中索引一个文档。

PUT /customers/_doc/1
{
  "first": "John",
  "last": "Doe"
}

您可以使用 cross_fields 查询来跨字段搜索 John Doe

GET /customers/_search
{
  "query": {
    "multi_match" : {
      "query": "John",
      "type": "cross_fields",
      "fields": [
        "first_name", "first_name.edge",
        "last_name",  "last_name.edge"
      ]
    }
  }
}

要查看查询如何执行,可以运行 Validate API。

GET /customers/_validate/query?explain
{
  "query": {
    "multi_match" : {
      "query": "John",
      "type": "cross_fields",
      "fields": [
        "first_name", "first_name.edge",
        "last_name",  "last_name.edge"
      ]
    }
  }
}

响应显示 last_namefirst_name 字段被分组并视为单个字段。同样,last_name.edgefirst_name.edge 字段也被分组并视为单个字段。

{
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "valid": true,
  "explanations": [
    {
      "index": "customers",
      "valid": true,
      "explanation": "(blended(terms:[last_name:john, first_name:john]) | (blended(terms:[last_name.edge:Jo, first_name.edge:Jo]) blended(terms:[last_name.edge:Joh, first_name.edge:Joh]) blended(terms:[last_name.edge:John, first_name.edge:John])))"
    }
  ]
}

在有多个字段组(如上述示例)的情况下使用 operatorminimum_should_match 参数可能会导致上一节中描述的问题。为避免此问题,您可以将之前的查询重写为两个 cross_fields 子查询,并用布尔查询组合,然后将 minimum_should_match 应用于其中一个子查询。

GET /customers/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "multi_match": {
            "query": "John Doe",
            "type": "cross_fields",
            "fields": [
              "first_name",
              "last_name"
            ],
            "minimum_should_match": "1"
          }
        },
        {
          "multi_match": {
            "query": "John Doe",
            "type": "cross_fields",
            "fields": [
              "first_name.edge",
              "last_name.edge"
            ]
          }
        }
      ]
    }
  }
}

要为所有字段创建一个组,请在查询中指定一个分析器。

GET customers/_search
{
  "query": {
   "multi_match" : {
      "query": "John Doe",
      "type": "cross_fields",
      "analyzer": "standard", 
      "fields": [ "first_name", "last_name", "*.edge" ]
    }
  }
}

对前一个查询运行 Validate API 会显示该查询是如何执行的。

{
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "valid": true,
  "explanations": [
    {
      "index": "customers",
      "valid": true,
      "explanation": "blended(terms:[last_name.edge:john, last_name:john, first_name:john, first_name.edge:john]) blended(terms:[last_name.edge:doe, last_name:doe, first_name:doe, first_name.edge:doe])"
    }
  ]
}

短语(Phrase)

phrase 查询的行为类似于 best_fields 查询,但它使用 match_phrase 查询而不是 match 查询。

以下是针对 best_fields 部分中描述的索引的一个 phrase 查询示例:

GET articles/_search
{
  "query": {
    "multi_match" : {
      "query": "northern lights",
      "type": "phrase",
      "fields": [ "title", "description" ]
    }
  }
}

上述查询将作为以下 dis_max 查询执行,每个字段都有一个 match_phrase 查询:

GET articles/_search
{
  "query": {
    "dis_max": {
      "queries": [
        { "match_phrase": { "title": "northern lights" }},
        { "match_phrase": { "description": "northern lights" }}
      ]
    }
  }
}

因为默认情况下,phrase 查询仅在词条按相同顺序出现时才匹配文本,所以结果中只返回文档 1。

响应
{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 0.84407747,
    "hits": [
      {
        "_index": "articles",
        "_id": "1",
        "_score": 0.84407747,
        "_source": {
          "title": "Aurora borealis",
          "description": "Northern lights, or aurora borealis, explained"
        }
      }
    ]
  }
}

您可以使用 slop 参数来允许查询短语中的词条之间存在其他词。例如,以下查询在 flourescenttherapy 之间最多有两个词时也接受文本作为匹配项。

GET articles/_search
{
  "query": {
    "multi_match" : {
      "query": "fluorescent therapy",
      "type": "phrase",
      "fields": [ "title", "description" ],
      "slop": 2
    }
  }
}

响应包含文档 2。

响应
{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 0.7003825,
    "hits": [
      {
        "_index": "articles",
        "_id": "2",
        "_score": 0.7003825,
        "_source": {
          "title": "Sun deprivation in the Northern countries",
          "description": "Using fluorescent lights for therapy"
        }
      }
    ]
  }
}

对于小于 2 的 slop 值,不返回任何文档。

fuzziness 参数不支持 phrase 查询。

短语前缀(Phrase prefix)

phrase_prefix 查询的行为类似于 phrase 查询,但它使用 match_phrase_prefix 查询而不是 match_phrase 查询。

以下是针对 best_fields 部分中描述的索引的一个 phrase_prefix 查询示例:

GET articles/_search
{
  "query": {
    "multi_match" : {
      "query": "northern light",
      "type": "phrase_prefix",
      "fields": [ "title", "description" ]
    }
  }
}

上述查询将作为以下 dis_max 查询执行,每个字段都有一个 match_phrase_prefix 查询:

GET articles/_search
{
  "query": {
    "dis_max": {
      "queries": [
        { "match_phrase_prefix": { "title": "northern light" }},
        { "match_phrase_prefix": { "description": "northern light" }}
      ]
    }
  }
}

您可以使用 slop 参数来允许查询短语中的词条之间存在其他词。

fuzziness 参数不支持 phrase_prefix 查询。

布尔前缀(Boolean prefix)

bool_prefix 查询的文档评分方式类似于 most_fields 查询,但它使用 match_bool_prefix 查询而不是 match 查询。

以下是针对 best_fields 部分中描述的索引的一个 bool_prefix 查询示例:

GET articles/_search
{
  "query": {
    "multi_match" : {
      "query": "li northern",
      "type": "bool_prefix",
      "fields": [ "title", "description" ]
    }
  }
}

上述查询将作为以下 dis_max 查询执行,每个字段都有一个 match_bool_prefix 查询:

GET articles/_search
{
  "query": {
    "dis_max": {
      "queries": [
        { "match_bool_prefix": { "title": "li northern" }},
        { "match_bool_prefix": { "description": "li northern" }}
      ]
    }
  }
}

fuzzinessprefix_lengthmax_expansionsfuzzy_rewritefuzzy_transpositions 参数支持用于构建词条查询的词条,但它们对从最终词条构建的前缀查询没有影响。

参数

该查询接受以下参数。除 query 外,所有参数都是可选的。

参数 数据类型 描述
查询(query) 字符串 用于搜索的查询字符串。必填。
auto_generate_synonyms_phrase_query 布尔型 指定是否为多词条同义词自动创建 match phrase 查询。例如,如果您指定 ba,batting average 为同义词并搜索 ba,OpenSearch 将搜索 ba OR "batting average"(如果此选项为 true)或 ba OR (batting AND average)(如果此选项为 false)。默认值为 true
分析器 字符串 用于分词查询字符串文本的分析器。默认为为 default_field 指定的索引时分析器。如果未为 default_field 指定分析器,则 analyzer 为索引的默认分析器。有关 index.query.default_field 的更多信息,请参阅动态索引级别索引设置
提升 浮点数(Floating-point) 按给定乘数提升子句。在复合查询中,用于衡量子句的权重。值在 [0, 1) 范围内会降低相关性,大于 1 的值会增加相关性。默认值为 1
字段(fields) 字符串数组 要搜索的字段列表。如果您不提供 fields 参数,multi_match 查询将在 index.query.default_field 设置中指定的字段中搜索,该设置默认为 *
模糊度(fuzziness) 字符串 在确定词条是否匹配某个值时,将一个词更改为另一个词所需的字符编辑(插入、删除、替换)数量。例如,winedwind 之间的距离是 1。有效值是非负整数或 AUTO。默认值 AUTO 根据每个词条的长度选择一个值,对于大多数用例来说是一个不错的选择。不支持 phrasephrase_prefixcross_fields 查询。
模糊重写(fuzzy_rewrite) 字符串 确定 OpenSearch 如何重写查询。有效值为 constant_scorescoring_booleanconstant_score_booleantop_terms_Ntop_terms_boost_Ntop_terms_blended_freqs_N。如果 fuzziness 参数不为 0,查询默认使用 top_terms_blended_freqs_${max_expansions}fuzzy_rewrite 方法。默认值为 constant_score
模糊转置(fuzzy_transpositions) 布尔型 fuzzy_transpositions 设置为 true(默认值)会在 fuzziness 选项的插入、删除和替换操作中添加相邻字符的交换。例如,如果 fuzzy_transpositions 为 true,则 windwnid 之间的距离为 1(交换“n”和“i”),如果为 false,则为 2(删除“n”,插入“n”)。如果 fuzzy_transpositions 为 false,则 rewindwnidwind 的距离相同(2),尽管以人为中心来看 wnid 是一个明显的拼写错误。默认值适用于大多数用例。
宽松(lenient) 布尔型 lenient 设置为 true 会忽略查询与文档字段之间的数据类型不匹配。例如,查询字符串 "8.2" 可以匹配 float 类型的字段。默认值为 false
最大扩展(max_expansions) 正整数 查询可以扩展到的最大词条数量。模糊查询会“扩展”到在 fuzziness 中指定距离内的匹配词条数量。然后 OpenSearch 尝试匹配这些词条。默认值为 50
minimum_should_match 正或负整数,正或负百分比,组合 如果查询字符串包含多个搜索词条且您使用 or 运算符,则文档被视为匹配所需的匹配词条数量。例如,如果 minimum_should_match 为 2,wind often rising 不匹配 The Wind Rises.。如果 minimum_should_match1,则匹配。详情请参阅Minimum should match
操作符(operator) 字符串 如果查询字符串包含多个搜索词条,则文档被视为匹配需要所有词条都匹配 (AND) 还是只需要一个词条匹配 (OR)。有效值为
- OR:字符串 to be 被解释为 to OR be
- AND:字符串 to be 被解释为 to AND be
默认值为 OR
prefix_length 非负整数 在模糊性中不考虑的前导字符数。默认值为 0
冗余(slop) 0(默认值)或正整数 控制查询中词条错序但仍被视为匹配的程度。摘自 Lucene 文档:“查询短语中词条之间允许的其他词条数量。例如,要交换两个词条的顺序需要两次移动(第一次移动将词条放在彼此之上),因此为了允许短语的重新排序,冗余值必须至少为二。零值要求精确匹配。” 支持 phrasephrase_prefix 查询类型。
tie_breaker 浮点数(Floating-point) 一个介于 0 和 1.0 之间的因子,用于赋予匹配多个查询子句的文档更高的权重。更多信息请参阅 tie_breaker 参数
类型(type) 字符串 多匹配查询类型。有效值为 best_fieldsmost_fieldscross_fieldsphrasephrase_prefixbool_prefix。默认值为 best_fields
zero_terms_query 字符串 在某些情况下,分析器会从查询字符串中删除所有词条。例如,stop 分析器会从字符串 an but this 中删除所有词条。在这些情况下,zero_terms_query 指定是匹配没有文档 (none) 还是所有文档 (all)。有效值为 noneall。默认值为 none

fuzziness 参数不支持 phrasephrase_prefixcross_fields 查询。

slop 参数仅支持 phrasephrase_prefix 查询。

tie_breaker 参数

每个词条级混合查询都会将文档评分计算为组中任何字段返回的最佳评分。所有混合查询的评分相加,产生最终评分。您可以通过使用 tie_breaker 参数来更改评分的计算方式。tie_breaker 参数接受以下值:

  • 0.0(best_fieldscross_fieldsphrasephrase_prefix 查询的默认值):取组中任何字段返回的单一最佳评分。
  • 1.0(most_fieldsbool_prefix 查询的默认值):将组中所有字段的评分相加。
  • 一个介于 (0, 1) 范围内的浮点值:取最佳匹配字段的单一最佳评分,并为所有其他匹配字段加上 (tie_breaker * _score)。