ElasticSearch之-文档操作

1 CURD

1.1 Create

当执行PUT命令时,如果数据不存在,则新增该条数据,如果数据存在则修改该条数据

#新增一个id为1的书籍(POST和PUT都可以)
PUT lqz/_doc/1   #POST lqz/_doc/1/_create  POST lqz/_doc/1  POST lqz/_doc 会自动创建id,必须用Post
{
  "title":"红楼梦",
  "price":12,
  "publish_addr":{
    "province":"黑龙江",
    "city":"鹤岗"
  },
  "publish_date":"2013-11-11",
  "read_num":199,
  "tag":["古典","名著"]
}

1.2 Retrieve

#查询lqz索引下id为7的文档
GET lqz/_doc/7
#查询lqz索引下id为7的文档,只要title字段
GET lqz/_doc/7?_source=title
#查询lqz索引下id为7的文档,只要title和price字段
GET lqz/_doc/7?_source=title,price
#查询lqz索引下id为7的文档,要全部字段
GET lqz/_doc/7?_source

1.3 Update

#修改文档(覆盖修改) PUT命令,在做修改操作时,如果未指定其他的属性,则按照指定的属性进行修改操作;只修改了"title"属性,其他的属性并没有一起添加进去
PUT lqz/_doc/1
{
  "title":"金品梅",
}

#修改文档,增量修改,修改了"title",原来其他的属性还在
#POST命令,这里可用来执行修改操作(还有其他的功能),`POST`命令配合`_update`完成修改操作,指定修改的内容放到`doc`中。
#注意是post请求,并且把修改的字段放在"doc"字典里
POST lqz/_update/1
{
  "doc":{
    "title":"足球小将"
  }
}

1.4 Delete

#删除文档id为1的
DELETE lqz/_doc/1

1.5 批量操作之_mget

#批量获取lqz索引_doc类型下id为2的数据和lqz2索引_doc类型下id为1的数据
GET _mget
{
  "docs":[
    {
      "_index":"lqz",
      "_type":"_doc",
      "_id":2
    },
    {
      "_index":"lqz2",
      "_type":"_doc",
      "_id":1
    }
    ]
}

#批量获取lqz索引下id为1和2的数据
GET lqz/_mget
{
  "docs":[
    {
      "_id":2
    },
    {
      "_id":1
    }
    ]
}
#同上
GET lqz/_mget
{
  "ids":[1,2]
}

1.6 批量操作之bulk

PUT test/_doc/2/_create
{
  "field1" : "value22"
}
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

 

2 Elasticsearch查询的两种方式

2.1 前言

简单的没挑战,来点复杂的,elasticsearch提供两种查询方式:

  • 查询字符串(query string),简单查询,就像是像传递URL参数一样去传递查询语句,被称为简单搜索或查询字符串(query string)搜索。

  • 另外一种是通过DSL语句来进行查询,被称为DSL查询(Query DSL),DSL是Elasticsearch提供的一种丰富且灵活的查询语言,该语言以json请求体的形式出现,通过restful请求与Elasticsearch进行交互。


2.2 数据准备

PUT zl/_doc/1
{
  "name":"刘亦菲",
  "age":30,
  "from": "中国",
  "desc": "性格直、娇美",
  "tags": ["靓", "矮", "直"]
}

PUT zl/_doc/2
{
  "name":"高圆圆",
  "age":18,
  "from":"中国",
  "desc":"肤白貌美,娇憨可爱",
  "tags":["白", "富","美"]
}

PUT zl/_doc/3
{
  "name":"古力娜扎",
  "age":22,
  "from":"新疆",
  "desc":"高鼻子,大眼睛,高大",
  "tags":["高", "大", "上"]
}


PUT zl/_doc/4
{
  "name":"仓老师",
  "age":29,
  "from":"日本",
  "desc":"粗中有细,影后",
  "tags":["肥", "大","猛"]
}

PUT zl/_doc/5
{
  "name":"汤唯",
  "age":25,
  "from":"中国",
  "desc":"仿佛兮若轻云之蔽月",
  "tags":["闭月","羞花"]
}

2.3 查询字符串

GET zl/_doc/_search?q=from:中国   #查询form字段是"中国"的所有人

还是使用GET命令,通过_serarch查询,查询条件是什么呢?条件是from属性是中国的人都有哪些。最后,别忘了_searchfrom属性中间的英文分隔符? 结果如下

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 1.077993,
    "hits" : [
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "1",
        "_score" : 1.077993,
        "_source" : {
          "name" : "刘亦菲",
          "age" : 30,
          "from" : "中国",
          "desc" : "性格直、娇美",
          "tags" : [
            "靓",
            "矮",
            "直"
          ]
        }
      },
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "2",
        "_score" : 1.077993,
        "_source" : {
          "name" : "高圆圆",
          "age" : 18,
          "from" : "中国",
          "desc" : "肤白貌美,娇憨可爱",
          "tags" : [
            "白",
            "富",
            "美"
          ]
        }
      },
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "5",
        "_score" : 1.077993,
        "_source" : {
          "name" : "汤唯",
          "age" : 25,
          "from" : "中国",
          "desc" : "仿佛兮若轻云之蔽月",
          "tags" : [
            "闭月",
            "羞花"
          ]
        }
      }
    ]
  }
}

我们来重点说下hitshits是返回的结果集——所有from属性为中国的结果集。重点中的重点是_score得分,满分10份,得分是什么呢?根据算法算出跟查询条件的匹配度,匹配度高得分就高。后面再说这个算法是怎么回事。

 2.4 结构化查询

我们现在使用DSL方式,来完成刚才的查询,查看来自中国的都有哪些人。

GET zl/_doc/_search
{
  "query": {
    "match": {
      "from": "中国"
    }
  }
}

上例,查询条件是一步步构建出来的,将查询条件添加到match中即可,而match则是查询所有from字段的值中含有中国的结果就会返回。 当然结果没啥变化。

 

3 term与match查询

3.1 match查询

还是上面的数据

3.1.1 match系列之match(按条件查询)

跟上例一样,已经演示过了

GET zl/_doc/_search
{
  "query": {
    "match": {
      "from": "中国"
    }
  }
}

3.1.2 match系列之match_all(查询全部)

除了按条件查询之外,我们还可以查询zl索引下的_doc类型中的所有文档,那就是查询全部:

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

match_all的值为空,表示没有查询条件,那就是查询全部。就像select * from table_name一样。返回的是zl索引下_doc类型的所有文档!

3.1.3 match系列之match_phrase(短语查询)

我们现在已经对match有了基本的了解,match查询的是散列映射,包含了我们希望搜索的字段和字符串。也就说,只要文档中只要有我们希望的那个关键字,但也因此带来了一些问题。 首先来创建一些示例:

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

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

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

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

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

这里match_phrase是在文档中搜索指定的词组,而中国则正是一个词组,所以愉快的返回了。 那么,现在我们要想搜索中国世界相关的文档,但又忘记其余部分了,怎么做呢?用match也不行,那就继续用match_phrase试试:

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

返回结果也是空的,因为没有中国世界这个短语。 我们搜索中国世界这两个指定词组时,但又不清楚两个词组之间有多少别的词间隔。那么在搜的时候就要留有一些余地。这时就要用到了slop了。相当于正则中的中国.*?世界。这个间隔默认为0,导致我们刚才没有搜到,现在我们指定一个间隔。

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

现在,两个词组之间有了2个词的间隔,这个时候,就可以查询到结果了。

slop间隔你可以根据需要适当改动。上例表示 中国和世界这两个词之间最小的距离为2, 因为这两个词中间间隔1,只要大于等1的间隔距离都能匹配。

短语查询, 比如要查询:python系统 会把查询条件 “python和系统分词”,放到列表中,再去搜索的时候,必须满足python和系统同时存在的才能搜出来


3.1.4 match系列之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"
    }
  }
}

结果搜出了t3索引下的两个_doc文档

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

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

max_expansions执行的是搜索的编辑(Levenshtein)距离。那什么是编辑距离呢?编辑距离是一种计算两个字符串间的差异程度的字符串度量(string metric)。我们可以认为编辑距离就是从一个字符串修改到另一个字符串时,其中编辑单个字符(比如修改、插入、删除)所需要的最少次数。俄罗斯科学家Vladimir Levenshtein于1965年提出了这一概念。

我们再引用elasticsearch官网的一段话:

该max_expansions设置定义了在停止搜索之前模糊查询将匹配的最大术语数,也可以对模糊查询的性能产生显着影响。但是,减少查询字词会产生负面影响,因为查询提前终止可能无法找到某些有效结果。重要的是要理解max_expansions查询限制在分片级别工作,这意味着即使设置为1,多个术语可能匹配,所有术语都来自不同的分片。此行为可能使其看起来好像max_expansions没有生效,因此请注意,计算返回的唯一术语不是确定是否有效的有效方法max_expansions。我想你也没看懂这句话是啥意思,但我们只需知道该参数工作于分片层,也就是Lucene部分,超出我们的研究范围了。 我们快刀斩乱麻的记住,使用前缀查询会非常的影响性能,要对结果集进行限制,就加上这个参数。


3.1.5 match系列之multi_match(多字段查询)

现在,我们有一个50个字段的索引,我们要在多个字段中查询同一个关键字,该怎么做呢?还是以t3索引的数据为例

我们可以用multi_match来做:

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

我们将多个字段放到fields列表中即可。以达到匹配多个字段的目的。 除此之外,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"
    }
  }
}

小结:

  • match:返回所有匹配的分词。

  • match_all:查询全部。

  • match_phrase:短语查询,在match的基础上进一步查询词组,可以指定slop分词间隔。

  • match_phrase_prefix:前缀查询,根据短语中最后一个词组做前缀匹配,可以应用于搜索提示,但注意和max_expanions搭配。其实默认是50.......

  • multi_match:多字段查询,使用相当的灵活,可以完成match_phrasematch_phrase_prefix的工作。


3.2 term查询

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

  • 删除大多数的标点符号。

  • 将文档分解为单个词条,我们称为token。

  • 将token转为小写。

完事再保存到倒排索引上,当然,原文件还是要保存一分的,而倒排索引是用来查询的。 例如Beautiful girl!,在经过分析后是这样的了

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

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

创建一个索引w10, 创建一个mapping,创建一个t1字段是text类型
PUT w10
{
  "mappings": {
     "properties":{
       "t1":{
         "type": "text"
      }
    }
  }
}

# 给t1字段插入数据
PUT w10/_doc/1
{
  "t1": "Beautiful girl!"
}
PUT w10/_doc/2
{
  "t1": "sexy girl!"
}

# match查询, 会将Beautiful girl分词,分别去查,查出两篇文档
GET w10/_doc/_search
{
  "query": {
    "match": {
      "t1": "Beautiful girl!"
    }
  }
}

也就是对查询关键字Beautiful girl!进行分析,得到["beautiful", "girl"],然后分别将这两个单独的token去索引w10中进行查询,结果就是将两篇文档都返回。

这在有些情况下是非常好用的,但是,如果我们想查询确切的词怎么办?也就是精确查询,将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查询。因为elasticsearch会对text类型字段进行分词,用term精准查询无法返回结果。

学会了吗?那再来一个示例,你说一下结果是什么:

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查询多个精确的值怎么办?我只能说:亲,这里推荐卸载es呢!低调又不失尴尬的玩笑!

这里推荐使用terms查询:

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

查询结果如下:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "w10",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "t1" : "Beautiful girl!"
        }
      },
      {
        "_index" : "w10",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "t1" : "sexy girl!"
        }
      }
    ]
  }
}

 

4 排序查询 sort

4.1 降序:desc

想到排序,出现在脑海中的无非就是升(正)序和降(倒)序。比如我们查询上例“中国”都有哪些人,并根据age字段按照降序,并且,我只想看nmaeage字段:

GET zl/_doc/_search
{
  "query": {
    "match": {
      "from": "中国"
    }
  },
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ]
}

上例,在条件查询的基础上,我们又通过sort来做排序,根据age字段排序,是降序呢还是升序,由order字段控制,desc是降序。

结果如下:

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "1",
        "_score" : null,
        "_source" : {
          "name" : "刘亦菲",
          "age" : 30,
          "from" : "中国",
          "desc" : "性格直、娇美",
          "tags" : [
            "靓",
            "矮",
            "直"
          ]
        },
        "sort" : [
          30
        ]
      },
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "5",
        "_score" : null,
        "_source" : {
          "name" : "汤唯",
          "age" : 25,
          "from" : "中国",
          "desc" : "仿佛兮若轻云之蔽月",
          "tags" : [
            "闭月",
            "羞花"
          ]
        },
        "sort" : [
          25
        ]
      },
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "2",
        "_score" : null,
        "_source" : {
          "name" : "高圆圆",
          "age" : 18,
          "from" : "中国",
          "desc" : "肤白貌美,娇憨可爱",
          "tags" : [
            "白",
            "富",
            "美"
          ]
        },
        "sort" : [
          18
        ]
      }
    ]
  }
}

4.2 升序:asc

上例,想要以升序的方式排列,只需要将order值换为asc就可以了。

GET zl/_doc/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "age": {
        "order": "asc"
      }
    }
  ]
}

4.3 不是什么数据类型都能排序

注意:在排序的过程中,只能使用可排序的属性进行排序。那么可以排序的属性有哪些呢?

- 数字
- 日期

其他的都不行!

 

5 分页查询 from/size

我们来看看elasticsearch是怎么将结果分页的:

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

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

  • from:从哪开始查

  • size:返回几条结果

如上例的结果:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 5,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "5",
        "_score" : null,
        "_source" : {
          "name" : "汤唯",
          "age" : 25,
          "from" : "中国",
          "desc" : "仿佛兮若轻云之蔽月",
          "tags" : [
            "闭月",
            "羞花"
          ]
        },
        "sort" : [
          25
        ]
      }
    ]
  }
}

上例中,在返回的结果集中,从第2条开始,返回1条数据。那如果想要从第2条开始,返回2条结果怎么做呢?

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

上例中,我们指定from为2,意为从第2条开始返回,返回多少呢?size意为2条。

如果我们从第4条开始返回2条呢?

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

上例中仅有一条数据,那是为啥呢?因为我们现在只有5条数据,从第4条开始查询,就只有1条符合条件,所以,就返回了1条数据。

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

GET lqz/doc/_search
{
  "query": {
    "match_all": {}
  },
  "from": 4,
  "size": 2
}

 

6 布尔查询

6.1 前言

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

  • must(and)

  • should(or)

  • must_not(not)

  • filter

下面我们来看看每个子查询条件都是怎么玩的。


6.2 must

现在,我们用布尔查询所有from属性为中国的数据:

GET zl/_doc/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "from": "中国"
          }
        }
      ]
    }
  }
}

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

那么,我们想要查询from中国,并且age30的数据怎么搞呢?

GET zl/_doc/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "from": "中国"
          }
        },
        {
          "match": {
            "age": 30
          }
        }
      ]
    }
  }
}

上例中,在must列表中,在增加一个age30的条件。查询结果如下:

{
  "took" : 5,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 2.077993,
    "hits" : [
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "1",
        "_score" : 2.077993,
        "_source" : {
          "name" : "刘亦菲",
          "age" : 30,
          "from" : "中国",
          "desc" : "性格直、娇美",
          "tags" : [
            "靓",
            "矮",
            "直"
          ]
        }
      }
    ]
  }
}

注意:现在你可能慢慢发现一个现象,所有属性值为列表的,都可以实现多个条件并列存在。

6.3 should

那么,如果要查询只要是from中国或者tags闭月的数据怎么搞?

GET zl/_doc/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "from": "中国"
          }
        },
        {
          "match": {
            "tags": "闭月"
          }
        }
      ]
    }
  }
}

上例中,或关系的不能用must的了,而是要用should,只要符合其中一个条件就返回。结果如下:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 3.5933313,
    "hits" : [
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "5",
        "_score" : 3.5933313,
        "_source" : {
          "name" : "汤唯",
          "age" : 25,
          "from" : "中国",
          "desc" : "仿佛兮若轻云之蔽月",
          "tags" : [
            "闭月",
            "羞花"
          ]
        }
      },
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "1",
        "_score" : 1.077993,
        "_source" : {
          "name" : "刘亦菲",
          "age" : 30,
          "from" : "中国",
          "desc" : "性格直、娇美",
          "tags" : [
            "靓",
            "矮",
            "直"
          ]
        }
      },
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "2",
        "_score" : 1.077993,
        "_source" : {
          "name" : "高圆圆",
          "age" : 18,
          "from" : "中国",
          "desc" : "肤白貌美,娇憨可爱",
          "tags" : [
            "白",
            "富",
            "美"
          ]
        }
      }
    ]
  }
}

6.4 must_not

那么,如果我想要查询from既不是中国并且tags也不是 ,还有age不是18的数据怎么办?

GET zl/_doc/_search
{
  "query": {
    "bool": {
      "must_not": [
        {
          "match": {
            "from": "中国"
          }
        },
        {
          "match": {
            "tags": "高"
          }
        },
        {
          "match": {
            "age": 18
          }
        }
      ]
    }
  }
}

上例中,mustshould都不能使用,而是使用must_not,又在内增加了一个age18的条件。结果如下:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.0,
    "hits" : [
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "4",
        "_score" : 0.0,
        "_source" : {
          "name" : "仓老师",
          "age" : 29,
          "from" : "日本",
          "desc" : "粗中有细,影后",
          "tags" : [
            "肥",
            "大",
            "猛"
          ]
        }
      }
    ]
  }
}

 上例中,只有仓老师这一条数据,因为只有苍老师既不是中国的人,标签没有“高”那一项,年龄也不等于18!
 这里有点需要补充,条件中`age`对应的`18`你写成整形还是字符串都没啥……


6.5 filter

那么,如果要查询from中国age大于25的数据怎么查?

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

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

  • gt:大于,相当于关系型数据库中的>

  • gte:大于等于,相当于关系型数据库中的>=

  • lt:小于,相当于关系型数据库中的<

  • lte:小于等于,相当于关系型数据库中的<=

要查询from中国age25~30之间的怎么查? 使用ltegte来限定范围。

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

注意事项:

1、在查询过程中,优先经过filter过滤,should或关系,如果在filter过滤条件中使用should的话,结果可能不会尽如人意!建议使用must代替

2、filter工作于bool查询内。如果我们将filterbool平级,查询会出现报错,所以,filter工作位置很重要。


6.6 组合查询总结

  • must:与关系,相当于关系型数据库中的and

  • should:或关系,相当于关系型数据库中的or

  • must_not:非关系,相当于关系型数据库中的not

  • filter:过滤条件。

  • range:条件筛选范围。

  • gt:大于,相当于关系型数据库中的>

  • gte:大于等于,相当于关系型数据库中的>=

  • lt:小于,相当于关系型数据库中的<

  • lte:小于等于,相当于关系型数据库中的<=

 

7 查询结果过滤

7.1 前言

在未来,一篇文档可能有很多的字段,每次查询都默认给我们返回全部,在数据量很大的时候,是的,比如我只想查姑娘的手机号,你一并给我个喜好、三围什么的算什么? 所以,我们对结果做一些过滤,清清白白的告诉elasticsearch。

7.2 数据准备

PUT lqz/_doc/1
{
  "name":"顾老二",
  "age":30,
  "from": "gu",
  "desc": "皮肤黑、武器长、性格直",
  "tags": ["黑", "长", "直"]
}

7.3 结果过滤:_source

现在,在所有的结果中,我只需要查看nameage两个属性,其他的不要怎么办?

GET lqz/_doc/_search
{
  "query": {
    "match": {
      "name": "顾老二"
    }
  },
  "_source": ["name", "age"]
}

 如上例所示,在查询中,通过_source来控制仅返回nameage属性。

{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.8630463,
    "hits" : [
      {
        "_index" : "lqz",
        "_type" : "doc",
        "_id" : "1",
        "_score" : 0.8630463,
        "_source" : {
          "name" : "顾老二",
          "age" : 30
        }
      }
    ]
  }
}

在数据量很大的时候,我们需要什么字段,就返回什么字段就好了,用来提高查询效率。

 

高亮查询

8.1 前言

如果返回的结果集中很多符合条件的结果,那怎么能一眼就能看到我们想要的那个结果呢?比如下面网站所示的那样,我们搜索elasticsearch,在结果集中,将所有elasticsearch高亮显示?

8.2 准备数据

PUT lqz/_doc/4
{
  "name":"石头",
  "age":29,
  "from":"gu",
  "desc":"粗中有细,狐假虎威",
  "tags":["粗", "大","猛"]
}

8.3 默认高亮显示

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

上例中,我们使用highlight属性来实现结果高亮显示,需要的字段名称添加到fields内即可,elasticsearch会自动帮我们实现高亮。

结果如下:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.5098256,
    "hits" : [
      {
        "_index" : "lqz",
        "_type" : "doc",
        "_id" : "4",
        "_score" : 1.5098256,
        "_source" : {
          "name" : "石头",
          "age" : 29,
          "from" : "gu",
          "desc" : "粗中有细,狐假虎威",
          "tags" : [
            "粗",
            "大",
            "猛"
          ]
        },
        "highlight" : {
          "name" : [
            "<em>石</em><em>头</em>"
          ]
        }
      }
    ]
  }
}

上例中,elasticsearch会自动将检索结果用标签包裹起来,用于在页面中渲染。

8.4 自定义高亮显示

但是,你可能会问,我不想用em标签, 我这么牛逼,应该用个b标签啊!好的,elasticsearch同样考虑到你很牛逼,所以,我们可以自定义标签。

GET lqz/_doc/_search
{
  "query": {
    "match": {
      "name": "老二"
    }
  },
  "highlight": {
    "pre_tags": "<b class='key' style='color:red'>",
    "post_tags": "</b>",
    "fields": {
      "name": {}
    }
  }
}

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

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 2.7725887,
    "hits" : [
      {
        "_index" : "lqz",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 2.7725887,
        "_source" : {
          "name" : "顾老二",
          "age" : 30,
          "from" : "gu",
          "desc" : "皮肤很黄,武器很长,性格很直",
          "tags" : [
            "很黄",
            "很长",
            "很直"
          ]
        },
        "highlight" : {
          "name" : [
            "顾<b class='key' style='color:red'>老</b><b class='key' style='color:red'>二</b>"
          ]
        }
      }
    ]
  }
}

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

 

聚合函数

9.1 前言

聚合函数大家都不陌生,elasticsearch中也没玩出新花样,所以,这一章相对简单,只需要记得:

  • avg

  • max

  • min

  • sum

以及各自的用法即可。先来看求平均。


9.2 avg

现在的需求是查询from中国的人的平均年龄。

# select avg(age) as my_avg

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

上例中,首先匹配查询from中国的数据。在此基础上做查询平均值的操作,这里就用到了聚合函数,其语法被封装在aggs中,而my_avg则是为查询结果起个别名,封装了计算出的平均值。那么,要以什么属性作为条件呢?是age年龄,查年龄的什么呢?是avg,查平均年龄。

返回结果如下:

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 1.077993,
    "hits" : [
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "1",
        "_score" : 1.077993,
        "_source" : {
          "name" : "刘亦菲",
          "age" : 30
        }
      },
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "2",
        "_score" : 1.077993,
        "_source" : {
          "name" : "高圆圆",
          "age" : 18
        }
      },
      {
        "_index" : "zl",
        "_type" : "doc",
        "_id" : "5",
        "_score" : 1.077993,
        "_source" : {
          "name" : "汤唯",
          "age" : 25
        }
      }
    ]
  },
  "aggregations" : {
    "my_avg" : {
      "value" : 24.333333333333332
    }
  }
}

上例中,在查询结果的最后是平均值信息,可以看到是24.3333岁。

虽然我们已经使用_source对字段做了过滤,但是还不够。我不想看都有哪些数据,只想看平均值怎么办?别忘了size!

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

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

查询结果如下:

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "my_avg" : {
      "value" : 24.333333333333332
    }
  }
}

查询结果中,我们看hits下的total值是3,说明有三条符合结果的数据。最后面返回平均值是24.3333。


9.3 max

那怎么查最大值呢?

GET zl/_doc/_search
{
  "query": {
    "match": {
      "from": "中国"
    }
  },
  "aggs": {
    "my_max": {
      "max": {
        "field": "age"
      }
    }
  },
  "size": 0
}

上例中,只需要在查询条件中将avg替换成max即可。


9.4 min

GET zl/_doc/_search
{
  "query": {
    "match": {
      "from": "中国"
    }
  },
  "aggs": {
    "my_min": {
      "min": {
        "field": "age"
      }
    }
  },
  "size": 0
}

9.5 sum

那么,要是想知道它们的年龄总和是多少怎么办呢?

GET zl/_doc/_search
{
  "query": {
    "match": {
      "from": "中国"
    }
  },
  "aggs": {
    "my_sum": {
      "sum": {
        "field": "age"
      }
    }
  },
  "size": 0
}

9.6 分组查询

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

分析需求,首先我们应该先把分组做出来。

GET zl/_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是范围,我们根据需求做出三组。

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 5,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "age_group" : {
      "buckets" : [
        {
          "key" : "15.0-20.0",
          "from" : 15.0,
          "to" : 20.0,
          "doc_count" : 1
        },
        {
          "key" : "20.0-25.0",
          "from" : 20.0,
          "to" : 25.0,
          "doc_count" : 1
        },
        {
          "key" : "25.0-30.0",
          "from" : 25.0,
          "to" : 30.0,
          "doc_count" : 2
        }
      ]
    }
  }
}

返回的结果中可以看到,已经拿到了三个分组。doc_count为该组内有几条数据,此次共分为三组,查询出4条内容。还有一条数据的age属性值是30,不在分组的范围内!那么接下来,我们就要对每个小组内的数据做平均年龄处理。

GET zl/_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 @ 2022-12-04 23:12  不会钓鱼的猫  阅读(45)  评论(0编辑  收藏  举报