ElasticSearch-Advanced
ElasticSearch2
索引操作
- 创建索引
PUT /hotel
PUT /hotel
{
"settings":{
"number_of_shards":1,
"number_of_replicas":1
},
"mappings":{
"properties":{
"name":{
"type":"text"
},
"age":{
"type":"integer"
}
}
}
}
- 删除索引
PUT /hotel
- 索引打开
POST /hotel/_open
- 索引关闭
POST /hotel/_close
- 插入
POST /hotel/_doc/001
{
"title":"酒店",
"city":"深圳",
"price":9999
}
- 别名
POST /_aliases
{
"actions" : [
{
"add":{
"index":"hotel",
"alias":"guesthouse"
}
}
]
}
# 使用别名查询,验证:
POST /guesthouse/_search
{
"query":{
"match":{
"title":"酒店"
}
}
}
当原索引需要改变创建新的索引的时候,对外部的接口需要保持不变.我们可以创建一个别名,对外保持别名不变,只需要修改底层索引即可.
比如我原来的hotel索引需要修改主片为2,我创建了新的索引hotelv2,操作如下:
POST /_aliases
{
"actions" : [
{
"remove":{
"index":"hotel",
"alias":"guesthouse"
}
},
{
"add":{
"index":"hotel",
"alias":"guesthouse"
}
}
]
}
验证:
GET /guesthouse/_search
{
"query":{
"match_all": {}
}
}
映射操作
常用操作
- 查看索引映射
GET /guesthouse/_mapping
- 扩展索引
POST /guesthouse/_mapping
{
"properties":{
"address":{
"type":"text"
}
}
}
基本类型
- keyword
不分词,作为整体.支持聚合,支持排序,支持通配符,大小写敏感.
# 创建新索引
PUT /user
{
"mappings":{
"properties":{
"name":{
"type":"keyword"
}
}
}
}
# 写入数据
POST /user/_doc/001
{
"name":"大胖熊"
}
# 查询
# 可以查询到
GET /user/_search
{
"query":{
"term":{
"name":"大胖熊"
}
}
}
# 不能查询到,因为keyword不分词,所以找不到"胖熊"
GET /user/_search
{
"query":{
"match":{
"name":"胖熊"
}
}
}
- text
会分词,全文搜索.不支持聚合,排序.部分支持通配符.
# 扩展索引
PUT /user/_mappings
{
"properties":{
"title":{
"type":"text"
},
"price":{
"type":"float"
}
}
}
# 插入数据
POST /user/_doc/002
{
"name":"小胖熊",
"title":"a little good",
"price":9999
}
# 使用match搜索
# 会分词查询,可以匹配到
GET /user/_search
{
"query":{
"match":{
"title":"little"
}
}
}
# term搜索
# term查询对text类型不会匹配完整的字符串,先分词再索引.
GET /user/_search
{
"query":{
"term":{
"title":"a little good"
}
}
}
一般keyword使用term进行整体精确匹配,而text则更多使用match进行匹配
- 数值类型
ES支持很多数值类型,包括long,integer,short,byte,double,float,loat,half_float,scaled_float和unsigned_long等.
PUT /apartment
{
"mappings":{
"properties":{
"title":{
"type":"text"
},
"city":{
"type":"keyword"
},
"year":{
"type":"integer"
}
}
}
}
POST /apartment/_doc/001
{
"title":"a good big bear".
"city":"深圳".
"year":"1998"
}
POST /apartment/_doc/002
{
"title":"a good small dog".
"city":"北京".
"year":"2004"
}
POST /apartment/_doc/003
{
"title":"good bad mixed".
"city":"上海".
"year":"2023"
}
# 范围查询
GET /apartment/_search
{
"query":{
"range":{
"year":{
"gte":"1997",
"lte":"2023"
}
}
}
}
- 布尔类型
# 创建索引
PUT /player
{
"mappings":{
"properties":{
"name":{
"type":"text"
},
"age_over_tweenty":{
"type":"boolean"
}
}
}
}
# 写入数据
POST /player/_doc/001
{
"name":"john",
"age_over_tweenty":"true"
}
POST /player/_doc/002
{
"name":"bob",
"age_over_tweenty":"false"
}
# 查询
GET /player/_search
{
"query":{
"match":{
"age_over_tweenty":"true"
}
}
}
- 日期类型
ES存储的是标准的UTC格式
PUT /student
{
"mappings":{
"properties":{
"name":{
"type":"text"
},
"birthday":{
"type":"date"
}
}
}
}
POST /student/_doc/001
{
"name":"张三",
"birthday":"2005-02-11T22:15:45"
}
POST /student/_doc/002
{
"name":"李四",
"birthday":"2003-11-08T11:56:01"
}
# 检索数据
GET /student/_search
{
"query":{
"range":{
"birthday":{
"gte":"2001-01-01",
"lte":"2028-10-10"
}
}
}
}
自定义格式
PUT /student_v2
{
"mappings":{
"properties":{
"name":{
"type":"text"
},
"birthday":{
"type":"date",
"format":"yyyy-MM-dd HH:mm:ss" // 自定义格式
}
}
}
}
- 数组类型
动态映射
PUT /type_v1
{
"mappings":{
"properties":{
"name":{
"type":"text"
}
}
}
}
POST /type_v1/_doc
{
"name":"Simon",
"hobbies":["篮球","听音乐","看定影","狼人杀"] // 动态映射,虽然没有定义可以自动定义
}
GET /type_v1/_search
{
"query":{
"match_all":{}
}
}
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "type_v1",
"_id": "LWQTl54BOv1vaXU066-h",
"_score": 1,
"_source": {
"name": "Simon",
"hobbies": [
"篮球",
"听音乐",
"看定影",
"狼人杀"
]
}
}
]
}
}
# 匹配数组(字段类型必须是keyword)其一:使用match而非term
GET /type_v1/_search
{
"query":{
"match":{
"value":"篮球"
}
}
}
手动创建
PUT /type_v2
{
"mappings": {
"properties": {
"name":{
"type":"text"
},
"hobbies":{
"type":"keyword"
},
"year":{
"type":"date",
"format":"yy-MM-dd HH:mm:ss"
}
}
}
}
POST /type_v2/_doc
{
"name":"Simon",
"hobbies":["螃蟹","篮球","跳"],
"year":"23-12-11 22:11:20"
}
GET /type_v2/_search
{
"query":{
"match":{
"hobbies":"篮球"
}
}
}
{
"took": 10,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0.39556286,
"hits": [
{
"_index": "type_v2",
"_id": "HVKHmp4Bg2H-pnawx8_s",
"_score": 0.39556286,
"_source": {
"name": "Simon",
"hobbies": [
"螃蟹",
"篮球",
"跳"
],
"year": "23-12-11 22:11:20"
}
}
]
}
}
- 对象类型
动态映射
# 创建索引restaurant
PUT /restaurant
{
"mappings": {
"properties": {
"name":{
"type":"text"
},
"food_type":{
"type":"text"
},
"address":{
"type":"text"
}
}
}
}
POST /restaurant/_doc
{
"name":"东北默默",
"food_type":"东北菜",
"address":"深圳南山区",
"comment_info":{
"properties":{
"good":10,
"bad":2,
"ok":false,
"how":"not bad"
}
}
}
{
"_index": "restaurant",
"_id": "PlKMmp4Bg2H-pnaw3M-D",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
# 查询
GET /restaurant/_search
{
"query":{
"range":{
"comment_info.properties.good": {
"gte": 2,
"lte": 20
}
}
}
}
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "restaurant",
"_id": "PlKMmp4Bg2H-pnaw3M-D",
"_score": 1,
"_source": {
"name": "东北默默",
"food_type": "东北菜",
"address": "深圳南山区",
"comment_info": {
"properties": {
"good": 10,
"bad": 2,
"ok": false,
"how": "not bad"
}
}
}
}
]
}
}
# 对象内部也可以继续包含对象
POST /restaurant/_doc/003
{
"name":"陶居居",
"food type":"粤菜",
"adress":"深圳南山区",
"comment_info":{
"good":50,
"bad":6,
"ok":false,
"how":"not bad",
"top3_good":{
"top1":{
"content":"就餐环境很不错,干净清爽",
"score":92
},
"top2":{
"content":"很多粤菜很好吃,口感细腻",
"score":95
},
"top3":{
"content":"服务员很专业,上菜速度很快",
"score":86
}
}
}
}
# 查询
GET /restaurant/_search
{
"query":{
"multi_match":{
"query":"就餐环境很不错,干净清爽",
"fields":[
"comment_info.top3_good.top1.content",
"comment_info.top3_good.top2.content"
]
}
}
}
{
"took": 4,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 3.1645029,
"hits": [
{
"_index": "restaurant",
"_id": "003",
"_score": 3.1645029,
"_source": {
"name": "陶居居",
"food type": "粤菜",
"adress": "深圳南山区",
"comment_info": {
"good": 50,
"bad": 6,
"ok": false,
"how": "not bad",
"top3_good": {
"top1": {
"content": "就餐环境很不错,干净清爽",
"score": 92
},
"top2": {
"content": "很多粤菜很好吃,口感细腻",
"score": 95
},
"top3": {
"content": "服务员很专业,上菜速度很快",
"score": 86
}
}
}
}
}
]
}
}
- 地理类型
geo_shape类型
# 创建索引
PUT /location
{
"mappings":{
"properties":{
"city":{
"type":"text"
},
"location":{
"type":"geo_shape"
}
}
}
}
# 写入数据
POST /location/_doc/001
{
"city":"深圳",
"location":{
"type":"point",
"coordinates":[18.400544, 52.530286]
}
}
# 检索数据
GET /location/_searchGET /location/_search
{
"query":{
"bool":{
"must":{
"match_all":{}
},
"filter":{
"geo_shape":{
"location":{
"shape":{
"type":"envelope", // 包围,下面是左上角和右下角两个顶点
"coordinates": [[17.0,55.0],
[53.0,10.0]]
},
"relation":"within"
}
}
}
}
}
}
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "location",
"_id": "001",
"_score": 1,
"_source": {
"city": "深圳",
"location": {
"type": "point",
"coordinates": [
18.400544,
52.530286
]
}
}
}
]
}
}
geo_point类型
PUT /location_v2
{
"mappings":{
"properties":{
"name":{
"type":"text"
},
"location":{
"type":"geo_point"
}
}
}
}
POST /location_v2/_doc/001
{
"name":"希尔顿酒店",
"location":{
"lat":40.123192, //维度
"lon":-71.341213 // 经度
}
}
// 情形一: geo_bouding_box 搜索指定的两个地理位置形成的矩形范围中包含的酒店信息
GET /location_v2/_search
{
"query":{
"bool":{
"must":[
{"match_all":{}}
],
"filter":{
"geo_bounding_box": {
"location": {
"top_left": {
"lat": 40.73,
"lon": -74.1
},
"bottom_right": {
"lat": 40.01,
"lon": -60.01
}
}
}
}
}
}
}
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "location_v2",
"_id": "001",
"_score": 1,
"_source": {
"name": "希尔顿酒店",
"location": {
"lat": 40.123192,
"lon": -71.341213
}
}
}
]
}
}
// 情形二:geo_polygon 搜索指定的多个地理位置形成的多边形范围中包含的酒店信息:逆时针(CCW)或顺时针(CW)顺序 依次连接形成闭合多边形
GET /location_v2/_search
{
"query": {
"bool": {
"filter": {
"geo_polygon": {
"location": {
"points": [
{ "lat": 42.0, "lon": -71.0 }, // 东北
{ "lat": 39.5, "lon": -71.0 }, // 东南
{ "lat": 39.5, "lon": -75.5 }, // 西南
{ "lat": 42.0, "lon": -75.5 } // 西北
]
}
}
}
}
}
}
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0,
"hits": [
{
"_index": "location_v2",
"_id": "001",
"_score": 0,
"_source": {
"name": "希尔顿酒店",
"location": {
"lat": 40.123192,
"lon": -71.341213
}
}
}
]
}
}
# 情形三:搜索指定位置10km范围内的酒店数据
GET /location_v2/_search
{
"query":{
"bool":{
"must":[
{"match_all":{}}
],
"filter":{
"geo_distance":{
"distance":"300km",
"location":{
"lat": 40.73,
"lon": -74.1
}
}
}
}
}
}
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "location_v2",
"_id": "001",
"_score": 1,
"_source": {
"name": "希尔顿酒店",
"location": {
"lat": 40.123192,
"lon": -71.341213
}
}
}
]
}
}
# 情形四:搜索距离指定位置一定范围内有多少个酒店
# origin : 指定位置
# ranges : 范围
# unit : 范围使用单位
GET /location_v2/_search
{
"size":0,
"aggs":{
"count_by_distinct":{
"geo_distance":{
"field":"location",
"origin":{
"lat":40,
"lon":70
},
"ranges":[
{"to":100},
{"from":100, "to":300},
{"from":300}
],
"unit":"km",
"distance_type": "arc" // arc:最高经度,plane:最高效率,sloppy_arc:默认算法
}
}
}
}
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"count_by_distinct": {
"buckets": [
{
"key": "*-100.0",
"from": 0,
"to": 100,
"doc_count": 0
},
{
"key": "100.0-300.0",
"from": 100,
"to": 300,
"doc_count": 0
},
{
"key": "300.0-*",
"from": 300,
"doc_count": 1
}
]
}
}
}
- nested类型
nested 是一种特殊的字段数据类型,用于处理对象数组中每个元素需要被独立索引和查询的场景
PUT /nested_case
{
"mappings":{
"properties":{
"people":{
"type":"nested",
"properties":{
"name":{
"type":"keyword"
},
"age":{
"type":"integer"
}
}
}
}
}
}
PUT /nested_case/_doc/1
{
"people":[
{
"name":"tommy",
"age":25
},
{
"name":"ttt",
"age":30
}
]
}
GET /nested_case/_search
{
"query":{
"nested":{
"path":"people",
"query":{
"bool":{
"must":[
{"match":{"people.name":"tommy"}},
{"match":{"people.age":25}}
]
}
}
}
}
}
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.6931472,
"hits": [
{
"_index": "nested_case",
"_id": "1",
"_score": 1.6931472,
"_source": {
"people": [
{
"name": "tommy",
"age": 25
},
{
"name": "ttt",
"age": 30
}
]
}
}
]
}
}
- 多字段
# 3.2.5 多字段
# 按照用户姓名进行搜索,又希望按照姓氏进行排列
# 创建索引
PUT /order
{
"mappings":{
"properties": {
"order_id":{
"type":"keyword"
},
"user_id":{
"type":"keyword"
},
"user_name":{
"type":"text",
"fields":{ # 子字段,使用keyword,排序要求
"user_name_keyword":{
"type":"keyword"
}
}
},
"hotel_id":{
"type":"keyword"
}
}
}
}
POST /_bulk
{"index":{"_index":"order", "_id":"001"}}
{"order_id":"1", "user_id":"x001", "user_name":"Michael Jordan", "hotel_id":"h0501"}
{"index":{"_index":"order","_id":"002"}}
{"order_id":"2","user_id":"x002","user_name":"Stephen Demon","hotel_id":"h0502"}
{"index":{"_index":"order","_id":"003"}}
{"order_id":"3","user_id":"x003","user_name":"Tim Jason","hotel_id":"h0503"}
{"index":{"_index":"order","_id":"004"}}
{"order_id":"4","user_id":"x004","user_name":"Richale Jordan","hotel_id":"h0504"}
{"index":{"_index":"order","_id":"005"}}
{"order_id":"5","user_id":"x005","user_name":"Paul Jordan","hotel_id":"h0505"}
// ③ 检索user_name中有Jordan的数据,并根据user_name中last_name排序
GET /order/_search
{
"query":{
"match":{
"user_name":"Jordan"
}
},
"sort":{
"user_name.user_name_keyword":"asc"
}
}
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "order",
"_id": "001",
"_score": null,
"_source": {
"order_id": "1",
"user_id": "x001",
"user_name": "Michael Jordan",
"hotel_id": "h0501"
},
"sort": [
"Michael Jordan"
]
},
{
"_index": "order",
"_id": "005",
"_score": null,
"_source": {
"order_id": "5",
"user_id": "x005",
"user_name": "Paul Jordan",
"hotel_id": "h0505"
},
"sort": [
"Paul Jordan"
]
},
{
"_index": "order",
"_id": "004",
"_score": null,
"_source": {
"order_id": "4",
"user_id": "x004",
"user_name": "Richale Jordan",
"hotel_id": "h0504"
},
"sort": [
"Richale Jordan"
]
}
]
}
}
分词器
安装及配置
- 安装(IK分词器)
sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/9.4.2 # 对应elasticsearch版本
- 补充词典文件
cd /usr/share/elasticsearch/plugins/analysis-ik
sudo mkdir -p config
sudo chown elasticsearch:elasticsearch config
# 下载词典文件
cd config
sudo curl -L -O https://raw.githubusercontent.com/medcl/elasticsearch-analysis-ik/master/config/main.dic
sudo curl -L -O https://raw.githubusercontent.com/medcl/elasticsearch-analysis-ik/master/config/stopword.dic
sudo curl -L -O https://raw.githubusercontent.com/medcl/elasticsearch-analysis-ik/master/config/quantifier.dic
sudo curl -L -O https://raw.githubusercontent.com/medcl/elasticsearch-analysis-ik/master/config/IKAnalyzer.cfg.xml
sudo curl -L -O https://raw.githubusercontent.com/medcl/elasticsearch-analysis-ik/master/config/extra_main.dic
sudo curl -L -O https://raw.githubusercontent.com/medcl/elasticsearch-analysis-ik/master/config/extra_stopword.dic
sudo curl -L -O https://raw.githubusercontent.com/medcl/elasticsearch-analysis-ik/master/config/extra_single_word.dic
sudo curl -L -O https://raw.githubusercontent.com/medcl/elasticsearch-analysis-ik/master/config/extra_single_word_low_freq.dic
sudo curl -L -O https://raw.githubusercontent.com/medcl/elasticsearch-analysis-ik/master/config/preposition.dic
sudo curl -L -O https://raw.githubusercontent.com/medcl/elasticsearch-analysis-ik/master/config/suffix.dic
sudo curl -L -O https://raw.githubusercontent.com/medcl/elasticsearch-analysis-ik/master/config/surname.dic
# 修复权限
sudo chown -R elasticsearch:elasticsearch /usr/share/elasticsearch/plugins/analysis-ik/config/
sudo chmod -R 644 /usr/share/elasticsearch/plugins/analysis-ik/config/*.dic
sudo chmod 644 /usr/share/elasticsearch/plugins/analysis-ik/config/IKAnalyzer.cfg.xml
- 验证
curl -X POST "localhost:9200/_analyze" -H 'Content-Type: application/json' -d'
{
"analyzer": "ik_max_word",
"text": "中华人民共和国国歌"
}'
{"tokens":[{"token":"中华人民共和国","start_offset":0,"end_offset":7,"type":"CN_WORD","position":0},{"token":"中华人民","start_offset":0,"end_offset":4,"type":"CN_WORD","position":1},{"token":"中华","start_offset":0,"end_offset":2,"type":"CN_WORD","position":2},{"token":"华人","start_offset":1,"end_offset":3,"type":"CN_WORD","position":3},{"token":"人民共和国","start_offset":2,"end_offset":7,"type":"CN_WORD","position":4},{"token":"人民","start_offset":2,"end_offset":4,"type":"CN_WORD","position":5},{"token":"共和国","start_offset":4,"end_offset":7,"type":"CN_WORD","position":6},{"token":"共和","start_offset":4,"end_offset":6,"type":"CN_WORD","position":7},{"token":"国","start_offset":6,"end_offset":7,"type":"CN_CHAR","position":8},{"token":"国歌","start_offset":7,"end_offset":9,"type":"CN_WORD","position":9}]}%
使用
默认分词
// ① ES默认使用的standard
POST _analyze
{
"analyzer":"standard",
"text":"Hello, I am Tommy and living in China."
}
// ② 使用simple
POST _analyze
{
"analyzer":"simple",
"text":"Hello, I am Tommy and living in China."
}
// ③ 使用whitespace
POST _analyze
{
"analyzer":"whitespace",
"text":"Hello, I am Tommy and living in China."
}
创建索引使用analyzer
# settings 指定
PUT /case1
{
"settings":{
"analysis":{
"analyzer":{
"default":{
"type":"standard"
}
}
}
},
"mappings":{
"properties":{
"name":{ // 属性1
"type":"text"
},
"address":{ // 属性2
"type":"text"
}
}
}
}
# 字段分别指定
PUT /case2
{
"mappings":{
"properties":{
"name":{
"type":"text",
"analyzer":"whitespace" // 分词器1
},
"address":{
"type":"text",
"analyzer":"simple" // 分词器2
}
}
}
}
# 搜索时使用analyzer
PUT /case3
{
"mappings":{
"properties":{
"name":{
"type":"text",
"analyzer":"whitespace", // 索引时,
"search_analyzer":"simple" // 搜索时
}
}
}
}
# 自定义analyzer
PUT /case4
{
"settings":{
"analysis":{
"analyzer":{
"rebuild_analyzer":{ // 自定义的analyzer名称
"type":"custom",
"tokenizer":"standard",
"filter":["lowercase"]
}
}
}
}
}
# 对指定内容根据如上自定义的分词规则进行分词
POST /case4/_analyze
{
"text":"Hello, I am Tommy and living in China."
}
IK分词器使用
# ① ik_smart
POST _analyze
{
"analyzer":"ik_smart", // 粗粒度
"text":"今天天气很好,我们去郊游。"
}
# ② ik_max_word
POST _analyze
{
"analyzer":"ik_max_word", // 细粒度
"text":"今天天气很好,我们去郊游"
}
自定义词典
- 第一步:在IK分析器的安装目录下的config子目录中创建文件my.dic,在其中添加词语即可。如果有更多的词语需要添加,则每个词语单独一行
- 第二步:添加完成后修改IK分析器的配置文件,路径为config/IKAnalyzer.cfg.xml,将新建的字典文件加入ext_dict选项中
- 第三步:重启ES
POST _analyze
{
"analyzer":"ik_max_word",
"text":"埃隆马斯克表示搭乘SpaceX星际飞船前往火星的价格约为十万美元"
}
# 埃隆马斯克切分不正确
{
"tokens": [
{
"token": "埃",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
"token": "隆",
"start_offset": 1,
"end_offset": 2,
"type": "CN_CHAR",
"position": 1
},
{
"token": "马斯克",
"start_offset": 2,
"end_offset": 5,
"type": "CN_WORD",
"position": 2
},
{
"token": "表示",
"start_offset": 5,
"end_offset": 7,
"type": "CN_WORD",
"position": 3
},
{
"token": "搭乘",
"start_offset": 7,
"end_offset": 9,
"type": "CN_WORD",
"position": 4
},
{
"token": "spacex",
"start_offset": 9,
"end_offset": 15,
"type": "ENGLISH",
"position": 5
},
{
"token": "星际",
"start_offset": 15,
"end_offset": 17,
"type": "CN_WORD",
"position": 6
},
{
"token": "飞船",
"start_offset": 17,
"end_offset": 19,
"type": "CN_WORD",
"position": 7
},
{
"token": "前往",
"start_offset": 19,
"end_offset": 21,
"type": "CN_WORD",
"position": 8
},
{
"token": "火星",
"start_offset": 21,
"end_offset": 23,
"type": "CN_WORD",
"position": 9
},
{
"token": "的",
"start_offset": 23,
"end_offset": 24,
"type": "CN_CHAR",
"position": 10
},
{
"token": "价格",
"start_offset": 24,
"end_offset": 26,
"type": "CN_WORD",
"position": 11
},
{
"token": "约为",
"start_offset": 26,
"end_offset": 28,
"type": "CN_WORD",
"position": 12
},
{
"token": "十万",
"start_offset": 28,
"end_offset": 30,
"type": "CN_WORD",
"position": 13
},
{
"token": "美元",
"start_offset": 30,
"end_offset": 32,
"type": "CN_WORD",
"position": 14
}
]
}
cd /usr/share/elasticsearch/plugins/analysis-ik/config
sudo touch my.dic
echo "埃隆马斯克" >> my.dic
sudo chown elasticsearch:elasticsearch my.dic
sudo nvim IKAnalyzer.cfg.xml
```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">my.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
```
再次测试:
POST _analyze
{
"analyzer":"ik_smart",
"text":"埃隆马斯克表示搭乘SpaceX星际飞船前往火星的价格约为十万美元"
}
{
"tokens": [
{
"token": "埃隆马斯克",
"start_offset": 0,
"end_offset": 5,
"type": "CN_WORD",
"position": 0
},
{
"token": "表示",
"start_offset": 5,
"end_offset": 7,
"type": "CN_WORD",
"position": 1
},
{
"token": "搭乘",
"start_offset": 7,
"end_offset": 9,
"type": "CN_WORD",
"position": 2
},
{
"token": "spacex",
"start_offset": 9,
"end_offset": 15,
"type": "ENGLISH",
"position": 3
},
{
"token": "星际",
"start_offset": 15,
"end_offset": 17,
"type": "CN_WORD",
"position": 4
},
{
"token": "飞船",
"start_offset": 17,
"end_offset": 19,
"type": "CN_WORD",
"position": 5
},
{
"token": "前往",
"start_offset": 19,
"end_offset": 21,
"type": "CN_WORD",
"position": 6
},
{
"token": "火星",
"start_offset": 21,
"end_offset": 23,
"type": "CN_WORD",
"position": 7
},
{
"token": "的",
"start_offset": 23,
"end_offset": 24,
"type": "CN_CHAR",
"position": 8
},
{
"token": "价格",
"start_offset": 24,
"end_offset": 26,
"type": "CN_WORD",
"position": 9
},
{
"token": "约为",
"start_offset": 26,
"end_offset": 28,
"type": "CN_WORD",
"position": 10
},
{
"token": "十万",
"start_offset": 28,
"end_offset": 30,
"type": "CN_WORD",
"position": 11
},
{
"token": "美元",
"start_offset": 30,
"end_offset": 32,
"type": "CN_WORD",
"position": 12
}
]
}
同义词用法
- 创建索引时使用同义词
settings.analysis
├── filter ← 自定义过滤器
│ └── ik_synonyms_filter (同义词替换)
│
└── analyzer ← 自定义分析器
└── ik_analyzer_synonyms
├── tokenizer: ik_smart ← 先分词
└── filter: [lowercase, ik_synonyms_filter] ← 再过滤
PUT /city
{
"settings":{
"analysis": {
"filter": {
"ik_synonyms_filter":{ // 自定义filter
"type":"synonym",
"synonyms":[
"北京,首都,京城,北平",
"深圳,示范区",
"台湾省,宝岛"
]
}
},
"analyzer":{
"ik_analyzer_synonyms":{ // 自定义analyzer
"tokenizer":"ik_smart",
"filter":[
"lowercase", // 内置filter
"ik_synonyms_filter" // 自定义filter
]
}
}
}
},
"mappings":{
"properties":{
"title":{
"type":"text",
"analyzer":"ik_analyzer_synonyms" // 自定义analyzer
}
}
}
}
POST /city/_doc/001
{
"title":"北京"
}
POST /city/_doc/002
{
"title":"深圳"
}
POST /city/_doc/003
{
"title":"宝岛"
}
GET /city/_search
{
"query":{
"match":{
"title":"示范区"
}
}
}
{
"took": 6,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.6362648,
"hits": [
{
"_index": "city",
"_id": "002",
"_score": 1.6362648,
"_source": {
"title": "深圳"
}
}
]
}
}
- 配置文件写入同义词
cd /etc/elasticsearch/
mkdir -p config/analysis
cd /config/analysis
sudo touch synonyms.txt
sudo nvim synonyms.txt
sudo chown elasticsearch:elasticsearch synonyms.txt
# 示例
```
你好,hello,hi,您好
狗狗,大狗,小狗狗,修狗,勾勾,修勾
```
# 创建索引
PUT /syn_case
{
"settings": {
"analysis": {
"filter": {
"my_synonyms_filter": {
"type": "synonym",
"synonyms_path": "config/analysis-ik/synonyms.txt" # 指定路径
}
},
"analyzer": {
"ik_analyzer_synonyms": {
"tokenizer": "ik_smart",
"filter": [
"lowercase",
"my_synonyms_filter"
]
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "ik_analyzer_synonyms"
}
}
}
}
PUT /syn_case/_doc/001
{
"content":"Hello我来自中国"
}
PUT /syn_case/_doc/002
{
"content":"地球被修狗占领了"
}
GET /syn_case/_search
{
"query":{
"match":{
"content":"修勾"
}
}
}
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0.6548753,
"hits": [
{
"_index": "syn_case",
"_id": "002",
"_score": 0.6548753,
"_source": {
"content": "地球被修狗占领了"
}
}
]
}
}
GET /syn_case/_analyze
{
"analyzer":"ik_analyzer_synonyms",
"text":"Hello我来自中国"
}
{
"tokens": [
{
"token": "hello",
"start_offset": 0,
"end_offset": 5,
"type": "ENGLISH",
"position": 0
},
{
"token": "你好",
"start_offset": 0,
"end_offset": 5,
"type": "SYNONYM",
"position": 0
},
{
"token": "hi",
"start_offset": 0,
"end_offset": 5,
"type": "SYNONYM",
"position": 0
},
{
"token": "您好",
"start_offset": 0,
"end_offset": 5,
"type": "SYNONYM",
"position": 0
},
{
"token": "我",
"start_offset": 5,
"end_offset": 6,
"type": "CN_CHAR",
"position": 1
},
{
"token": "来自",
"start_offset": 6,
"end_offset": 8,
"type": "CN_WORD",
"position": 2
},
{
"token": "中国",
"start_offset": 8,
"end_offset": 10,
"type": "CN_WORD",
"position": 3
}
]
}
- 查询时使用同义词
PUT /country
{
"settings": {
"analysis":{
"filter":{
"ik_synonyms_filter":{ // 自定义filter
"type":"synonym_graph", // ES内置的分词过滤器
"updateable":true, // 动态更新
"synonyms_path":"config/analysis/synonyms.txt"
}
},
"analyzer":{
"ik_synonyms_search_analyzer":{
"tokenizer":"ik_max_word",
"filter":[
"lowercase", // 内置filter
"ik_synonyms_filter" // 自定义filter
]
}
}
}
},
"mappings":{
"properties":{
"name":{
"type":"text",
"analyzer":"ik_max_word", // 索引时
"search_analyzer":"ik_synonyms_search_analyzer" // 搜索时
}
}
}
}
# 先查询尝试默认分词
POST /country/_analyze
{
"analyzer": "ik_synonyms_search_analyzer",
"text":"中国是历史悠久的国家"
}
POST /country/_doc/1
{
"name":"中国是历史悠久的国家"
}
POST /country/_doc/2
{
"name":"美国是霸权主义的典范"
}
# 修改同义词文件词典synonyms.txt
# 添加同义词:
# 中国,CHINA,中华人民共和国
# 美国,USA,美利坚
# 动态更新
POST /country/_reload_search_analyzers
# 查询
GET /country/_search
{
"query":{
"match":{
"name":"CHINA"
}
}
}
{
"took": 5,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0.6931471,
"hits": [
{
"_index": "country",
"_id": "1",
"_score": 0.6931471,
"_source": {
"name": "中国是历史悠久的国家"
}
}
]
}
}
停用词
停用词(Stop Words)是指在 Elasticsearch 中会被分析器(Analyzer)过滤掉的常见词汇。这些词通常出现频率极高,但对搜索相关性贡献很小。
英文为例
| 类型 | 示例 |
|---|---|
| 冠词 | a, an, the |
| 介词 | at, by, for, from, in, of, on, to, with |
| 连词 | and, but, or, not |
| 代词 | I, you, he, she, it, we, they, me, him, her, us, them |
| 助动词 | is, am, are, was, were, be, been, being, have, has, had, do, does, did |
| 其他常见词 | this, that, these, those, as, at, by, from |
中文的话比如:
的、了、在、是、我、有、和、就、不、人、都、一、一个、上、也、很、到、说、要、去、你、会、着、没有、看、好、自己、这等
- 创建索引指定停用词
PUT /stop_case_1
{
"settings": {
"analysis":{
"filter":{
"my_stop":{ // 自定义filter
"type":"stop",
"stopwords":[
"我",
"的",
"这"
]
}
},
"analyzer":{
"ik_analyzer_stop":{ // 自定义analyzer
"tokenizer":"ik_smart",
"filter":[
"my_stop" // 自定义filter
]
}
}
}
},
"mappings":{
"properties": {
"name":{
"type":"text",
"analyzer":"ik_analyzer_stop" // 自定义
}
}
}
}
GET /stop_case_1/_analyze
{
"analyzer":"ik_analyzer_stop",
"text":"这里的东西不在我这"
}
- 在内置analyzer中使用停用词
# 像standard这种常用的分析器都自带有停用词过滤器,只需要对其参数进行相应设置即可。
PUT /stop_case_2
{
"settings":{
"analysis":{
"analyzer":{
"my_standard":{
"type":"standard", // 内置analyzer
"stopwords":["我","的","这"] // 停用词
}
}
}
},
"mappings":{
"properties": {
"name":{
"type":"text",
"analyzer":"my_standard"
}
}
}
}
GET /stop_case_2/_analyze
{
"analyzer":"my_standard",
"text":"这里的东西不在我这"
}
- 在IK analyzer中使用停用词
# 在默认情况下,IK分析器的分词器只有英文停用词,没有中文停用词。
POST /_analyze
{
"analyzer":"ik_smart",
"text":"哦对的,我在深圳工作、生活。"
}
如果用户想要添加中文停用词,需要通过自定义停用词文件的形式进行添加。在plugins/IK/config目录下创建my_stopword.dict文件,并在其中添加中文停用词即可
cd /etc/elasticsearch/plugins/analysis-ik/config
sudo touch stopwords.txt
sudo tee ./stopwords.txt << 'EOF'
的
了
在
是
我
有
和
就
不
人
都
一
一个
上
也
很
到
说
要
去
你
会
着
没有
看
好
自己
这
EOF
sudo chown elasticsearch:elasticsearch /etc/elasticsearch/config/
sudo nvim IKAnalyzer.cfg.xml
```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">my.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">stopwords.dic</entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
```
# 重启服务
sudo systemctl restart elasticsearch
POST /_analyze
{
"analyzer":"ik_smart",
"text":"哦对的,我在深圳工作、生活。"
}
{
"tokens": [
{
"token": "哦",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
"token": "对",
"start_offset": 1,
"end_offset": 2,
"type": "CN_CHAR",
"position": 1
},
{
"token": "深圳",
"start_offset": 6,
"end_offset": 8,
"type": "CN_WORD",
"position": 5
},
{
"token": "工作",
"start_offset": 8,
"end_offset": 10,
"type": "CN_WORD",
"position": 6
},
{
"token": "生活",
"start_offset": 11,
"end_offset": 13,
"type": "CN_WORD",
"position": 7
}
]
}
拼音搜索
# 获取你的ES版本
curl -X GET "localhost:9200"
# 安装拼音插件
sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install \
https://get.infini.cloud/elasticsearch/analysis-pinyin/9.4.2
sudo systemctl restart elasticsearch
POST /_analyze
{
"analyzer": "pinyin",
"text":"王府井"
}
{
"tokens": [
{
"token": "wang",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 0
},
{
"token": "wangfujing",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 0
},
{
"token": "fu",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 1
},
{
"token": "jing",
"start_offset": 0,
"end_offset": 0,
"type": "word",
"position": 2
}
]
}
PUT /countries
{
"mappings":{
"properties":{
"name":{
"type":"text",
"analyzer":"pinyin"
}
}
}
}
PUT /_bulk
{"index":{"_index":"countries"}}
{"name":"中国"}
{"index":{"_index":"countries"}}
{"name":"美国"}
{"index":{"_index":"countries"}}
{"name":"俄罗"}
GET /countries/_search
{
"query":{
"match":{
"name":"zg"
}
}
}
GET /countries/_search
{
"query":{
"match":{
"name":"els"
}
}
}
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.135697,
"hits": [
{
"_index": "countries",
"_id": "Bw8pnZ4B_XnyEV1ZKros",
"_score": 1.135697,
"_source": {
"name": "俄罗"
}
}
]
}
}
GET /countries/_search
{
"query":{
"match":{
"name":"zhongguo"
}
}
}
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1.6799117,
"hits": [
{
"_index": "countries",
"_id": "BQ8pnZ4B_XnyEV1ZKros",
"_score": 1.6799117,
"_source": {
"name": "中国"
}
},
{
"_index": "countries",
"_id": "Bg8pnZ4B_XnyEV1ZKros",
"_score": 0.5442147,
"_source": {
"name": "美国"
}
}
]
}
}
GET /countries/_search
{
"query": {
"match": {
"name": {
"query": "zhongguo",
"minimum_should_match": "100%"
}
}
}
}
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.6799117,
"hits": [
{
"_index": "countries",
"_id": "BQ8pnZ4B_XnyEV1ZKros",
"_score": 1.6799117,
"_source": {
"name": "中国"
}
}
]
}
}
进阶
辅助功能
# 创建索引
PUT /hotel_info
{
"settings":{
"number_of_shards": 1, // 主分片数,
"number_of_replicas": 1
},
"mappings":{
"properties": {
"hotel_id":{ // 酒店ID
"type":"integer"
},
"name":{ // 酒店名称
"type":"text",
"analyzer":"ik_smart" // 分析器
},
"city":{ // 城市
"type":"keyword"
},
"star":{ // 星级
"type":"integer"
},
"open_year":{ // 开业时间
"type":"date"
},
"room_quantity":{ // 房间数量
"type":"integer"
},
"zone":{ // 所在区域范围
"type":"text",
"analyzer":"ik_max_word" // 分析器
},
"area":{ // 在哪个区
"type":"keyword"
},
"address":{ // 酒店地址
"type":"text",
"analyzer":"ik_max_word"
},
"location":{ // 经纬度
"type":"geo_point"
},
"comment_total":{ // 评论数量
"type":"integer"
},
"total_score":{ // 总评分
"type":"float"
},
"health_score":{ // 健康评分
"type":"float"
},
"environment_score":{ // 环境评分
"type":"float"
},
"service_score":{ // 服务评分
"type":"float"
},
"facilities_score":{ // 设施评分
"type":"float"
},
"bullet_screen_info":{ // 弹幕记录
"type":"text",
"fields":{ // 多字段定义,解决NaN值问题
"keyword":{ // 子字段名(叫 keyword
"type":"keyword", // 子字段的类型是 keyword
"null_value":"NULL"
}
}
},
"hotel_facilities_popular":{ // 酒店受欢迎的设施
"type":"text"
},
"hotel_facilities_full":{ // 酒店提供的所有设施
"type":"text"
},
"hotel_policy":{ // 酒店规定
"type":"text"
},
"description":{ // 酒店描述
"type":"text"
},
"traffic":{ // 交通信息
"type":"text"
},
"min_price":{ // 最低价格
"type":"float",
"null_value": 0.0
},
"min_price_checkin_date":{ // 最低价格入住日期
"type":"date",
"null_value":"1997-01-01"
},
"base_rooms":{ // 基础房间类型
"type":"text",
"fields":{
"keyword":{ // 子字段名(叫 keyword)
"type":"keyword", // 子字段的类型是 keyword
"null_value":"NULL"
}
}
}
}
}
}
# 批量插入文件内的数据
curl -H "Content-Type: application/json" "127.0.0.1:9200/_bulk?pretty" --data-binary "@hotel_info.json"
# 查询数量验证
GET /hotel_info/_count
{
"count": 126,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
}
}
# 1.
GET /hotel_info/_search
{
"_source": ["name", "city", "area"],
"query": {
"match_all": {}
}
}
# 2.
GET /hotel_info/_search?_source=name,city,area
{
"query":{
"match_all":{}
}
}
- 结果分页
GET /hotel_info/_search?_source=name
{
"query":{
"match_all":{}
},
"from":4,
"size":5
}
- 性能分析
GET /hotel_info/_search?_source=name,city
{
"profile": true,
"from":0,
"size":5,
"query":{
"match":{
"city":"上海"
}
}
}
- 评分分析
Elasticsearch 默认使用 BM25 相似度,公式为:
score=boost×idf×tf*score*=boost×idf×tf
其中:
boost= 2.2(查询时的权重提升)idf:逆文档频率tf:词频归一化值
idf计算 :idf=log(1+*n*+0.5*N*−*n*+0.5)
- n = 126:包含“上海”的文档数
- N = 126:该字段有值的总文档数
tf计算:tf = freq / (freq + k1 *( 1-b+b * dl / avgdl))
freq= 1(文档中“上海”出现1次)k1= 1.2(控制词频饱和)b= 0.75(控制长度归一化)dl= 1(该文档的city字段长度,可能是分词后的词数)avgdl= 1(平均字段长度)
- 带入总分:
score = boost * idf * tf = 0.0039448
GET /hotel_info/_explain/0
{
"query":{
"match":{
"city":"上海"
}
}
}
布尔查询
- must查询
// 相当于逻辑查询中的“与”查询
// 命中的文档必须匹配该子查询的结果
// 示例:查询区域为“黄浦区”和“静安区”,最低价格在300~400之间的酒店
GET /hotel_info/_search?_source=name,area,min_price
{
"query":{
"bool":{
"must":[
{
"terms": {
"area.keyword": [
"黄浦区",
"静安区"
]
}
},
{
"range":{
"min_price":{
"gt":300,
"lt":400
}
}
}
]
}
}
}
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 21,
"relation": "eq"
},
"max_score": 2,
"hits": [
{
"_index": "hotel_info",
"_id": "0",
"_score": 2,
"_source": {
"name": "上海瑞金洲际酒店",
"area": "黄浦区",
"min_price": 1613
}
},
{
"_index": "hotel_info",
"_id": "1",
"_score": 2,
"_source": {
"name": "上海静安洲际酒店",
"area": "静安区",
"min_price": 1315
}
},
{
"_index": "hotel_info",
"_id": "6",
"_score": 2,
"_source": {
"name": "汉庭酒店(上海南京路步行街中心店)",
"area": "黄浦区",
"min_price": 269
}
},
{
"_index": "hotel_info",
"_id": "7",
"_score": 2,
"_source": {
"name": "上海雅居乐万豪侯爵酒店",
"area": "黄浦区",
"min_price": 1056
}
},
{
"_index": "hotel_info",
"_id": "11",
"_score": 2,
"_source": {
"name": "上海宝华万豪酒店",
"area": "静安区",
"min_price": 862
}
},
{
"_index": "hotel_info",
"_id": "13",
"_score": 2,
"_source": {
"name": "上海绿地万豪酒店",
"area": "黄浦区",
"min_price": 1087
}
},
{
"_index": "hotel_info",
"_id": "16",
"_score": 2,
"_source": {
"name": "全季酒店(上海外滩山东中路店)",
"area": "黄浦区",
"min_price": 323
}
},
{
"_index": "hotel_info",
"_id": "17",
"_score": 2,
"_source": {
"name": "汉庭酒店(上海人民广场店)",
"area": "黄浦区",
"min_price": 353
}
},
{
"_index": "hotel_info",
"_id": "19",
"_score": 2,
"_source": {
"name": "汉庭优佳酒店(上海西藏南路店)",
"area": "黄浦区",
"min_price": 342
}
},
{
"_index": "hotel_info",
"_id": "49",
"_score": 2,
"_source": {
"name": "宿适酒店(上海外滩南京东路地铁站店)",
"area": "黄浦区",
"min_price": 275
}
}
]
}
}
- should
GET /hotel_info/_search?_source=name,area
{
"query":{
"bool":{
"should":[
{
"term":{
"area.keyword":{
"value":"黄浦区"
}
}
},
{
"term":{
"area.":{
"value":"闵行区"
}
}
}
]
}
}
}
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 42,
"relation": "eq"
},
"max_score": 2.103347,
"hits": [
{
"_index": "hotel_info",
"_id": "10",
"_score": 2.103347,
"_source": {
"name": "汉庭酒店(上海虹桥机场沪青平公路店)",
"area": "闵行区"
}
},
{
"_index": "hotel_info",
"_id": "14",
"_score": 2.103347,
"_source": {
"name": "汉庭酒店(上海虹桥机场新店)",
"area": "闵行区"
}
},
{
"_index": "hotel_info",
"_id": "18",
"_score": 2.103347,
"_source": {
"name": "汉庭酒店(上海虹桥火车站沪青平公路店)",
"area": "闵行区"
}
},
{
"_index": "hotel_info",
"_id": "23",
"_score": 2.103347,
"_source": {
"name": "桔子酒店(上海虹桥国展中心七莘路店)",
"area": "闵行区"
}
},
{
"_index": "hotel_info",
"_id": "26",
"_score": 2.103347,
"_source": {
"name": "汉庭酒店(上海虹桥火车站中春路店)",
"area": "闵行区"
}
},
{
"_index": "hotel_info",
"_id": "36",
"_score": 2.103347,
"_source": {
"name": "锦江之星(上海虹梅南路店)",
"area": "闵行区"
}
},
{
"_index": "hotel_info",
"_id": "40",
"_score": 2.103347,
"_source": {
"name": "汉庭酒店(上海漕河泾宜山路店)",
"area": "闵行区"
}
},
{
"_index": "hotel_info",
"_id": "48",
"_score": 2.103347,
"_source": {
"name": "格林豪泰(上海虹桥枢纽国家会展中心华翔路店)",
"area": "闵行区"
}
},
{
"_index": "hotel_info",
"_id": "57",
"_score": 2.103347,
"_source": {
"name": "格林酒店(上海虹桥国家会展中心北翟路店)",
"area": "闵行区"
}
},
{
"_index": "hotel_info",
"_id": "58",
"_score": 2.103347,
"_source": {
"name": "格林豪泰(上海虹桥机场沪青平公路店)",
"area": "闵行区"
}
}
]
}
}
- must not查询
GET /hotel_info/_search?_source=name,area,min_price
{
"query":{
"bool":{
"must_not":[
{
"term":{
"area.keyword":{
"value":"黄浦区"
}
}
},
{
"range": {
"min_price": {
"gte": 500
}
}
}
]
}
}
}
- filter查询
// 示例:查询万豪酒店且在黄浦区
GET /hotel_info/_search?_source=name,area
{
"query":{
"bool":{
"filter":[
{
"match":{
"name":{
"query":"万豪",
"operator":"and"
}
}
},
{
"term":{
"area.keyword":{
"value":"黄浦区"
}
}
}
]
}
}
}
全文搜索
全文搜索首先对查询词进行分析,然后根据查询词的分词结果构建查询,这里所说的全文指的是文本类型数据(text类型)
- match 查询
# 说明:match搜索可以设置operator参数,该参数决定文档按照分词后的词集合进行“与”还是“或”匹配。在默认情况下,该参数的值为“或”关系,即operator的值为or,这也解释了搜索结果中包含部分匹配的文档。如果希望各个词之间的匹配结果是“与”关系,则可以设置operator参数的值为and。
# 默认,operator=or,比如喜来登酒店可能分成"喜来登"和"酒店",只要有一个出现既可以匹配
GET /hotel_info/_search?_source=name
{
"query":{
"match":{
"name":"喜来登酒店"
}
}
}
# operator=and,喜来登酒店分为"喜来登"和"酒店",and必须要求两者都出来才匹配
GET /hotel_info/_search?_source=name
{
"query":{
"match":{
"name":{
"query":"喜来登酒店",
"operator": "and"
}
}
}
}
- multi-match 查询
有时用户需要在多个字段中查询关键词,除了使用布尔查询封装多个match查询之外,可替代的方案是使用multi_match。可以在multi_match的query子句中组织数据匹配规则,并在fields子句中指定需要搜索的字段列表。
# 查询 hotel_facilities_popular=24小时前台 或 hotel_facilities_full=免费停车场
GET /hotel_info/_search?_source=name,hotel_facilities_popular,hotel_facilities_full
{
"query":{
"bool":{
"should":[
{
"match":{
"hotel_facilities_popular":"24小时前台" // 条件1
}
},
{
"match":{
"hotel_facilities_full":"免费停车场" // 条件2
}
}
]
}
}
}
GET /hotel_info/_search?_source=name,hotel_facilities_popular,hotel_facilities_full
{
"query":{
"multi_match":{
"query":"免费停车场,24小时前台",
"type":"best_fields", // 多个字段中,返回评分最高的
"fields": ["hotel_facilities_popular","hotel_facilities_full"],
"operator": "or"
}
}
}
- match_phrase 查询
match_phrase用于匹配短语,与match查询不同的是,match_phrase用于搜索确切的短语或邻近的词语。可以设置match_phrase查询的slop参数,它用来调节匹配词之间的距离阈值。
# 在酒店标题中搜索“万豪酒店”,希望酒店标题中的“万豪”与“酒店”紧邻,并且“万豪”在“酒店”前面
GET /hotel_info/_search?_source=name
{
"query":{
"match_phrase": {
"name": "万豪酒店"
}
}
}
# 设置match_phrase查询的slop参数,它用来调节匹配词之间的距离阈值
GET /hotel_info/_search?_source=name
{
"query":{
"match_phrase": {
"name": {
"query":"万豪酒店",
"slop":4 // 调节匹配词之间的距离阈值:这样可以匹配比如"万豪侯爵酒店"等
}
}
}
}
搜索排序
ES提供了sort子句可以对数据进行排序。使用sort子句一般是按照字段信息进行排序,不受相关性影响,而且打分步骤需要耗费一定的硬件资源和时间,因此默认情况下,不对文档进行打分。
使用sort排序分为两种类别,一种是按照字段值的大小进行排序,另一种是按照给定地理坐标的距离远近进行排序。排序字段不可以是text等特殊类型,一般是整数类型和keyword类型。
- 普通字段值排序
GET /hotel_info/_search?_source=name,min_price
{
"query":{
"match":{
"name":"万豪"
}
},
"sort":[
{"min_price": {
"order": "asc"
}}
]
}
# 说明:文档的_score值和max_score都为null,这说明在默认情况下ES查询时使用sort对结果排序是不计算分数的。
# 搜索名称包含"万豪"或"全季"的酒店,并对酒店按照价格进行升序排列,再按照星级进行降序排列
GET /hotel_info/_search?_source=name
{
"query": {
"bool": {
"must_not": [
{
"match_phrase": {
"name": "侯爵"
}
}
],
"should": [
{
"bool": {
"should": [
{
"match_phrase": {
"name": "万豪"
}
}
],
"minimum_should_match": 1
}
}
]
}
},
"sort": [
{ "min_price": "asc" },
{ "star": "desc" }
]
}
- 按照地理距离排序
使用geo_distance查询,配合sort可以指定另一种排序规则,即按照文档坐标与指定坐标的距离对结果进行排序.除了可以指定升序或者降序排列外,还可以指定排序结果中sort子句中的距离的计量单位,默认值为km即千米。
在进行距离计算时,系统默认使用的算法为arc,该算法的特点是计算精准但是耗费时间较长,用户可以使用distance_type参数选择另一种计算速度快但经度略差的算法,名称为plane。
GET /hotel_info/_search?_source=name,area,address
{
"query":{
"geo_distance":{
"distance":"5km", // 设置地理范围5公里
"location":{ // 中心点坐标
"lat":"31.276613", // 维度
"lon":"121.442561" // 经度
}
}
},
"sort":[
{
"_geo_distance": {
"location": { // 中心点坐标
"lat": "31.276613",
"lon": "121.442561"
},
"order": "asc", // 按距离由近及远进行排序
"unit": "km", // 距离单位
"distance_type": "plane" // 计算算法,默认算法arc
}
}
]
}
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 7,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "hotel_info",
"_id": "41",
"_score": null,
"_source": {
"name": "锦江之星(上海大宁灵石公园沪太路店)",
"area": "静安区",
"address": "沪太路893号"
},
"sort": [
0.007857917715522732
]
},
{
"_index": "hotel_info",
"_id": "11",
"_score": null,
"_source": {
"name": "上海宝华万豪酒店",
"area": "静安区",
"address": "广中西路333号"
},
"sort": [
1.398519587852979
]
},
{
"_index": "hotel_info",
"_id": "1",
"_score": null,
"_source": {
"name": "上海静安洲际酒店",
"area": "静安区",
"address": "恒丰路500号"
},
"sort": [
3.2709865684604873
]
},
{
"_index": "hotel_info",
"_id": "125",
"_score": null,
"_source": {
"name": "汉庭酒店(上海环球港店)",
"area": "普陀区",
"address": "武宁路492弄30号"
},
"sort": [
3.4314923918596096
]
},
{
"_index": "hotel_info",
"_id": "20",
"_score": null,
"_source": {
"name": "汉庭酒店(上海华师大店)",
"area": "普陀区",
"address": "宁夏路268号"
},
"sort": [
4.313841412041143
]
},
{
"_index": "hotel_info",
"_id": "62",
"_score": null,
"_source": {
"name": "白玉兰酒店(上海恒隆广场店)",
"area": "静安区",
"address": "西康路400号"
},
"sort": [
4.384552609593147
]
},
{
"_index": "hotel_info",
"_id": "99",
"_score": null,
"_source": {
"name": "如家·neo(上海南京路步行街黄河路店)",
"area": "黄浦区",
"address": "黄河路288号"
},
"sort": [
4.824022597054677
]
}
]
}
}

浙公网安备 33010602011771号