Hey, Nice to meet You. 

必有过人之节.人情有所不能忍者,匹夫见辱,拔剑而起,挺身而斗,此不足为勇也,天下有大勇者,猝然临之而不惊,无故加之而不怒.此其所挟持者甚大,而其志甚远也.          ☆☆☆所谓豪杰之士,

ElasticSearch的DSL高级查询操作

1、ES的两种查询方式

1、查询字符串搜索

GET /user/_search?q=name:张三

2、DSL查询

Elasticsearch提供丰富且灵活的查询语言叫做DSL查询(Query DSL),它允许你构建更加复杂、强大的查询。 DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现。

GET user/_search
{
  "query": {
    "match": {
      "name": "张三"
    }
  }
}

平时更多采用这种方式,因为可操作性更强,处理复杂请求时更得心应手。

2、精确查询(term)

term查询不会分析查询条件,只有当词条和查询字符串完全匹配时才匹配,也就是精确查找,比如数字,日期,布尔值或 not_analyzed 的字符串(未经分析的文本数据类型):

测试准备数据:

PUT /test/_doc/1
{
  "name":"张三",
  "age":18,
  "sex":"男",
  "address":"上海市浦东区"
}
PUT /test/_doc/2
{
  "name":"李四",
  "age":29,
  "sex":"男",
  "address":"广东省广州市"
}
PUT /test/_doc/3
{
  "name":"王五",
  "age":30,
  "sex":"男",
  "address":"广东省深圳市"
}

示例:查询年龄为29岁的用户信息

POST /test/_search
{
  "query": {
    "term": {
      "age": 29
    }
  }
}

image


terms查询:terms 跟 term 有点类似,但 terms 允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去 做匹配:

示例:查询年龄为29或者30岁的用户信息

POST /test/_search
{
  "query": {
    "terms": {
      "age": [
        29,
        30
      ]
    }
  }
}

image

3、全文查询(match)

全文查询会分析查询条件,先将查询条件进行分词,然后查询,求并集。

term和match的区别是:match是经过analyer的,也就是说,文档首先被分析器给处理了。根据不同的分析器,分析的结果也稍显不同,然后再根据分词结果进行匹配。term则不经过分词,它是直接去倒排索引中查找了精确的值了。

match 查询语法汇总:

  1. match_all:查询全部。
  2. match:返回所有匹配的分词。
  3. match_phrase:短语查询,在match的基础上进一步查询词组,可以指定slop分词间隔。
  4. match_phrase_prefix:前缀查询,根据短语中最后一个词组做前缀匹配,可以应用于搜索提示,但注意和max_expanions搭配。其实默认是50.......
  5. multi_match:多字段查询,使用相当的灵活,可以完成match_phrase和match_phrase_prefix的工作。

测试准备数据:

PUT /test1/_doc/1
{
  "name":"Jack",
  "age":18,
  "sex":"男",
  "address":"美国纽约州",
  "remark":"美国是世界上军事实力最强大的国家"
}
PUT /test1/_doc/2
{
  "name":"李四",
  "age":29,
  "sex":"男",
  "address":"中国广东省广州市",
  "remark":"中国是世界上人口最多的国家"
}
PUT /test1/_doc/3
{
  "name":"阿三",
  "age":30,
  "sex":"男",
  "address":"印度新德里",
  "remark":"印度的食品干净又卫生"
}
PUT /test1/_doc/4
{
  "name":"王五",
  "age":30,
  "sex":"男",
  "address":"中国北京市三里屯",
  "remark":"中国的首都是北京市"
}

1、match系列之match_all (查询全部)

GET /test1/_search
{
  "query": {
    "match_all": {}
  }
}

这个没啥好说的。


2、match系列之match查询(单字段条件查询)

POST /test1/_search
{
  "query": {
    "match": {
      "address": "中国"
    }
  }
}

image

从结果我们可以看到,我查询的条件是中国,虽然如期的返回了中国的文档,但是为什么含有美国的地址信息也被查询出来了,这并不是我们想要的,是怎么回事呢?因为这是elasticsearch在内部对文档做分词的时候,对于中文来说,就是一个字一个字分的,例如:'I Love You',此时会被分为三个词分别是:I、Love、You,而中文也是一样的,例如:‘我爱中国’,会被分词为:我、爱、中、国。所以我们的查询条件也会被分为:中和国,所以中和国都符合条件,当去查询文档的时候,此时美国中的 国 字也符合,所以匹配上了并且将结果返回了。

而我们认为中国是个短语,是一个有具体含义的词。所以elasticsearch在处理中文分词方面比较弱势。后面会讲针对中文的分词插件。但目前我们还有办法解决,那就是使用短语查询 用match_phrase来处理。


3、match系列之match_phrase(短语查询)

GET /test1/_search
{
  "query": {
    "match_phrase": {
      "address": {
        "query": "中国"
      }
    }
  }
}

image

此时可以发现返回的结果中只包含了中国的文档数据。


4、match系列之match_phrase_prefix(最左前缀查询)智能搜索--以什么开头

示例:查询地址以 印度 开头的文档信息

GET /test1/_search
{
  "query": {
    "match_phrase_prefix": {
      "remark": "印度"
    }
  }
}

image


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

multi_match是要在多个字段中查询同一个关键字 除此之外,mulit_match甚至可以当做match_phrase和match_phrase_prefix使用,只需要指定type类型即可

GET /test1/_search
{
  "query": {
    "multi_match": {
      "query": "中国",
      "fields": ["address","remark"]
    }
  }
}

image

加上phrase属性,当设置属性 type:phrase 时 等同于 短语查询。

GET /test1/_search
{
  "query": {
    "multi_match": {
      "query": "中国",
      "fields": ["address","remark"],
      "type": "phrase"
    }
  }
}

加上phrase_prefix属性,当设置属性 type:phrase_prefix时 等同于 最左前缀查询。

GET /test1/_search
{
  "query": {
    "multi_match": {
      "query": "中国",
      "fields": ["address","remark"],
      "type": "phrase_prefix"
    }
  }
}

4、通配符查询(wildcard)

wildcard查询:会对查询条件进行分词。还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符)

# wildcard 查询。查询条件分词,模糊查询
GET /test1/_search
{
  "query": {
    "wildcard": {
      "name": {
        "value": "李*"
      }
    }
  }
}

image

prefix查询:前缀查询

# 前缀查询
GET /test1/_search
{
  "query": {
    "wildcard": {
      "name": {
        "value": "三"
      }
    }
  }
}

image

5、排序查询(sort)

注意:需要分词的字段不可以直接排序,比如:text类型,如果想要对这类字段进行排序,需要特别设置:对字段索引两次,一次索引分词(用于搜索)一次索引不分词(用于排序),es默认生成的text类型字段就是通过这样的方法实现可排序的。

GET /test1/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ]
}

6、分页查询

Elasticsearchde 的分页查询和 SQL 使用 LIMIT 关键字返回只有一页的结果一样,Elasticsearch 接受 from 和 size 参数:

  • size: 结果数,默认10
  • from: 跳过开始的结果数,即从哪一行开始获取数据,默认0

示例:从第一页开始获取两条数据

GET /test1/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 2
}

image

示例:分页查询用户名为Jack的用户信息

GET /test1/_search
{
  "query": {
    "match_phrase_prefix": {
      "name": "Jack"
    }
  },
  "from": 0,
  "size": 2
}

7、范围查询(range)

range 过滤允许我们按照指定范围查找一批数据,范围操作符包含:

  • gt:大于,相当于关系型数据库中的 >。
  • gte:大于等于,相当于关系型数据库中的 >=。
  • lt:小于,相当于关系型数据库中的 <。
  • lte:小于等于,相当于关系型数据库中的 <=。

示例:查询年龄在20-30岁之间的用户信息

POST /test1/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 20,
        "lte": 30
      }
    }
  }
}

image

8、布尔查询(bool)

bool 查询可以用来合并多个条件查询结果的布尔逻辑,它包含一下操作符:

  • must:多个查询条件必须完全匹配,相当于关系型数据库中的 and。
  • should:至少有一个查询条件匹配,相当于关系型数据库中的 or。
  • must_not: 多个查询条件的相反匹配,相当于关系型数据库中的 not。
  • filter:过滤满足条件的数据。
    • range:条件筛选范围。
      • gt:大于,相当于关系型数据库中的 >。
      • gte:大于等于,相当于关系型数据库中的 >=。
      • lt:小于,相当于关系型数据库中的 <。
      • lte:小于等于,相当于关系型数据库中的 <=。

1、must:多个查询条件必须完全匹配,相当于关系型数据库中的 and。如果有一个条件不满足则不返回数据。

实例:查询用户名称满足叫 李四 的用户信息

GET /test1/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "李四"
          }
        }
      ]
    }
  }
}

image

示例:查询用户名称满足叫 李四 并且年龄为29 的用户信息

GET /test1/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "李四"
          }
        },
        {
          "match": {
            "age": 29
          }
        }
      ]
    }
  }
}

image


2、should:至少有一个查询条件匹配,相当于关系型数据库中的 or。只要符合其中一个条件就返回

查询用户名称叫 李四 或者 年龄为27岁的用户信息

GET /test1/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "name": "李四"
          }
        },
        {
          "match": {
            "age": 27
          }
        }
      ]
    }
  }
}

image


3、must_not:多个查询条件的相反匹配,相当于关系型数据库中的 not。

示例:查询名称不包含李四并且年龄不包含30岁的用户信息

GET /test1/_search
{
  "query": {
    "bool": {
      "must_not": [
        {
          "match": {
            "name": "李四"
          }
        },
        {
          "match": {
            "age": 30
          }
        }
      ]
    }
  }
}

image


4、filter:条件过滤查询,过滤满足条件的数据。过滤条件的范围用range表示gt表示大于、lt表示小于、gte表示大于等于、lte表示小于等于

示例:查询出用户年龄在20-30岁之间名称为 李四 的用户

GET /test1/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "李四"
          }
        }
      ],
      "filter": {
        "range": {
          "age": {
            "gte": 20,
            "lt": 30
          }
        }
      }
    }
  }
}

image

9、查询结果过滤

我们在查询数据的时候,返回的结果中,所有字段都给我们返回了,但是有时候我们并不需要那么多,所以可以对结果进行过滤处理。

示例:只查看name和age两个属性的返回值,其他不需要。

GET /test1/_search
{
  "query": {
    "match_all": {}
  },
  "_source": [
    "name",
    "age"
  ]
}

image

10、高亮查询

我们平时在使用百度的时候,输入关键字查询内容后,关键字一般都是高亮显示的。

image

所以ES作为一个专业的搜索框架肯定也提供了这样的功能。

ES的默认高亮显示:

GET /test1/_search
{
  "query": {
    "match": {
      "name": "李四"
    }
  },
  "highlight": {
    "fields": {
      "name": {}
    }
  }
}

image

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

GET /test1/_search
{
  "query": {
    "match": {
      "remark": "中国"
    }
  },
  "highlight": {
    "pre_tags": "<b class='key' style='color:red'>",
    "post_tags": "</b>",
    "fields": {
      "remark": {}
    }
  }
}

# 或者
GET /test1/_search
{
  "query": {
    "match": {
      "remark": "中国"
    }
  },
  "highlight": {
    "fields": {
      "remark": {
        "pre_tags": "<b class='key' style='color:red'>",
        "post_tags": "</b>"
      }
    }
  }
}

image

11、聚合查询

我们平时在使用Elasticsearch时,更多会用到聚合操作,它类似SQL中的group by操作。ES的聚合查询一定是先查出结果,然后对结果使用聚合函数做处理,常用的操作有:avg:求平均、max:最大值、min:最小值、sum:求和等。

在ES中聚合分为指标聚合和分桶聚合:

  • 指标聚合:指标聚合对一个数据集求最大、最小、和、平均值
  • 分桶聚合:除了有聚合函数外,还可以对查询出的数据进行分组group by,再在组上进行游标聚合。

测试准备数据:

PUT /test2/_doc/1
{
  "name":"Jack",
  "age":18,
  "sex":"男",
  "salary":6000,
  "bithday":"1997-07-18",
  "address":"美国纽约州",
  "remark":"美国是世界上军事实力最强大的国家"
}
PUT /test2/_doc/2
{
  "name":"李四",
  "age":29,
  "sex":"男",
  "salary":12000,
  "bithday":"1997-02-07",
  "address":"中国广东省广州市",
  "remark":"中国是世界上人口最多的国家"
}
PUT /test2/_doc/3
{
  "name":"阿三",
  "age":30,
  "sex":"男",
  "salary":3000,
  "bithday":"1995-04-09",
  "address":"印度新德里",
  "remark":"印度的食品干净又卫生"
}
PUT /test2/_doc/4
{
  "name":"王五",
  "age":30,
  "sex":"男",
  "salary":16000,
  "bithday":"1997-07-07",
  "address":"中国北京市三里屯",
  "remark":"中国的首都是北京市"
}
PUT /test2/_doc/5
{
  "name":"老六",
  "age":29,
  "sex":"男",
  "salary":15000,
  "bithday":"1987-02-02",
  "address":"中国上海市浦东新区",
  "remark":"老六是中国某不知名小厂的程序员一枚"
}
PUT /test2/_doc/6
{
  "name":"七七",
  "age":20,
  "sex":"女",
  "salary":15000,
  "bithday":"1999-11-17",
  "address":"中国上海市浦东新区",
  "remark":"七七是一个漂亮的Web前端程序猿"
}

11.1、Metric 指标聚合分析

指标聚合:指标聚合对一个数据集求最大、最小、和、平均值

  • 单值分析,只输出一个分析结果——avg:求平均、max:最大值、min:最小值、sum:求和、cardinality:值去重计数。
  • 多值分析,输出多个分析结果
    • stats:统计了count max min avg sum5个值
    • extended_stats:extended_stats比stats多很多更加高级的统计结果:如平方和、方差、标准差、平均值加/减两个标准差的区间等
    • percentile:占比百分位对应的值统计,默认返回【1,5,25,50,75,95,99】分位上的值

avg示例:查询所有用户的平均年龄

GET /test2/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "avg_age": {
      "avg": {
        "field": "age"
      }
    }
  },
  "_source": [
    "name",
    "age"
  ]
}

上例中,首先匹配查询所有的数据。在此基础上做查询平均值的操作,这里就用到了聚合函数,其语法被封装在aggs中,而avg_age则是为查询结果起个别名,封装了计算出的平均值。

image

如果只想看输出的值,而不关心输出的文档的话可以通过size=0来控制

GET /test2/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "avg_age": {
      "avg": {
        "field": "age"
      }
    }
  },
  "size": 0,
  "_source": [
    "name",
    "age"
  ]
}

image


avg示例:查询地址在中国用户的平均工资

GET /test2/_search
{
  "query": {
    "match_phrase": {
      "address": "中国"
    }
  },
  "aggs": {
    "avg_salary": {
      "avg": {
        "field": "salary"
      }
    }
  },
  "size": 0
}

image


max示例:查询年龄的最大值

GET /test2/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "max_age": {
      "max": {
        "field": "age"
      }
    }
  },
  "size": 0
}

image


min示例:查询年龄的最小值

GET /test2/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "age_min": {
      "min": {
        "field": "age"
      }
    }
  },
  "size": 0
}

image


sum示例:查询符合条件的年龄之和

GET /test2/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "age_sum": {
      "sum": {
        "field": "age"
      }
    }
  },
  "size": 0
}

image


示例:查询所有用户不同年龄的数量

POST /test2/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "count_age": {
      "cardinality": {
        "field": "age"
      }
    }
  },
  "size": 0
}

示例:查出所有用户的年龄stats信息

POST /test2/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "stats_age": {
      "stats": {
        "field": "age"
      }
    }
  },
  "size": 0
}

image


示例:查出所有用户的年龄extended_stats信息

POST /test2/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "extended_stats_age": {
      "extended_stats": {
        "field": "age"
      }
    }
  },
  "size": 0
}

image


示例:查出所有用户的年龄占比

POST /test2/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "pecent_age": {
      "percentiles": {
        "field": "age"
      }
    }
  },
  "size": 0
}

11.2、Bucket 分桶聚合分析

分桶聚合:除了有聚合函数外,还可以对查询出的数据进行分组group by,再在组上进行游标聚合。

分桶group by示例:根据年龄聚合查询

GET /test2/_search
{
  "size": 0,
  "query": {
    "match_all": {}
  },
  "aggs": {
    "age_group": {
      "terms": {
        "field": "age"
      }
    }
  }
}

image


分桶group by示例:根据年龄段聚合查询

GET /test2/_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
          }
        ]
      }
    }
  }
}

image


分桶group by示例:根据用户出生日期的年月分组分段聚合查询

POST /test2/_search
{
  "size": 0,
  "query": {
    "match_all": {}
  },
  "aggs": {
    "bithday_range": {
      "date_range": {
        "field": "bithday",
        "format": "yyy-MM",
        "ranges": [
          {
            "to": "1989-01"
          },
          {
            "from": "1989-01",
            "to": "1999-01"
          },
          {
            "from": "1999-01",
            "to": "2005-01"
          },
          {
            "from": "2005-01"
          }
        ]
      }
    }
  }
}

image


分桶group by 进阶示例:按照年龄聚合,并且查询出这些年龄段的这些人的平均薪资

#其实就是aggs里面又加了一个aggs,第二个aggs根据第一个aggs聚合后的结果在聚合
GET /test2/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age",
        "size": 100
      },
      "aggs": {
        "ageAvg": {
          "avg": {
            "field": "salary"
          }
        }
      }
    }
  },
  "size": 0
}

image


分桶group by 进阶示例:查出所有年龄分布,并且这些年龄段中 性别为男 的平均薪资和 性别为女 的平均薪资以及这个年龄的用户信息

GET /test2/_search
{
  "query": {
    "match_all": {}
  },
  "size": 0,
  "aggs": {
    "age_agg": {
      "terms": {
        "field": "age",
        "size": 1000
      },
      "aggs": {
        "sex_agg": {
          "terms": {
            "field": "sex.keyword",
            "size": 10
          },
          "aggs": {
            "salary_avg": {
              "avg": {
                "field": "salary"
              }
            }
          }
        },
        "salary_avg": {
          "avg": {
            "field": "salary"
          }
        }
      }
    }
  }
}

image

12、queryString查询

会对查询条件进行分词, 然后将分词后的查询条件和词条进行等值匹配,默认取并集(OR),可以指定单个字段也可多个查询字段

POST /test2/_search
{
  "query": {
    "query_string": {
      "default_field": "name",
      "query": "张三 OR 七七"
    }
  },
  "size": 100
}
GET /test2/_search
{
  "query": {
    "query_string": {
      "fields": ["name","address"],
      "query": "张三 or 中国"
    }
  }
}

13、重建索引

随着业务需求的变更,索引的结构可能发生改变。ElasticSearch的索引一旦创建,只允许添加字段,不允许改变字段。因为改变字段,需要重建倒排索引,影响内部缓存结构,性能太低。那么此时,就需要重建一个新的索引,并将原有索引的数据导入到新索引中。

  1. 原索引库 :test_index_v1
  2. 新索引库 :test_index_v2
# 新建test_index_v1索引,索引名称必须全部小写
PUT test_index_v1 
{
  "mappings": {
    "properties": {
      "birthday":{
        "type": "date"
      }
    }
  }
}
# 查询索引
GET test_index_v1
# 添加数据
PUT test_index_v1/_doc/1
{
  "birthday":"2020-11-11"
}
# 查询数据
GET test_index_v1/_search
# 随着业务的变更,换种数据类型进行添加数据,程序会直接报错
PUT test_index_v1/_doc/1
{
  "birthday":"2020年11月11号"
}
# 业务变更,需要改变birthday数据类型为text
# 1:创建新的索引 test_index_v2
# 2:将test_index_v1 数据拷贝到 test_index_v2

# 创建新的索引
PUT test_index_v2 
{
  "mappings": {
    "properties": {
      "birthday":{
        "type": "text"
      }
    }
  }
}

# 2:将test_index_v1 数据拷贝到 test_index_v2
POST _reindex
{
  "source": {
    "index": "test_index_v1"
  },
  "dest": {
    "index": "test_index_v2"
  }
}

# 查询新索引库数据
GET test_index_v2/_search

# 在新的索引库里面添加数据
PUT test_index_v2/_doc/2
{
  "birthday":"2020年11月13号"
}

DELETE test_index_v1

14、别名的使用

1、查询别名

#获取指定索引的别名
GET /test2/_alias
#获取ES中所有索引的别名
GET /_alias

2、新增别名

POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "test2",
        "alias": "test2_alias_1.0"
      }
    }
  ]
}

3、删除别名

#方式一
POST /_aliases
{
  "actions": [
    {
      "remove": {
        "index": "test2",
        "alias": "test2_alias_1.0"
      }
    }
  ]
}

#方式二
DELETE /test2/_alias/test2_alias_1.0

4、重命名别名

POST /_aliases
{
  "actions": [
    {
      "remove": {
        "index": "test2",
        "alias": "test2_alias_1.0"
      }
    },
    {
      "add": {
        "index": "test2",
        "alias": "test2_alias_1.0"
      }
    }
  ]
}

5、为多个索引指定一个别名

POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "test2",
        "alias": "test2_alias_2.0"
      }
    },{
      "add": {
        "index": "test1",
        "alias": "test1_alias_2.0"
      }
    }
  ]
}

6、为同个索引指定多个别名

POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "test2",
        "alias": "test2_alias_2.1"
      }
    },{
      "add": {
        "index": "test2",
        "alias": "test2_alias_2.2"
      }
    }
  ]
}

7、通过别名读索引

GET /test2_alias_2.1

8、通过别名写索引

POST /test2_alias_2.1.0/_doc/
{
  "name": "Tom",
  "age": "26"
}


参考链接:

posted @ 2022-06-12 21:55  唐浩荣  阅读(2532)  评论(0编辑  收藏  举报