Loading

高级查询

五、高级查询

查询文档中,已经介绍了基本的查询。下面是高级查询:

1 DSL查询之match

DSL分为matchtrem两种模式。

1.1 match

在前面简单介绍了match的查询:

GET books/_doc/_search
{
  "query": {
    "match": {
      "price": 50
    }
  }
}

将查询条件添加到match中即可,而match则是查询所有price字段的值中为50的结果。

1.2 match_all

除了按条件查询之外,我们还可以查询books索引下的doc类型中的所有文档,查询全部match_all

GET books/_doc/_search
{
  "query": {
    "match_all": {}
  }
}

1.3 match_phrase

前面的查询中,我们只能获取文档中的关键字,首先创建一些示例:

PUT t1/_doc/1
{
  "title": "中国是世界上人口最多的国家"
}
PUT t1/_doc/2
{
  "title": "美国是世界上军事实力最强大的国家"
}
PUT t1/_doc/3
{
  "title": "北京是中国的首都"
}

现在,当我们以中国作为搜索条件,我们希望只返回和中国相关的文档。我们首先来使用match查询:

GET t1/_doc/_search
{
  "query": {
    "match": {
      "title": "中国"
    }
  }
}

image

虽然如期的返回了中国的文档。但是却把和美国的文档也返回了,这并不是我们想要的。是怎么回事呢?因为这是elasticsearch在内部对文档做分词的时候,对于中文来说,就是一个字一个字分的,所以,我们搜中国都符合条件,返回,而美国的也符合。
而我们认为中国是个短语,是一个有具体含义的词。所以elasticsearch在处理中文分词方面比较弱势。我们可以用中文分词器来解决。

另外还有另一种办法解决,那就是使用短语查询:

GET t1/_doc/_search
{
  "query": {
    "match_phrase": {
      "title": {
        "query": "中国"
      }
    }
  }
}

这里match_phrase是在文档中搜索指定的词组,而中国则正是一个词组,所以能获取到。

比如我们要想搜索中国世界相关的文档,但又忘记其余部分了,此时可以使用match_phrase指定分词之间的间隔:

GET t1/_doc/_search
{
  "query": {
    "match_phrase": {
      "title": {
        "query": "中国世界",
        "slop": 2
      }
    }
  }
}

这里的slop相当于正则中的中国.*?世界。这个间隔默认为0,如果不加会导致查询不到,指定为2就可以查到了。

1.4 match_phrase_prefix

凌晨2点半,单身狗小黑为了缓解寂寞,就准备搜索几个beautiful girl来陪伴自己。但是由于英语没过2级,但单词beautiful拼到bea就不知道往下怎么拼了。这个时候,我们的智能搜索要帮他啊,elasticsearch就看自己的词库有啥是bea开头的词,结果还真发现了两个:

PUT t3/_doc/1
{
  "title": "maggie",
  "desc": "beautiful girl you are beautiful so"
}
PUT t3/_doc/2
{
  "title": "sun and beach",
  "desc": "I like basking on the beach"
}

但这里用matchmatch_phrase都不太合适,因为小黑输入的不是完整的词。那怎么办呢?我们用match_phrase_prefix来搞:

GET t3/_doc/_search
{
  "query": {
    "match_phrase_prefix": {
      "desc": "bea"
    }
  }
}

这样就搜索到了前面插入的两条数据。

前缀查询是短语查询类似,但前缀查询可以更进一步的搜索词组,只不过它是和词组中最后一个词条进行前缀匹配(如搜这样的you are bea)。应用也非常的广泛,比如搜索框的提示信息,当使用这种行为进行搜索时,最好通过max_expansions来设置最大的前缀扩展数量,因为产生的结果会是一个很大的集合,不加限制的话,影响查询性能。

GET t3/_doc/_search
{
  "query": {
    "match_phrase_prefix": {
      "desc": {
        "query": "bea",
        "max_expansions": 1
      }
    }
  }
}

我们只需要记住,使用前缀查询会非常的影响性能,要对结果集进行限制,就加上这个max_expansions参数。

1.5 multi_match

有时候需要多字段查询:现在,我们有一个50个字段的索引,我们要在多个字段中查询同一个关键字,该怎么做呢?

PUT t3/_doc/1
{
  "title": "maggie is beautiful girl",
  "desc": "beautiful girl you are beautiful so"
}
PUT t3/_doc/2
{
  "title": "beautiful beach",
  "desc": "I like basking on the beach,and you? beautiful girl"
}

我们先用原来的方法查询:

GET t3/_doc/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "beautiful"
          }
        },
        {
          "match": {
            "desc": "beautiful"
          }
        }
      ]
    }
  }
}

bool是布尔查询,后面会介绍。使用must来限制两个字段(值)中必须同时含有关键字。这样虽然能达到目的,但是当有很多的字段,我们可以用multi_match来做:

GET t3/_doc/_search
{
  "query": {
    "multi_match": {
      "query": "beautiful",
      "fields": ["title", "desc"]
    }
  }
}

我们将多个字段放到fields列表中即可。以达到匹配多个字段的目的。也就是在titledesc中查询beautiful关键字。

除此之外,multi_match甚至可以当做match_phrasematch_phrase_prefix使用,只需要指定type类型即可:

GET t3/_doc/_search
{
  "query": {
    "multi_match": {
      "query": "gi",
      "fields": ["title"],
      "type": "phrase_prefix"
    }
  }
}
GET t3/_doc/_search
{
  "query": {
    "multi_match": {
      "query": "girl",
      "fields": ["title"],
      "type": "phrase"
    }
  }
}

2 DSL查询之term

默认情况下,es在对文档分析期间(将文档分词后保存到倒排索引中),会对文档进行分词,比如默认的标准分析器会对文档进行:

  • 删除大多数的标点符号。
  • 将文档分解为单个词条,我们称为token。
  • 将token转为小写。

完事再保存到倒排索引上,当然,原文件还是要保存一份的,而倒排索引使用来查询的。

例如Beautiful girl!,在经过分析后是这样的了:

POST _analyze
{
  "analyzer": "standard",
  "text": "Beautiful girl!"
}
# 结果
["beautiful", "girl"]

而当在使用match查询时,elasticsearch同样会对查询关键字进行分析,

PUT w10
{
  "mappings": {
    "_doc":{
      "properties":{
        "t1":{
          "type": "text"
        }
      }
    }
  }
}
PUT w10/_doc/1
{
  "t1": "Beautiful girl!"
}
PUT w10/_doc/2
{
  "t1": "sexy girl!"
}

GET w10/_doc/_search
{
  "query": {
    "match": {
      "t1": "Beautiful girl!"
    }
  }
}

也就是对查询关键字Beautiful girl!进行分析,得到["beautiful", "girl"],然后分别将这两个单独的token去索引中进行查询,结果就是将两篇文档都返回。这在有些情况下是非常好用的,但是,如果我们想查询确切的词怎么办?也就是精确查询,将Beautiful girl!当成一个token而不是分词后的两个token。这就要用到了term查询了,term查询的是没有经过分析的查询关键字。

如果你要查询的字段类型(如上例中的字段t1类型是text)是text(因为elasticsearch会对文档进行分析,上面说过),那么你得到的可能是不尽如人意的结果或者压根没有结果:

GET w10/_doc/_search
{
  "query": {
    "term": {
      "t1": "Beautiful girl!"
    }
  }
}

如上面的查询,将不会有结果返回,因为索引w10中的两篇文档在经过elasticsearch分析后没有一个分词是Beautiful girl!,那此次查询结果为空也就好理解了。

所以,我们这里得到一个论证结果:不要使用term对类型是text的字段进行查询,要查询text类型的字段,请改用match查询。

再来一个示例:

GET w10/_doc/_search
{
  "query": {
    "term": {
      "t1": "Beautiful"
    }
  }
}

答案是,没有结果返回!因为elasticsearch在对文档进行分析时,会经过小写!人家倒排索引上存的是小写的beautiful,而我们查询的是大写的Beautiful

所以,想有结果要这样:

GET w10/_doc/_search
{
  "query": {
    "term": {
      "t1": "beautiful"
    }
  }
}

那term查询可以查询哪些类型的字段呢,例如elasticsearch会将keyword类型的字段当成一个token保存到倒排索引上,你可以将term和keyword结合使用。

最后,要想使用term查询多个精确的值使用terms(term+s)查询:

GET w10/_doc/_search
{
  "query": {
    "terms": {
      "t1": ["beautiful", "sexy"]
    }
  }
}

3 排序sort

3.1 降序desc

比如想要根据age字段按照降序排序:

GET foo/_doc/_search
{
  "query": {
    "match": {
      "title": "xxx"
    }
  },
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ]
}

3.2 升序asc

GET foo/_doc/_search
{
  "query": {
    "match": {
      "title": "xxx"
    }
  },
  "sort": [
    {
      "age": {
        "order": "asc"
      }
    }
  ]
}

注意:在排序的过程中,只能使用可排序的属性进行排序。可以排序的属性有数字和日期,使用其它的都不行。

4 分页from/size

将结果分页:

GET foo/_doc/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ], 
  "from": 2,
  "size": 1
}

上例,首先以age降序排序,查询所有。并且在查询的时候,添加两个属性fromsize来控制查询结果集的数据条数。

  • from:从哪开始查
  • size:返回几条结果

你可以这样:from:2,size:2意为从第2条开始返回两条数据。

如果这样写:siez:0那么就返回0条结果。

学到这里,我们也可以看到,查询条件越来越多,开始仅是简单查询,慢慢增加条件查询,增加排序,对返回结果进行限制。所以,我们可以说:对于elasticsearch来说,所有的条件都是可插拔的,彼此之间用,分割。

5 布尔查询

布尔查询是最常用的组合查询,根据子查询的规则,只有当文档满足所有子查询条件时,elasticsearch引擎才将结果返回。布尔查询支持的子查询条件共4种:

  • must(and)
  • should(or)
  • must_not(not)
  • filter(条件过滤)

5.1 must

逻辑与(and)。

GET foo/_doc/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "from": "gu"
          }
        }
      ]
    }
  }
}

上例中,我们通过在bool属性(字段)内使用must来作为查询条件,那么条件是什么呢?条件同样被match包围,就是fromgu的所有数据。这里需要注意的是must字段对应的是个列表[],也就是说可以有多个并列的查询条件,一个文档满足各个子条件后才最终返回。

比如想要查询fromgu,并且age30的数据:

GET foo/_doc/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "from": "gu"
          }
        },
        {
          "match": {
            "age": 30
          }
        }
      ]
    }
  }
}

5.2 should

逻辑或(or)。

那么,如果要查询只要是fromgu或者tags闭月的数据:

GET foo/_doc/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "from": "gu"
          }
        },
        {
          "match": {
            "tags": "闭月"
          }
        }
      ]
    }
  }
}

5.3 must_not

逻辑非(not)。

查询from既不是gu并且tags也不是可爱,还有age不是18的数据:

GET foo/_doc/_search
{
  "query": {
    "bool": {
      "must_not": [
        {
          "match": {
            "from": "gu"
          }
        },
        {
          "match": {
            "tags": "可爱"
          }
        },
        {
          "match": {
            "age": 18
          }
        }
      ]
    }
  }
}

5.4 filter

查询fromguage大于25的数据:

GET foo/_doc/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "from": "gu"
          }
        }
      ],
      "filter": {
        "range": {
          "age": {
            "gt": 25
          }
        }
      }
    }
  }
}

这里就用到了filter条件过滤查询,过滤条件的范围用range表示,gt表示大于。

过滤条件有:

  • gt:大于
  • lt:小于
  • gte:大于等于
  • lte:小于等于

想要查询一个范围内的数据,比如要查询fromguage25~30之间的数据:

GET foo/_doc/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "from": "gu"
          }
        }
      ],
      "filter": {
        "range": {
          "age": {
            "gte": 25,
            "lte": 30
          }
        }
      }
    }
  }
}

同时使用ltegte来限定范围。

另外,如果在filter过滤条件中使用should的话,结果可能不会尽如人意!建议使用must代替。并且注意filter工作于bool查询内。比如我们将刚才的查询条件改一下,把filterbool中挪出来,就会报错。

6 查询结果高亮显示

如果返回的结果集中很多符合条件的结果,我们想要让某些结果高亮显示。比如下面网站所示的那样,我们搜索elasticsearch,在结果集中,将所有elasticsearch高亮显示。
image

我们这样做:

GET foo/_doc/_search
{
  "query": {
    "match": {
      "name": "石头"
    }
  },
  "highlight": {
    "fields": {
      "name": {}
    }
  }
}

使用highlight属性来实现结果高亮显示,需要的字段名称添加到fields内即可,es会自动将检索结果用标签包裹起来,用于在页面中渲染。返回的结果会变成这样

"highlight" : {
          "name" : [
            "<em>石</em><em>头</em>"
          ]
}

结果被<em></em>标签包裹。如果不想用em标签,也可以自定义标签。

GET foo/_doc/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "highlight": {
    "pre_tags": "<b class='key' style='color:red'>",
    "post_tags": "</b>",
    "fields": {
      "from": {}
    }
  }
}

上例中,在highlight中,pre_tags用来实现我们的自定义标签的前半部分,在这里,我们也可以为自定义的标签添加属性和样式。post_tags实现标签的后半部分,组成一个完整的标签。至于标签中的内容,则还是交给fields来完成。

需要注意的是:自定义标签中属性或样式中的逗号一律用英文状态的单引号表示,应该与外部elasticsearch语法的双引号区分开。

7 查询结果过滤

对查询结果进行过滤,使用_source,比如只需要查看nameage两个属性,其他的不要:

GET foo/_doc/_search
{
  "query": {
    "match": {
      "name": "xxx"
    }
  },
  "_source": ["name", "age"]
}

8 聚合查询

聚合函数大家都不陌生,es中也没玩出新花样:

  • avg
  • max
  • min
  • sum

8.1 avg

GET foo/_doc/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "aggs": {
    "my_avg": {
      "avg": {
        "field": "age"
      }
    }
  },
  "_source": ["name", "age"]
}

上例中,首先匹配查询fromgu的数据。在此基础上做查询平均值的操作,这里就用到了聚合函数,其语法被封装在aggs中,而my_avg则是为查询结果起个别名,封装了计算出的平均值。那么,要查age字段的平均值。最后对结果进行过滤,只返回nameage字段的数据以及年龄的平均值。

如果不想看都有哪些数据,只想看平均值,用size即可:

GET foo/_doc/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "aggs": {
    "my_avg": {
      "avg": {
        "field": "age"
      }
    }
  },
  "size": 0, 
  "_source": ["name", "age"]
}

只需要在原来的查询基础上,增加一个size就可以了,我们写上0,就是输出0条查询结果。

8.2 max

查最大值:

GET foo/_doc/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "aggs": {
    "my_max": {
      "max": {
        "field": "age"
      }
    }
  },
  "size": 0
}

8.3 min

查最小值

GET foo/_doc/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "aggs": {
    "my_min": {
      "min": {
        "field": "age"
      }
    }
  },
  "size": 0
}

8.4 sum

求总和:

GET foo/_doc/_search
{
  "query": {
    "match": {
      "from": "gu"
    }
  },
  "aggs": {
    "my_sum": {
      "sum": {
        "field": "age"
      }
    }
  },
  "size": 0
}

8.5 分组查询

假如想要查询所有人的年龄段,并且按照15~20,20~25,25~30分组,并且算出每组的平均年龄。

首先做出分组:

GET foo/_doc/_search
{
  "size": 0, 
  "query": {
    "match_all": {}
  },
  "aggs": {
    "age_group": {
      "range": {
        "field": "age",
        "ranges": [
          {
            "from": 15,
            "to": 20
          },
          {
            "from": 20,
            "to": 25
          },
          {
            "from": 25,
            "to": 30
          }
        ]
      }
    }
  }
}

上例中,在aggs的自定义别名age_group中,使用range来做分组,field是以age为分组,分组使用ranges来做,fromto是范围,我们根据需求做出三组。接下来,我们就要对每个小组内的数据做平均年龄处理。

GET foo/_doc/_search
{
  "size": 0, 
  "query": {
    "match_all": {}
  },
  "aggs": {
    "age_group": {
      "range": {
        "field": "age",
        "ranges": [
          {
            "from": 15,
            "to": 20
          },
          {
            "from": 20,
            "to": 25
          },
          {
            "from": 25,
            "to": 30
          }
        ]
      },
      "aggs": {
        "my_avg": {
          "avg": {
            "field": "age"
          }
        }
      }
    }
  }
}

上例中,在分组下面,我们使用aggsage做平均数处理,这样就可以了。

注意:一定要先查出结果,然后对结果使用聚合函数做处理。

posted @ 2021-12-16 23:32  yyyz  阅读(125)  评论(0编辑  收藏  举报