Elasticsearch系列(12)Query之复合查询

1. 前言

Elasticsearch提供了一个完整的基于JSON的查询DSL(领域特定语言)来定义查询。可以将查询DSL看作查询的AST(抽象语法树),它由两种类型的子句(clauses)组成:
(1)叶查询子句(Leaf query clauses):特定字段中查找特定值,例如匹配(match)、词条(term)或范围(range)查询。这些查询可以单独查询使用。
(2)复合查询子句(Compound query clauses):包装其他叶查询或复合查询,并使用逻辑方式来组合多个查询(如bool或dis_max查询),或用于改变它们的行为(如constant_score查询)。

查询子句的行为取决于它们是在查询上下文中使用还是在筛选上下文中使用。

允许昂贵查询

某些类型的查询通常执行缓慢,这是因为它们的实现方式会影响集群的稳定性,这些查询分类如下:

  • 需要做线性扫描来识别匹配的查询:
    (1)脚本查询(script查询)
  • 具有高前期成本的查询:
    (1)模糊查询(通配符字段除外)
    (2)正则表达式查询(通配符字段除外)
    (3)前缀查询(通配符字段或没有index_prefixes的字段除外)
    (4)通配符查询(通配符字段除外)
    (5)部分范围查询
  • join查询
  • 对于每个文档可能有较高成本的查询:
    (1)脚本分数查询
    (2)过滤查询(percolate查询)

可以通过设置参数allow_expensive_queries为false(默认为true)来阻止此类查询的执行。

2. 查询和过滤器上下文

相关性得分

默认情况下,Elasticsearch根据相关性评分对匹配的搜索结果进行排序,相关性评分衡量每个文档与查询的匹配程度。相关性得分是一个正浮点数,在搜索API的_score元数据字段中返回。_score字段值越高,文档越相关。

查询上下文

在查询(query)上下文中,一个查询子句回答了问题“这个文档与这个查询子句匹配得有多好?”,除了决定文档是否匹配之外,查询子句还会计算相关性分数保存在_score元数据字段中。

过滤器上下文

在过滤器(filter)上下文中,查询子句回答问题“这个文档与这个查询子句匹配吗?”,答案是简单的“是”或“不是”—没有计算分数。过滤器上下文主要用于过滤结构化数据。

查询和过滤器例子
PUT /my_index_01
{
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "store": true
      },
      "create_date": { "type": "date" },
      "id": {  "type": "keyword" },
      "user": {
        "properties": {
          "name": { "type": "keyword" },
          "age": { "type": "integer" }
        }
      },
      "status": {  "type": "keyword" }
    }
  }
}
POST /my_index_01/_doc/1
{"id":"00000001", "content":"Quick Brown Fox", "create_date": "2015-01-01", "user.name":"james", "user.age": 35, "status": "enable" }
POST /my_index_01/_doc/2
{"id":"00000002", "content":"Quick White Fox", "create_date": "2015-01-02", "user.name":"fake", "user.age": 26, "status": "disable" }
GET my_index_01/_search
{
    "query": {  //1
      "bool": {   //2
        "must":[
          {"match":{"content":"Fox"}},
          {"match":{"user.name":"james"}}
        ],
        "filter":[  //3
          {"term":{"status":"enable"}},
          {"range":{"create_date": {"gte":"2014-01-01"}}}
        ]
      }
  }
}

注释1:query参数表示查询上下文
注释2:在查询上下文中使用bool和两个match子句,这意味着它们会对每个文档的匹配程度进行评分。
注释3:filter参数表示过滤器上下文,在过滤器上下文中使用term和range子句,它们将过滤掉不匹配的文档,但不会影响匹配文档的得分。

注意:在查询上下文中为查询计算的分数表示为单精度浮点数;它们只有24位的精度。超过精度将被转换为浮点数进行分数计算,从而丢失精度。

3. 复合查询

首先创建索引my_index_01,并索引数据:

PUT /my_index_01
{
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "store": true
      },
      "create_date": { "type": "date" },
      "id": {  "type": "keyword" },
      "user": {
        "properties": {
          "name": { "type": "keyword" },
          "age": { "type": "integer" }
        }
      },
      "status": {  "type": "keyword" }
    }
  }
}
POST /my_index_01/_doc/1
{"id":"00000001", "content":"Quick Brown Fox", "create_date": "2015-01-01", "user.name":"james jordan", "user.age": 35, "status": "enable" }
POST /my_index_01/_doc/2
{"id":"00000002", "content":"Quick White Fox", "create_date": "2015-01-02", "user.name":"fake li", "user.age": 26, "status": "disable" }

3.1 bool查询

用于组合多个叶子或复合查询子句的布尔值查询,使用一个或多个布尔子句构建的,每个子句都有一个有类型的,如must、should、must_not或filter子句。

查询参数
  • must:子句(查询)必须出现在匹配的文档中,对文档分数产生影响。
  • filter:子句(查询)必须出现在匹配的文档中,filter子句在Filter上下文中执行,这意味着评分被忽略,如果只有该类型子句,那么文档评分为0。
  • should:子句(查询)应该出现在匹配的文档中,对文档分数产生影响。must和should子句的分数组合在一起,匹配的子句越多文档分数越高。
  • must_no:子句(查询)不能出现在匹配的文档中,must_no子句在过滤器上下文中执行,这意味着评分被忽略,如果只有该类型子句,那么文档评分为0。

查询示例如下:

POST my_index_01/_search
{
  "query": {
    "bool" : {
      "must" : {
        "term" : { "user.name" : "james jordan" }
      },
      "filter": {
        "term" : { "status" : "enable" }
      },
      "must_not" : {
        "range" : {
          "age" : { "gte" : 10, "lte" : 30 }
        }
      },
      "should" : [
        { "term" : { "id" : "00000001" } },
        { "term" : { "id" : "00000002" } }
      ],
      "minimum_should_match" : 1,
      "boost" : 1.0
    }
  }
}

使用minimum_should_match参数来指定返回的文档必须匹配的should子句的数量或百分比。如果布尔查询包含should子句,并且没有must或filter子句,则默认值为1,否则默认值为0。

命名查询

每个查询可在其顶级定义一个_name。可以使用命名查询来跟踪匹配返回的文档。如果使用了命名查询,则响应对于每次命中都包含一个matched_queries属性。示例如下:

POST my_index_01/_search
{
  "query": {
    "bool": {
      "should": {
        "match": {
          "user.name": { "query": "james jordan", "_name": "custom_value" }
        }
      }
    }
  }
}

返回结果如下

3.2 boosting查询

boosting查询包含两部分:positive查询和negative查询。

查询参数
  • positive:(必需的,object类型)任何返回的文档都必须匹配此查询。
  • negative:(必需的,object类型)用来降低匹配文档的相关性得分的查询,不过滤匹配的文档。
    如果返回的文档匹配positive查询和negative查询,boosting查询将计算该文档的最终相关性得分,如下所示:
    (1)从正查询中取原始的相关性分数。
    (2)将分数乘以negative_boost参数值。
  • negative_boost:(必需的、float类型)0到1.0之间的浮点数,用于降低与negative查询匹配的文档的相关性得分。

示例如下:

POST my_index_01/_search
{
  "query": {
    "boosting": {
      "positive": {
        "match": {
          "content": "Fox"
        }
      },
      "negative": {
        "match": {
          "content": "White"
        }
      },
      "negative_boost": 10
    }
  }
}

返回结果片段如下:

3.3 constant_score查询

包装一个过滤器(filter)查询,并返回每个文档的相关性分数为常量boost参数值。

查询参数
  • filter:(必需的,object类型)任何返回的文档都必须匹配此查询。过滤查询不计算相关性分数。为了提高性能,Elasticsearch会自动缓存经常使用的过滤查询。
  • boost:(可选的、float类型)用来设置返回文档的常量相关性分数,默认为1.0。

示例如下:

POST my_index_01/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "match": {  "content": "Fox" }
      },
      "boost": 1.2
    }
  }
}

3.4 dis_max查询

dis_max查询包含一个或多个查询子句,如果返回的文档匹配了多个查询子句,dis_max查询将找到匹配子句中最高相关性分数, 将其分配给文档,而且为任何其他匹配子查询加上一个离线增量。

查询参数
  • queries:(必需的,object数组)包含一个或多个查询子句。返回的文档必须匹配一个或多个这些查询。如果一个文档匹配多个查询,Elasticsearch使用最高的相关性得分。
  • tie_breaker:(可选的,float类型)0到1.0之间的浮点数,用于增加匹配查询子句的文档的相关性得分。默认为0.0。此值越大,文档的相关性得分越高。如果一个文档匹配多个子句,dis_max查询将计算该文档的相关性得分,如下所示:
    (1)从得分最高的匹配子句中取相关度分数。
    (2)将来自任何其他匹配子句的分数乘以tie_breaker值。
    (3)将最高的分数与相乘的分数相加得到最终文档的相关性得分。
    如果tie_breaker值大于0.0,那么所有匹配的子句都被计算在内,但是得分最高的子句被计算的最多。

示例如下:

POST my_index_01/_search
{
  "query": {
     "dis_max": {
        "queries": [
          { "term" : { "user.name" : "fake li" } },
          { "term" : { "id" : "00000002" } }
        ],
        "tie_breaker": 0.5
      }
  }
}

返回结果片段如下:

"max_score" : 0.76246184,
"hits" : [
  {
    "_index" : "my_index_01",
    "_type" : "_doc",
    "_id" : "2",
    "_score" : 0.76246184,
    "_source" : {
      "id" : "00000002",
      "content" : "Quick White Fox",
      "create_date" : "2015-01-02",
      "user.name" : "fake li",
      "user.age" : 26,
      "status" : "disable"
    }
  }
]

3.5 function_score查询

function_score查询可以修改查询返回的文档相关性分数,需要定义一个查询和若干个函数,用这些函数为查询返回的每个文档计算一个新分数。

示例如下:

POST my_index_01/_search
{
  "query": {
     "function_score": {
        "query": { "match_all": {} },
        "boost": "5", 
        "functions": [
          {
            "filter": { "match": { "content": "Brown" } },
            "random_score": {}, 
            "weight": 23
          },
          {
            "filter": { "match": { "content": "White" } },
            "weight": 42
          }
        ],
        "max_boost": 42,
        "score_mode": "max",
        "boost_mode": "multiply",
        "min_score": 30
    }
  }
}
  • 参数score_mode:每个文档由定义的函数评分。参数score_mode指定计算的分数是如何组合的。
score_mode
参数值
参数说明
multiply 分数相乘(默认)
sum 分数之和
avg 分数平均
first 第一个函数的分数
max 最大分数值
min 最小分数值
  • 参数weight:每个函数的分数可以根据用户定义的权重进行调整。权重可以在functions数组中为每个函数定义,并与各自函数计算的分数相乘。如果权重是在没有任何其他函数声明的情况下给出的,那么权重就充当一个函数,它只是返回权重。在score_mode设置为avg的情况下,单个分数将通过加权平均合并。例如,如果两个函数返回score 1和2,它们各自的权重为3和4,那么它们的得分将被合并为(1*3+2*4)/(3+4)
  • 参数max_boost:通过设置max_boost参数,可以限制新计算分数不超过某个限制。max_boost的默认值是FLT_MAX。
  • 参数boost_mode:通过设置boost_mode参数,用来结合文档的函数分数与查询分数。
boost_mode
参数值
参数说明
multiply 函数分数与查询分数相乘(默认)
replace 只使用函数分数,忽略查询分数
sum 函数分数与查询分数之和
avg 函数分数与查询分数平均
max 函数分数与查询分数之间最大值
min 函数分数与查询分数之间最小值
script_score

除了自定义函数,还可以使用脚本函数,使用script_score函数可以包装另一个查询,并使用脚本表达式从文档中的其他数值字段值来定制它的评分。示例如下:

POST my_index_01/_search
{
  "query": {
    "function_score": {
      "query": { "match_all": {} },
      "script_score": {
        "script": {
          "source": "Math.log(2 + doc['user.age'].value)"
        }
      }
    }
  }
}

脚本支持参数形式,示例如下:

POST my_index_01/_search
{
  "query": {
    "function_score": {
      "query": { "match_all": {} },
      "script_score": {
        "script": {
          "params": {
            "a": 5,
            "b": 1.2
          },
          "source": "params.a / Math.pow(params.b, doc['user.age'].value)"
        }
      }
    }
  }
}
其他得分函数
  • 随机函数(random_score):random_score生成从0到1(不包括1)均匀分布的分数。默认情况下,它使用内部Lucene文档id作为随机性的来源。
  • 衰减函数:衰减函数为文档计算得分,该函数根据文档的数值字段值与给定起点的距离衰减。

你可能感兴趣的:(Elasticsearch系列(12)Query之复合查询)