1.倒排索引
1》mysql等数据库使用正向索引
如果根据id(索引列查询),速度会非常快,但是如果根据非索引列,并且模糊查询时,速度会非常慢,流程如下(比如id是索引列,title是要模糊查询的非索引列)
1)用户搜索数据,条件是title符合"%手机%"
2)逐行获取数据,比如id为1的数据
3)判断数据中的title是否符合用户搜索条件
4)如果符合则放入结果集,不符合则丢弃。回到步骤1
2》所以要用到倒排索引,倒排索引的搜索流程如下:
1)用户输入条件"华为手机"
进行搜索。
2)对用户输入内容分词,得到词条:华为
、手机
。
3)拿着词条在倒排索引中查找,可以发现id为1 2 3的记录,或者有手机,或者有华为,或者两个都有,
4)拿着文档id 1 2 3到正向索引中查找具体文档。
3》那么为什么一个叫做正向索引,一个叫做倒排索引呢?
-
正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程。
-
而倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程。
-
倒排索引虽然要先查询倒排索引,再查询正向索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。
2. ElasticSearch 和mysql 的对应关系
3.索引库dsl操作
3.1mapping映射属性
mapping是对索引库中文档的约束,常见的mapping属性包括:
-
type:字段数据类型,常见的简单类型有:
-
字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
-
数值:long、integer、short、byte、double、float、
-
布尔:boolean
-
日期:date
-
对象:object
-
-
index:是否创建索引,默认为true
-
analyzer:使用哪种分词器
-
properties:该字段的子字段
例如下面的json文档:
{ "age": 21, "weight": 52.1, "isMarried": false, "info": "黑马程序员Java讲师", "email": "zy@itcast.cn", "score": [99.1, 99.5, 98.9], "name": { "firstName": "云", "lastName": "赵" } }
对应的每个字段映射(mapping):
-
age:类型为 integer;参与搜索,因此需要index为true;无需分词器
-
weight:类型为float;参与搜索,因此需要index为true;无需分词器
-
isMarried:类型为boolean;参与搜索,因此需要index为true;无需分词器
-
info:类型为字符串,需要分词,因此是text;参与搜索,因此需要index为true;分词器可以用ik_smart
-
email:类型为字符串,但是不需要分词,因此是keyword;不参与搜索,因此需要index为false;无需分词器
-
score:虽然是数组,但是我们只看元素的类型,类型为float;参与搜索,因此需要index为true;无需分词器
-
name:类型为object,需要定义多个子属性
-
name.firstName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器
-
name.lastName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器
-
3.2dsl操作索引库
json格式,用于ElasticSearch的语句,进行增删改查操作
1》创建索引库(索引库相当于mysql中的表)
-
PUT /heima 索引库名
-
{
-
"mappings": {
-
"properties": {
-
"info":{ 文档名
-
"type":
"text", 类型
-
"analyzer":
"ik_smart" 使用的分词器
-
},
-
"email":{
-
"type":
"keyword",
-
"index":
"false" 是否使用倒排索引排序 ,电子邮箱,不会根据电子邮箱查询,所以是false,其他属性默认是true
-
},
-
"name":{
-
"properties": { 因为name中有姓和名两个属性,所以要写个properties
-
"firstName": {
-
"type":
"keyword"
-
}
-
}
-
},
-
}
-
}
-
}
2》查询索引库结构
GET /heima
3》修改索引库结构
倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping。
只能增加新的属性
-
PUT /heima/_mapping
-
{
-
"properties":{
-
"age":{
-
"type":
"integer"
-
}
-
}
-
}
4》删除索引库
DELETE /索引库名
4.文档dsl操作
1.添加一条文档(记录) 如果该文档已存在,则更新该文档
-
-
POST /heima/_doc/
1
-
{
-
"info":
"黑马程序员java讲师",
-
"email":
"1960703672@qq.com",
-
"name":{
-
"firstname":
"云",
-
"lastname":
"ZHAO"
-
}
-
}
2. 查询一条文档(记录)
GET /heima/_doc/1
3.删除一条文档(记录)
DELETE /heima/_doc/1
4.修改一条文档,如果不存在,就创建新的该条文档
-
POST /heima/_doc/
1
-
{
-
"info":
"黑马程序员java讲师",
-
"email":
"1960703672@qq.com",
-
"name":{
-
"firstname":
"云",
-
"lastname":
"ZHAO"
-
}
-
}
5.局部修改文档字段
-
POST
/heima
/_update
/
1
-
{
-
"doc"
:
{
-
"email"
:
"1195"
-
}
-
}
5.dsl创建索引练习及文档查询操作
-
location:地理坐标,里面包含精度、纬度
-
all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索,
会创建一个新的all字段,包含使用了all的其他字段(下面代码中的name,brand,city),并创建反向索引
-
PUT
/hotel
-
{
-
"mappings"
:
{
-
"properties"
:
{
-
"id"
:
{
-
"type"
:
"keyword"
-
}
,
-
"name"
:
{
-
"type"
:
"text"
,
-
"analyzer"
:
"ik_max_word"
,
-
"copy_to"
:
"all"
-
}
,
-
"address"
:
{
-
"type"
:
"keyword"
,
-
"index"
: false
-
}
,
-
"price"
:
{
-
"type"
:
"integer"
-
}
,
-
"score"
:
{
-
"type"
:
"integer"
-
}
,
-
"brand"
:
{
-
"type"
:
"keyword"
,
-
"copy_to"
:
"all"
-
}
,
-
"city"
:
{
-
"type"
:
"keyword"
,
-
"copy_to"
:
"all"
-
}
,
-
"starName"
:
{
-
"type"
:
"keyword"
-
}
,
-
"business"
:
{
-
"type"
:
"keyword"
-
}
,
-
"location"
:
{
-
"type"
:
"geo_point"
-
}
,
-
"pic"
:
{
-
"type"
:
"keyword"
,
-
"index"
: false
-
}
,
-
"all"
:
{
-
"type"
:
"text"
,
-
"analyzer"
:
"ik_max_word"
-
}
-
}
-
}
-
}
文档查询操作
1.查询所有文档
-
GET /hotel/_search
-
{
-
"query": {
-
"match_all": {}
-
}
-
}
2. 全文检索查询
模糊查询info中有java工程师的,模糊查询不太准确,因为是根据分词来倒排索引查询的,比如分成了java 和工程师,会去找eleaticsearch分词后的字典中工程师和java对应的id,所以比如查询参数的info是 java 哈哈哈哈 工程师,是可以查询出来的,但如果是java 工程大师,工程大师在eleaticsearch分词后的字典中没有(就是eleaticsearch的info中没有工程大师info的记录),就查询不出来。
-
GET /heima2/_search
-
{
-
"query": {
-
"match": {
"info":
"java工程师"}
-
}
-
}
在定义索引(表结构)时,定义了all字段,关联了 酒店名称 品牌 城市 三个字段
所以可以直接根据all字段查询,会查询酒店名称 或者品牌 或者城市中有上海的
-
GET /hotel/_search
-
{
-
"query": {
-
"match": {
-
"all":
"上海"
-
}
-
}
-
}
3.全文检索查询2
如果没有定义all字段,想查询酒店名称 或者品牌 或者城市中有上海的 ,就必须使用如下的查询方式,速度很慢,推荐在定义表时使用all字段,查询all字段。
-
GET /hotel/_search
-
{
-
"query": {
-
"multi_match": {
-
"query":
"杭州",
-
"fields": [
"brand",
"name",
"bussiness"]
-
}
-
}
-
}
4.term精确查询
查询城市是上海的 (比如有个上海市,则不能查询到)
-
GET /hotel/_search
-
{
-
"query": {
-
"term": {
-
"city": {
-
"value":
"上海"
-
}
-
}
-
}
-
}
5.范围查询 要查询的字段必须是数字类型,可以比较的。
-
GET /hotel/_search
-
{
-
"query": {
-
"range": {
-
"price": {
-
"gte":
1000,
-
"lte":
2000
-
}
-
}
-
}
-
}
6.地理位置查询1(不常用)画一个矩形,查询在该矩形范围内的所有地址
-
GET /hotel/_search
-
{
-
"query": {
-
"geo_bounding_box": {
-
"location": {
-
"top_left": {
-
"lat":
31.1,
-
"lon":
121.5
-
},
-
"bottom_right": {
-
"lat":
30.9,
-
"lon":
0.0
-
}
-
}
-
}
-
}
-
}
7.地理位置查询2 (很常用) 附近的人,查询距离某个点什么范围的记录
-
GET /hotel/_search
-
{
-
"query": {
-
"geo_distance": {
-
"distance":
"15km",
-
"location":
"31.21,121.5"
-
}
-
}
-
}
8.设置查询的优先级
在使用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。比如在搜索虹桥如家时:
-
[
-
{
-
"_score" :
17.850193,
-
"_source" : {
-
"name" :
"虹桥如家酒店真不错",
-
}
-
},
-
{
-
"_score" :
12.259849,
-
"_source" : {
-
"name" :
"外滩如家酒店真不错",
-
}
-
},
-
{
-
"_score" :
11.91091,
-
"_source" : {
-
"name" :
"迪士尼如家酒店真不错",
-
}
-
}
-
]
算法主要有两种:
而这个算分结果,是可以被干预的,比如,收了如家的广告费,想要如家的搜索结果提前
实现:
原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)
-
过滤条件:filter部分,符合该条件的文档才会重新算分
-
算分函数:符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数
-
weight:函数结果是常量
-
field_value_factor:以文档中的某个字段值作为函数结果
-
random_score:以随机数作为函数结果
-
script_score:自定义算分函数算法
-
-
运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
-
multiply:相乘
-
replace:用function score替换query score
-
其它,例如:sum、avg、max、min
-
-
GET /hotel/_search
-
{
-
"query": {
-
"function_score": {
-
"query": {
-
"match": {
-
"all":
"外滩"
-
}
-
},
-
"functions": [
-
{
-
"filter": {
-
"term": {
-
"brand":
"如家"
-
}
-
},
-
"weight":
10
-
}
-
]
-
}
-
}
-
}
查询结果:
9. 布尔查询
一个或多个查询子句的组合,每一个子句就是一个子查询。子查询的组合方式有:
-
must:必须匹配每个子查询,类似“与”
-
should:选择性匹配子查询,类似“或”
-
must_not:必须不匹配,不参与算分,类似“非”
-
filter:必须匹配,不参与算分
-
比如在搜索酒店时,除了关键字搜索外,我们还可能根据品牌、价格、城市等字段做过滤:
每一个不同的字段,其查询的条件、方式都不一样,必须是多个不同的查询,而要组合这些查询,就必须用bool查询了。
-
查询城市是上海的,品牌是皇冠假日或者华美达的,价格大于500的,评分大于45的酒店
-
-
#布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询
-
GET /hotel/_search
-
{
-
"query": {
-
"bool": {
-
"must": [
-
{
-
"term": {
-
"city": "上海"
-
}
-
}
-
],
-
"should": [
-
{ "term": { "brand": "皇冠假日"}},
-
{ "term":{ "brand": "华美达"}}
-
],
-
"must_not": [
-
{ "range": { "price": {
-
"lte": 500
-
}}}
-
],
-
"filter": [
-
{
-
"range": {
-
"score": {
-
"gte": 45
-
}
-
}
-
}
-
]
-
}
-
}
-
}
-
-
10.根据地理位置排序(查询距离某个点最近的)
-
-
GET /hotel/_search
-
{
-
"query": {
-
"match_all": {}
-
},
-
"sort": [
-
{
-
"_geo_distance": {
-
"location": {
-
"lat": 40.476483,
-
"lon": 115.97481
-
},
-
"order": "asc"
-
}
-
}
-
]
-
}
-
-
6.搜索结果处理
-
6.1手动排序
-
elasticsearch默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。
手动排序后,不会再打分,也就没有_score属性了。
-
-
GET /indexName/_search
-
{
-
"query": {
-
"match_all": {}
-
},
-
"sort": [
-
{
-
"FIELD": "desc" // 排序字段、排序方式ASC、DESC
-
}
-
]
-
}
6.2分页查询
-
-
elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:
-
from:从第几个文档开始
-
size:总共查询几个文档
-
-
GET /hotel/_search
-
{
-
"query": {
-
"match_all": {}
-
},
-
"from": 0, // 分页开始的位置,默认为0
-
"size": 10, // 期望获取的文档总数
-
}
6.3高亮
-
-
高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
-
默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
-
如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false
-
#fields指定高亮的标签,如果搜索字段和高亮字段(即city和name)不是一个,需要require_field_match设为false
-
GET /hotel/_search
-
{
-
"query": {
-
"match": {
-
"city": "上海"
-
}
-
},
-
"highlight": {
-
"fields": {
-
"name": {
-
"require_field_match": "false"
-
}
-
}
-
}
-
}
6.4聚合查询
-
-
统计所有酒店品牌拥有的酒店数,并升序排列
-
-
GET /hotel/_search
-
{
-
"size": 0, // 设置size为0,结果中不包含文档,只包含聚合结果
-
"aggs": { // 定义聚合
-
"brandAgg": { //给聚合起个名字
-
"terms": { // 聚合的类型,按照品牌值聚合,所以选择term
-
"field": "brand", // 参与聚合的字段
-
"size": 20, // 希望获取的聚合结果数量,比如获取出现次数前二十的,
-
"order": { //根据出现次数递增排序
-
"_count": "asc"
-
}
-
}
-
}
-
}
-
}
-
-
6.5嵌套聚合
- 统计每个品牌的,最高评分,最低评分,平均值
-
-
GET /hotel/_search
-
{
-
"size": 0,
-
"aggs": {
-
"brandAgg": {
-
"terms": {
-
"field": "brand",
-
"size": 20
-
},
-
"aggs": {
-
"scoreAgg": {
-
"stats": {
-
"field": "score"
-
}
-
}
-
}
-
}
-
}
-
}
-
转载:https://blog.csdn.net/sharesb/article/details/128302665