Elasticsearch常用DSL
常用的一些操作
- Document APIs
- 自动创建索引设置
- 强制创建或默认更新
- 不指定ID新增数据
- 指定刷新时间
- 快速查看某个文档是否存在
- 过滤只想要的某些字段
- 删除文档
- 更新完档
- 批量操作
- Search APIs
- 普通查询
- 排序
- 高亮查询
- SerchType
- Scroll Query
- Search After
- Count
- 分析API
- Mapping
- 常用的字段类型
- 核心类型
- text
- keyword
- 数字类型
- 日期类型
- 布尔类型
- binary 类型
- range类型
- 复合类型
- 数组类型
- 常见的Mapping属性
- coerce
- copy_to
- fields
- Aggregations
- Query DSL
- 全文查询
- match
- match_phrase_query
- multi_match
- common_terms_query
- 词项查询
- term查询
- terms查询
- range查询
- prefix 前缀查询
- whildcard 通配符查询
- regexp正则查询
- fuzzy模糊纠错查询
- indices查询
- function_score_query
- boosting调整打分查询
- 复合bool查询
- 特殊查询
- more_like_this近似查询
- script查询
- 聚合查询
- 指标聚合
- avg聚合
- min聚合
- max聚合
- sum聚合
- value_count统计
- cardinality统计
- stats统计
- Extend stats统计
- top_hits 查询
- 分桶聚合
- terms分桶
- filter分桶和filters分桶
- Range分桶
- Pipeline aggregations
Document APIs
自动创建索引设置
往一个不存在的index中插入一条数据时,es的默认设置是会根据数据中包含的字段自动创建出index,我们可以手动调节来关闭或者只对某些index开放、关闭。
//对twitter和index10以及以ind开头的index开放自动创建index,对index1开头的索引禁止自动创建index
PUT _cluster/settings
{"persistent": {"action.auto_create_index": "twitter,index10,-index1*,+ind*" }
}
//全部关闭自动创建索引
PUT _cluster/settings
{"persistent": {"action.auto_create_index": "false" }
}
//全部开启自动创建索引(默认)
PUT _cluster/settings
{"persistent": {"action.auto_create_index": "true" }
}
强制创建或默认更新
es中如果某个document已经存在了,那默认情况下在往同一个document上put数据时会覆盖原来的数据,如果不想覆盖可以替换为如果已经存在就不能写入了。
//在最后加上/_create
PUT twitter/_doc/1/_create
{"user" : "kimchy","post_date" : "2009-11-15T14:12:12","message" : "trying out Elasticsearch"
}
不指定ID新增数据
es允许不指定ID创建数据,它会帮我们创建出一个唯一ID,但是必须使用POST来提交不能使用PUT
POST twitter/_doc/
{"user" : "kimchy","post_date" : "2009-11-15T14:12:12","message" : "trying out Elasticsearch"
}
指定刷新时间
es默认情况是每1刷新插入的数据,也可以调整这个阈值使新来的数据暂时放在内存等到了一定的时间后统一持久化。
/**
* 将某个index的刷新时间改为50秒,
* 这个值也可以设置为-1,从而关闭刷新机制,常用语导数据时先设置为-1,等到完后设置为10s,则10秒后会批量持久化
* 当这个值设置为null时,代表立即更新,来了数据就进行持久化
*/
PUT {indexName}/_settings
{"refresh_interval": "50s"
}
如果某个索引指定了刷新时间且因为某些原因不允许修改但是某个数据又要立即查到则可以使用refresh=true强制刷新
POST twitter/_doc?refresh=true
{"user" : "AAAB","post_date" : "2009-11-15T14:12:12","message" : "asdawadw"
}
快速查看某个文档是否存在
如果我们能确定数据的ID,那可以使用HEAD命令来快速查看是否存在 如果存在则返回200,不存在返回404
HEAD {indexName}/{type}/{id}
过滤只想要的某些字段
//查询所有字段但是过滤掉message字段
{"_source": {"excludes": ["message"]},"query": {"bool": {"filter": {"term": {"user": "kimchy"}}}}
}
//只查询message字段
{"_source": ["message"],"query": {"bool": {"filter": {"term": {"user": "kimchy"}}}}
}
删除文档
//根据ID删除
DELETE /twitter/_doc/1//删除message中含有some message的数据
POST twitter/_delete_by_query
{"query": {"match": {"message": "some message"}}}//删除时候若遇到版本冲突则继续执行而不是中端处理
POST twitter/_delete_by_query?conflicts=proceed
{"query": {"match": {"message": "some message"}}}
更新完档
//根据ID进行更新 可以直接把新内容覆盖进去
PUT test/_doc/1
{"counter" : 1,"tags" : ["red"]
}//如果是根据各种查询条件将符合条件的进行更新,则可以通过借助painless脚本来进行更新。甚至可以将符合条件的直接删除
POST test/_doc/1/_update
{"script" : {"source": "if (ctx._source.tags.contains(params.tag)) { ctx._source.tags.remove(ctx._source.tags.indexOf(params.tag)) }","lang": "painless","params" : {"tag" : "blue"}}
}//如果存在则进行更新不存在则将upsert的内容插入且遇到冲突继续而不是报错
POST test/_doc/1/_update?conflicts=proceed
{"script" : {"source": "ctx._source.counter += params.count","lang": "painless","params" : {"count" : 4}},"upsert" : {"counter" : 1}
}
批量操作
//创建index为test,type为_doc,id=1的,source中包含1个值为value1的字段filed1,然后删除id=2的,创建id=3的,在更新id=1的
POST _bulk
{ "index" : { "_index" : "test", "_type" : "_doc", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_type" : "_doc", "_id" : "2" } }
{ "create" : { "_index" : "test", "_type" : "_doc", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_type" : "_doc", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
Search APIs
普通查询
GET /_search
{"query" : {"term" : { "user" : "kimchy" }},"from" : 0, "size" : 10
}
排序
//sort 不写order时默认是asc
GET /my_index/_search
{"sort" : [{ "post_date" : {"order" : "asc"}},"user",{ "name" : "desc" },{ "age" : "desc" },"_score"],"query" : {"term" : { "user" : "kimchy" }}
}
排序除了上面这种单值字段的排序还支持对数组字段或多值字段的排序,且支持通过mode选项对多值字段进行排序。mode支持以下几种模式:
min:将数组或多值字段中的最小值作为排序值
max:将数组或多值字段中的最大值作为排序值
sum:将数组或多值字段的和作为排序值
avg:将数组或多值字段中的平均值作为排序值
median:将数组或多值字段中的中位数作为排序值
PUT /my_index/_doc/1?refresh
{"product": "chocolate","price": [20, 4]
}POST /_search
{"query" : {"term" : { "product" : "chocolate" }},"sort" : [{"price" : {"order" : "asc", "mode" : "avg"}}]
}
ES的数据中途若增加了字段,则历史数据中是不包括该字段的,missing参数可以指定将该数据放到最前_first或最后_last。默认是放到最后面。unmapped_type可以指定一种类型放上默认值比如Long,倒叙排序时就会放上long类型的最小值为-9223372036854775808,正序排序时就会放上long类型的最大值9223372036854775808
GET /_search
{"sort" : [{ "price" : {"missing" : "_last"} }],"query" : {"term" : { "product" : "chocolate" }}
}
//排序时默认不计算分数如果想计算分数可以通过track_scores来打开
GET /_search
{"track_scores": true,"sort" : [{ "price" : {"unmapped_type" : "long"} }],"query" : {"term" : { "product" : "chocolate" }}
}
_source字段可以过滤返回的字段,也可以选择直接不返回任何字段
GET /_search
{"_source": false,//"_source": "obj.*",// "_source": {// "includes": [ "obj1.*", "obj2.*" ],// "excludes": [ "*.description" ]// },"query" : {"term" : { "user" : "kimchy" }}
}
ES可以通过使用post_filter ,就像SQL语句中的having查询那样对筛选后的结果再进行过滤。
GET /shirts/_search
//先过滤出Gucci品牌,然后再过滤出黄色的
{"query": {"bool": {"filter": {"term": { "brand": "gucci" } }}},"post_filter": { "term": { "color": "yello" }}
}
//注意:post_filter只对hits的结果进行过滤,它和分桶是平级的。下面这个查询只会影响hists中的内容而不会影响分桶的内容。
GET /shirts/_search
{"query": {"bool": {"filter": {"term": { "brand": "gucci" } }}},"aggs": {"colors": {"terms": { "field": "color" } },"color_red": {"filter": {"term": { "color": "red" } },"aggs": {"models": {"terms": { "field": "model" } }}}},"post_filter": { "term": { "color": "red" }}
}
高亮查询
高亮查询支持三种高亮方式分别是unified高亮器、plain高亮器、fvh高亮器,默认情况下是使用unified高亮器。unified高亮器把文本分成句子并对句子打分。plain高亮器适合在单个字段中突出显示简单查询匹配项。可以使用type字段自定义选择高亮器。
GET /_search
{"query" : {"match": { "user": "kimchy" }},"highlight" : {"fields" : {"comment" : {"type" : "plain"}}}
}
默认的 unified高亮器是最基本的高亮器。highlighter 高亮器实现高亮功能需要对 _source 中保存的原始文档进行二次分析,其速度在三种高亮器里最慢,优点是不需要额外的存储空间。postings-highlighter 高亮器实现高亮功能不需要二次分析,但是需要在字段的映射中设置 index_options 参数的取值为 offsets,即保存关键词的偏移量,速度快于默认的 highlighter 高亮器。例如,配置 comment 字段使用 postings-highlighter 高亮器,映射如下:
PUT /example
{"mappings": {"doc": {"properties": {"comment": {"type": "text","index_options": "offsets"}}}}
}
fast-vector-highlighter 高亮器实现高亮功能速度最快,但是需要在字段的映射中设置 term_vector 参数的取值为 with_positions_offsets,即保存关键词的位置和偏移信息,占用的存储空间最大,是典型的空间换时间的做法。例如,配置 comment 字段使用 fast-vector-highlighter 高亮器,映射如下:
PUT /example
{"mappings": {"doc": {"properties": {"comment": {"type": "text","term_vector": "with_positions_offsets"}}}}
}
ES提供了如下的高亮参数:
| 参数 | 说明 |
|---|---|
| boundary_chars | 包含每个边界字符的字符串。默认为,! ?\ \ n。 |
| boundary_max_scan | 扫描边界字符的距离。默认为20。 |
| boundary_scanner | 指定如何分割突出显示的片段,支持chars、sentence、word三种方式。 |
| boundary_scanner_locale | 用来设置搜索和确定单词边界的本地化设置,此参数使用语言标记的形式(“en-US”, “fr-FR”, “ja-JP”) |
| encoder | 表示代码段应该是HTML编码的:默认(无编码)还是HTML (HTML-转义代码段文本,然后插入高亮标记) |
| fields | 指定检索高亮显示的字段。可以使用通配符来指定字段。例如,可以指定comment_*来获取以comment_开头的所有文本和关键字字段的高亮显示。 |
| force_source | 根据源高亮显示。默认值为false。 |
| fragmenter | 指定文本应如何在突出显示片段中拆分:支持参数simple或者span。 |
| fragment_offset | 控制要开始突出显示的空白。仅在使用fvh highlighter时有效。 |
| fragment_size | 字符中突出显示的片段的大小。默认为100。 |
| highlight_query | 突出显示搜索查询之外的其他查询的匹配项。这在使用重打分查询时特别有用,因为默认情况下高亮显示不会考虑这些问题。 |
| matched_fields | 组合多个匹配结果以突出显示单个字段,对于使用不同方式分析同一字符串的多字段。所有的matched_fields必须将term_vector设置为with_positions_offsets,但是只有将匹配项组合到的字段才会被加载,因此只有将store设置为yes才能使该字段受益。只适用于fvh highlighter。 |
| no_match_size | 如果没有要突出显示的匹配片段,则希望从字段开头返回的文本量。默认为0(不返回任何内容)。 |
| number_of_fragments | 返回的片段的最大数量。如果片段的数量设置为0,则不会返回任何片段。相反,突出显示并返回整个字段内容。当需要突出显示短文本(如标题或地址),但不需要分段时,使用此配置非常方便。如果number_of_fragments为0,则忽略fragment_size。默认为5。 |
| order | 设置为score时,按分数对突出显示的片段进行排序。默认情况下,片段将按照它们在字段中出现的顺序输出(order:none)。将此选项设置为score将首先输出最相关的片段。每个高亮应用自己的逻辑来计算相关性得分。 |
| phrase_limit | 控制文档中所考虑的匹配短语的数量。防止fvh highlighter分析太多的短语和消耗太多的内存。提高限制会增加查询时间并消耗更多内存。默认为256。 |
| pre_tags | 与post_tags一起使用,定义用于突出显示文本的HTML标记。默认情况下,突出显示的文本被包装在和标记中。指定为字符串数组。 |
| post_tags | 与pre_tags一起使用,定义用于突出显示文本的HTML标记。默认情况下,突出显示的文本被包装在和标记中。指定为字符串数组。 |
| require_field_match | 默认情况下,只突出显示包含查询匹配的字段。将require_field_match设置为false以突出显示所有字段。默认值为true。 |
| tags_schema | 设置为使用内置标记模式的样式。 |
| type | 使用的高亮模式,可选项为unified、plain或fvh。默认为unified。 |
自定义高亮
如果我们想使用自定义标签,在高亮属性中给需要高亮的字段加上 pre_tags 和 post_tags 即可。例如,搜索 title 字段中包含关键词 javascript 的书籍并使用自定义 HTML 标签高亮关键词,查询语句如下:
GET /books/_search
{"query": {"match": { "title": "javascript" }},"highlight": {"fields": {"title": {"pre_tags": [""],"post_tags": [""]}}}
}
多字段高亮
关于搜索高亮,还需要掌握如何设置多字段搜索高亮。比如,搜索 title 字段的时候,我们期望 description 字段中的关键字也可以高亮,这时候就需要把 require_field_match 属性的取值设置为 fasle。require_field_match 的默认值为 true,只会高亮匹配的字段。多字段高亮的查询语句如下:
GET /books/_search
{"query": {"match": { "title": "javascript" }},"highlight": {"require_field_match": false,"fields": {"title": {},"description": {}}}
}
SerchType
这部分我有一篇专门介绍ES的读写流程的文章,文章中详细介绍了Es的读写流程和搜索类型。文章地址
Scroll Query
在ES的默认设置中,一次性最多查1万条,而且from+size的方式也不支持1万条后面的数据,当我们想查出几万条的数据时可以使用滚动查询来实现。滚动查询不适合实时的用户请求,而适用于处理大量数据。假如我们有3万条数据我们设置每次查询的size为6000,这样5次滚动就可以查出来3万条数据。滚动查询的结果反映了在发出初始搜索请求时的索引状态,就像从快照读取。对滚动未完成但是文档进行了修改的内容不会对本次滚动查询产生影响。滚动查询需要指定一个窗口过期时间,当第一个窗口返回数据时会带着一个Scroll_Id,当需要滚动到下一个窗口时需要带上这个滚动id。这个滚动ID可能两次窗口返回的是同一个也可能不是同一个,所以要在处理完最后一个窗口的数据时手动清理下,可以立刻释放还没过期但是已经处理完数据的Scrollid从而减少服务器的压力。
//使用_doc去sort得出来的结果,这个执行的效率最快,但是数据就不会有排序,适合用在只想取得所有数据的场景
POST /twitter/_search?scroll=1m
{"size": 100,"query": {"match" : {"title" : "elasticsearch"}},"sort": ["_doc"]
}
//每次发送scroll请求时,都要再重新刷新这个scroll的开启时间,以防不小心超时导致数据取得不完整
POST /_search/scroll
{"scroll" : "1m", "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}
Search After
上面我们讲到使用滚动查询来解决了最大查1万条的问题,但是又有个新问题是滚动查询是基于查询时刻的快照进行查询而不是实时的,假如还没滚动完后续有入了新数据,则查询不到。而SearchAfter则可以实现实时查询。它要求我们必须根据某一个或多个字段排序,且这些字段最好是唯一的,比如ID,这样在查询时就可以不断的根据ID的序号和每次查询的Size控制每批次返回的数据然后往后查询。如果排序字段不唯一可能会有数据重复或缺失的问题。
//根据data和tie_breaker_id排序取前10条
GET twitter/_search
{"size": 10,"query": {"match" : {"title" : "elasticsearch"}},"sort": [{"date": "asc"},{"tie_breaker_id": "asc"} ]
}
//再往后取10条时我们需要在search_after带上第一次查询返回的hits结果中sort里date和tie_breaker_id排序的值
GET twitter/_search
{"size": 10,"query": {"match" : {"title" : "elasticsearch"}},"search_after": [1463538857, "654323"],"sort": [{"date": "asc"},{"tie_breaker_id": "asc"}]
}
Count
统计就是把搜索时的后缀_search换成_count,即可对各种筛选条件的的结果进行统计条数
分析API
ES常用的分析API 有explain用于分析查询得分之类的,还有profile用于性能分析。
//只需要在查询时加上这俩参数即可,默认是false
{ "explain": true, "profile": "true", "query" : {"match" : { "message" : "elasticsearch" }}
}
Mapping
ES中的mapping有些像数据库中的表结构,它定义了每个doucument的存储形式及字段属性之类的。ES中mapping分为静态和动态两种。静态映射是在创建索引时手工指定索引映射,和 SQL 中在建表语句中指定字段属性类似。相比动态映射,通过静态映射可以添加更详细、更精准的配置信息,例子如下:
PUT my_index
{"mappings": {"article": {"_all": {"enabled": false},"properties": {"title": {"type": "text","analyzer": "ik_max_word","search_analyzer": "ik_smart","similarity": "BM25","store": true},"summary": {"type": "text","analyzer": "ik_max_word","search_analyzer": "ik_smart","similarity": "BM25"},"content": {"type": "text","analyzer": "ik_max_word","search_analyzer": "ik_smart","similarity": "BM25","store": true}}}}
}
动态映射是只需要声明一个index但是不指定mapping,当插入数据时如果mapping中没有该字段,ES会动态判断字段类型并修改mapping入库。使用动态 mapping 要结合实际业务需求来综合考虑,如果希望出现未知字段时抛出异常来提醒你注意这一问题,那么开启动态 mapping 并不适用。在 mapping 中可以通过 dynamic 设置来控制是否自动新增字段,接受以下参数:
true:默认值为 true,自动添加字段。
false:忽略新的字段。
strict:严格模式,发现新的字段抛出异常。
PUT books
{"mappings": {"it": {"dynamic": "strict","properties": {"title": {"type": "text"},"publish_date": {"type": "date"}}}}
}
常用的字段类型
ES中有很多类型可用于很多业务场景,此文只记录常用的一部分字段类型。
核心类型
Elasticsearch 字段类型的核心类型有字符串类型、数字类型、日期类型、布尔类型、二进制类型、范围类型等。
text
如果一个字段是要被全文搜索的,比如邮件内容、产品描述、新闻内容,应该使用 text 类型。设置 text 类型以后,字段内容会被分析,在生成倒排索引以前,字符串会被分词器分成一个一个词项(term)。text 类型的字段不用于排序,且很少用于聚合(Terms Aggregation 除外)。
keyword
keyword 类型适用于索引结构化的字段,比如 email 地址、主机名、状态码和标签,通常用于过滤(比如,查找已发布博客中 status 属性为 published 的文章)、排序、聚合。类型为 keyword 的字段只能通过精确值搜索到,区别于 text 类型。
数字类型
数字类型支持 byte、short、integer、long、float、double、half_float 和 scaled_float
日期类型
JSON 中没有日期类型,所以在 Elasticsearch 中的日期可以是以下几种形式:格式化日期的字符串,如 “2015-01-01” 或 “2015/01/01 12:10:30”。但是我们平常不这么用,我们一般使用数字型来保存日期,如20220101
布尔类型
如果一个字段是布尔类型,可接受的值为 true、false。Elasticsearch 5.4 版本以前,可以接受被解释为 true 或 false 的字符串和数字,5.4 版本以后只接受 true、false、“true”、“false”。
binary 类型
binary 类型接受 base64 编码的字符串,默认不存储(这里的不存储是指 store 属性取值为 false),也不可搜索。
range类型
range 类型的使用场景包括网页中的时间选择表单、年龄范围选择表单等,range 类型支持的类型有integer_range、float_range、long_range、double_range、date_range
PUT range_index
{"mappings": {"my_type": {"properties": {"expected_attendees": {"type": "integer_range"},"time_frame": {"type": "date_range","format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"}}}}
}
//索引一条文档,expected_attendees 的取值为 10 到 20,time_frame 的取值是 2015-10-31 12:00:00 至 2015-11-01,命令如下:
PUT my_index/my_type/1
{"expected_attendees": {"gte": 10,"lte": 20},"time_frame": {"gte": "2019-10-31 12:00:00","lte": "2019-11-01"}
}
复合类型
Elasticsearch 字段类型的复合类型有数组类型、对象类型和嵌套类型。
数组类型
Elasticsearch 没有专用的数组类型,默认情况下任何字段都可以包含一个或者多个值,但是一个数组中的值必须是同一种类型。例如:
字符数组:[“one”,“two”]
整型数组:[1,3]
嵌套数组:[1,[2,3]],等价于 [1,2,3]
对象数组:[{“city”:“amsterdam”,“country”:“nederland”},{“city”:“brussels”,“country”:“belgium”}]
动态添加数据时,数组的第一个值的类型决定整个数组的类型。混合数组类型是不支持的,比如:[1,“abc”]。数组可以包含 null 值,空数组 [ ] 会被当作 missing field 对待。在文档中使用 array 类型不需要提前做任何配置,默认支持。例如写入一条带有数组类型的文档,命令如下:
PUT my_index/_doc/1
{"message": "some arrays in this document...","tags": ["elasticsearch", "wow"],"lists": [{"name": "prog_list","description": "programming list"},{"name": "cool_list","description": "cool stuff list"}]
}
//搜索 lists 字段下的 name,命令如下:
GET my_index/_search
{"query": {"match": {"lists.name": "cool_list"}}
}
常见的Mapping属性
es的属性非常多,记录下一些常用的属性。
| 属性 | 说明 |
|---|---|
| analyzer | right-aligned 指定索引和搜索时的分析器,如果同时指定 search_analyzer 则搜索时会优先使用 search_analyzer。一般都是在查询时指定分词器,不在定义mapping时指定固定的分词器 |
| boost | right-aligned 定义字段权重,默认是1.0,权重越高,在多个字段同时查询同一个参数时会提高得分,不过一般也是在查询时才指定,且ES5.0以后废弃了定义Mapping时指定 |
| coerce | right-aligned 插入ES的数据并非完全是合规的,例如期望一个字段是数值类型,但是传入的时候以字符串形式传入,这时候可以配置coerce参数来强制ES接收 |
| copy_to | 拷贝字段值到其他字段上,可以把多个字段拷贝到一个字段上也可以把一个字段拷贝到多个字段上,copy_to的目标字段是个数组形式,可以作为单个字段进行查询 |
| dynamic | 是否启用动态映射,默认是true,可以设置为false或者是 strict |
| ignore_above | 忽略字段索引阈值,相当于varcahr类型后的数字。长于ignore_above设置的字符串将不会被索引或存储。对于字符串数组,ignore_above将分别应用于每个数组元素,并且长度超过的字符串元素ignore_above不会被索引或存储。 |
| norms | 是否使用归一化因子,可选值为 true |
| fileds | 映射多字段,有时候我们需要把同一个字段的值既用于term查询也用于分词查询,此时我们可以使用fileds映射为两种类型 |
| null_value | 空值或空数组可以设置默认值,当传来的值是null或空数组时使用默认值代替 |
| search_analyzer | 搜索时的分词器,既可以在定义map时指定也可以在执行query查询时指定 |
| store | 是否存储指定字段,可选值为 true |
coerce
插入ES的数据并非完全是合规的,例如期望一个字段是数值类型,但是传入的时候以字符串形式传入,这时候可以配置coerce参数来强制ES接收
PUT test
{"mappings": {"properties": {"a":{"type": "integer"},"b":{"type": "integer","coerce":false}}}
}
//正常
PUT test/_doc/1
{"a":"10"
}
//报错
PUT test/_doc/2
{"b":"10"
}
coecre参数在设置之后还可以通过api更改
PUT /test/_mapping
{"properties":{"b":{"type":"integer","coerce":true}}
}
//索引级别的coerce参数设置
PUT /test
{"settings": {"index.mapping.coerce":false}, "mappings": {"properties": {"a":{"type": "integer","coerce":true},"b":{"type": "integer"}}}
}
//正常
PUT test/_doc/1
{"a":"10"
}
//将报错
PUT test/_doc/2
{"b":"20"
}
copy_to
copy_to参数允许将多个值拷贝到一个组合字段中(可以作为单个字段进行查询)
PUT test
{"mappings": {"properties": {"a":{"type": "keyword","copy_to": "full_search_field"},"b":{"type": "keyword","copy_to": "full_search_field"},"c":{"type": "text"}}}
}
//从full_search_field字段中模糊搜索多个词 相当于一次性搜索多个字段 注意搜索的结果并不会包含full_search_field这个字段,返回的是copy_to之前的所有字段
GET test/_search
{"query": {"bool": {"must": [{"wildcard": {"full_search_field": {"value": "*汽油*"}}},{"wildcard": {"full_search_field": {"value": "*山东*"}}},{ "wildcard": {"full_search_field": {"value": "*化工*"}}},{"wildcard": {"full_search_field": {"value": "*92*"}}}]}},"size": 1000
}
fields
处于不同的目的,通过不同的方法索引相同的字段通常非常有用。这也是多字段的目的。例如,一个字符串字段可以映射为text字段用于全文本搜索,也可以映射为keyword字段用于排序或聚合。
//address.addressKey字段是address字段的keyword版本。
PUT my_index_test
{"mappings": {"_doc": {"properties": {"address": {"type": "text","fields": {"addressKey": { "type": "keyword"}}}}}}
}
//多字段的另一个应用场景是使用不同的分词器分析相同的字段以求获得更好的相关性。
PUT my_index
{"mappings": {"_doc": {"properties": {"address": { "type": "text","fields": {"engAddress": { "type": "text","analyzer": "english"}}}}}}
}
Aggregations
Query DSL
一般常用的查询有精确和全文。精确查询用term,全文匹配用match。term不会打分,match会根据查询内容分词然后查询并打分。如果不需要打分,尽量使用term查询可以极大的提高效率。
//查询全部的 document
GET index_name/_saerch
{"query": {"match_all": {}}
}
//统计全部document数量
GET index_name/_count
{"query": {"match_all": {}}
}
全文查询
match
/**
* 查询message字段中分词后包含this is a test 四个单词的document,并且包含关系是and,
* 也就是这四个单词都要在message中出现才行,如果是or就出现任一一个就匹配。默认是or。
**/
GET index_name/_search
{"query": {"match" : {"message" : {"query" : "this is a test","operator" : "and"}}}
}
match_phrase_query
//match_phrase query 首先会把 query 内容分词,分词器可以自定义,同时文档还要满足以下两个条件才会被搜索到:1、分词后所有词项都要出现在该字段中(相当于 and 操作)。2、字段中的词项顺序要一致。
GET index_name/_search
{"query": {"match_phrase" : {"message" : {"query" : "this is a test","analyzer" : "my_analyzer"}}}
}
multi_match
//多字段全文查询,不过一般不这么用,在查询时要尽量减少一次性查询的字段,我们可以再定义mapping时通过copy_to属性将要查询的字段复制到一个字段中然后进行搜索
GET /_search
{"query": {"multi_match" : {"query": "this is a test", "fields": [ "subject", "message" ] }}
}
common_terms_query
/*** common_terms query是一个很有用的全文分词查询,比如当我们搜"nelly the elephant as a cartoon"时,* the、as、a这三个单词会在很多的文档中都包含,然后我想降低这三个单词对于最终结果打分的影响,此时就* 可以通过common_terms query来指定低频词和高频词的频率控制打分**/
GET /_search
{"query": {"common": {"body": {"query": "nelly the elephant as a cartoon","cutoff_frequency": 0.001,"low_freq_operator": "and"}}}
}
//上面的查询中文档频率高于 0.1% 的词项将会被当作高频词项,词频之间可以用 low_freq_operator、high_freq_operator 参数连接。设置低频词操作符为“and”使所有的低频词都是必须搜索的,上面的查询等价于
GET books/_search
{"query": {"bool": {"must": [{ "term": { "body": "nelly" } },{ "term": { "body": "elephant" } },{ "term": { "body": "cartoon" } }],"should": [{ "term": { "body": "the" } },{ "term": { "body": "as" } },{ "term": { "body": "a" } }]}}
}
词项查询
全文查询在执行查询之前会分析查询字符串,词项查询时对倒排索引中存储的词项进行精确匹配操作。词项级别的查询通常用于结构化数据,如数字、日期和枚举类型。
term查询
/*** term 查询用来查找指定字段中包含给定单词的文档,term 查询不被解析,* 只有查询词和文档中的词精确匹配才会被搜索到,应用场景为查询人名、地名等需要精准匹配的需求。* 比如,查询 user字段是“Kimchy”的数据,查询命令如下:**/GET index_name/_search
{"query":{"term":{"user":"Kimchy"}}
}
terms查询
/*** terms 查询是 term 查询的升级,可以用来查询文档中包含多个词的文档。比如,想查询* title 字段中包含关键词 “java” 或 “python” 的文档,*/GET index_name/_search
{"query":{"terms":{"title":["java","python"]}}
}
range查询
/*** range query 即范围查询,用于匹配在某一范围内的数值型、日期类型或者字符串型字段的文档,比如搜索哪些书籍的价格在 50 到 100之间、* 哪些书籍的出版时间在 2015 年到 2019 年之间。使用 range 查询只能查询一个字段,不能作用在多个字段上。* range 查询支持的参数有以下几种:* gt 大于,查询范围的最小值,也就是下界,但是不包含临界值。* gte 大于等于,和 gt 的区别在于包含临界值。* lt 小于,查询范围的最大值,也就是上界,但是不包含临界值。* lte 小于等于,和 lt 的区别在于包含临界值。*/
GET index_name/_search
{"query":{"range":{"price":{"gt":50,"lte":70}}}
}
GET index_name/_search
{"query":{"range":{"publish_ time":{"gte":"2015-01-01","lte":"2019-12-31","format":"yyyy-MM-dd"}}}
}
prefix 前缀查询
/*** prefix 查询用于查询某个字段中以给定前缀开始的文档,比如查询 title 中含有以 java 为前缀的关键词* 的文档, 那么含有 java、javascript、javaee 等所有以 java 开头关键词的文档都会被匹配。* 查询 description 字段中包含有以 win 为前缀的关键词的文档,查询语句如下:*/
GET index_name/_search
{"query":{"prefix":{"description":"win"}}
}
whildcard 通配符查询
/*** wildcard query 中文译为通配符查询,支持单字符通配符和多字符通配符,? 用来匹配一个任意字符,* * 用来匹配零个或者多个字符。 * 以 H?tland 为例,Hatland、Hbtland 等都可以匹配,* 但是不能匹配 Htland,? 只能代表一位。H*tland 可以匹配 Htland、Habctland 等,* 可以代* 表 0 至多个字符。和 prefix 查询一样,wildcard 查询的查询性能也不是很高,需要消耗较多的 CPU 资源。*/
GET index_name/_search
{"query": {"wildcard": {"author": "保尔咔嚓*"}}
}
regexp正则查询
/*** Elasticsearch 也支持正则表达式查询,通过 regexp query 可以查询指定字段包含与指定正则表达式* 匹配的文档。可以代表任意字符, “a.c.e” 和 “ab...” 都可以匹配 “abcde”,a{3}b{3}、* a{2,3}b{2,4}、a{2,}{2,} 都可以匹配字符串 “aaabbb”。* 例如需要匹配以 W 开头紧跟着数字的code,使用正则表达式查询构造查询语句如下:*/
GET index_name/_search
{"query": {"regexp": {"code": "W[0-9].+"}}
}
fuzzy模糊纠错查询
/*** fuzzy_query意思是模糊查询,实际可以理解为纠正查询,比如我想搜hello这个单词结果打成了hallo,* fuzzy query 在基于Levenshtein Edit Distance(莱温斯坦编辑距离)基础上,对索引文档进行模糊搜索。* 当用户输入有错误时,使用这个功能能在一定程度上召回一些和输入相近的文档。*/
GET index_name/_search
{"query": {"fuzzy": {"title": "hallo"}}
}
indices查询
/*** indices查询适用于需要在多个索引之间进行查询的场景,它允许指定一个索引名字列表和内部查询。* indices query 中有 query 和 no_match_query 两部分,query 中用于搜索指定索引列表中的文档,* no_match_query 中的查询条件用于搜索指定索引列表之外的文档。下面的查询语句实现了搜索索引 books、books2 中* title 字段包含关键字 testA,其他索引中 title 字段包含 testB的文档,查询语句如下:*/
GET index_name/_search
{"query": {"indices": {"indices": ["index_nameA", "index_nameB"],"query": {"match": {"title": "testA"}},"no_match_query": {"term": {"title": "testB"}}}}
}
function_score_query
/*** function_score query 可以修改查询的文档得分,这个查询在有些情况下非常有用,比如通过评分* 函数计算文档得分代价较高,可以改用过滤器加自定义评分函数的方式来取代传统的评分方式。* 使用 function_score query,用户需要定义一个查询和一至多个评分函数,评分函数会对查询到的* 每个文档分别计算得分。这个DSL把dataValue值的十分之一开方作为每个文档的得分*/
GET index_name/_search
{"query": {"function_score": {"query": {"match": {"name": "test"}},"script_score": {"inline": "Math.sqrt(doc['dataValue'].value/10)"}}}
}
boosting调整打分查询
/**
* boosting 查询用于需要对两个查询的评分进行调整的场景,boosting 查询会把两个查询封装在一起
* 并降低其中一个查询的评分。boosting 查询包括 positive、negative 和 negative_boost 三个部分,
* positive 中的查询评分保持不变,negative 中的查询会降低文档评分,negative_boost 指明
* negative 中降低的权值。如果我们想对92#汽油2021年之前的历史数据评分降低0.2个百分比,可以构造一个 boosting 查询,
* 查询语句如下:
*/
GET index_name/_search
{"query": {"boosting": {"positive": {"match": {"varietiesName": "汽油92#"}},"negative": {"range": {"publish_time": {"lte": "2021-01-01"}}},"negative_boost": 0.2}}
}
复合bool查询
/**
* bool query 主要通过下列 4 个选项来构建用户想要的布尔查询,每个选项的含义如下:
* must 文档必须匹配 must 选项下的查询条件,相当于逻辑运算的 AND,且参与文档相关度的评分。
* should 文档可以匹配 should 选项下的查询条件也可以不匹配,相当于逻辑运算的 OR,且参与文档相关度的评分。
* must_not 与 must 相反,匹配该选项下的查询条件的文档不会被返回;需要注意的是,must_not 语句不会影响评分,它的作用只是将不相关的文档排除。
* filter 和 must 一样,匹配 filter 选项下的查询条件的文档才会被返回,但是 filter 不评分,只起到过滤功能,与 must_not 相反。
* 这里需要说明的是,每一个子查询都独自地计算文档的相关性得分。一旦他们的得分被计算出来,
* bool 查询就将这些得分进行合并并且返回一个代表整个布尔操作的得分。
* 这里需要强调的是,当同时存在must和should时,must 语句必须匹配,没有 should 语句是必须匹配的。
* 当没有 must只有should语句的时候,至少有一个 should 语句必须匹配。
* bool 查询采用“匹配越多越好(more_matches_is_better)”的机制,因此满足 must 和 should 子句的文档将会合并起来计算分值。
* 在 filter 子句查询中,分值将会都返回 0。must_not 子句并不影响得分,它们存在的意义是排除已经被包含的文档。
* 如上所述,bool 查询的计算得分主要是 must 和 should 子句,它们的计算规则是,把所有符合 must 和 should 的子句得分加起来,
* 然后乘以匹配子句的数量,再除以子句的总数。
*/
GET index_name/_search
{"query": {"bool": {"filter": {"term": { "status": 1 }},"must_not": {"range": { "dataDate": { "gte": 20200101} }},"must": {"match": { "title": "This is a tree" }},"should": [{"match": { "desc": "cow" }},{"match": { "fileName": "file.txt" }}]}}
}
特殊查询
more_like_this近似查询
more_like_this query 可以查询和提供文本类似的文档,通常用于近似文本的推荐等场景。比如我们在百度搜索栏打上天气,会有提示天气预报
/**
*可选的参数及取值说明如下:fields 要匹配的字段,默认是 _all 字段。like 要匹配的文本。min_term_freq 文档中词项的最低频率,默认是 2,低于此频率的文档会被忽略。max_query_terms query 中能包含的最大词项数目,默认为 25。min_doc_freq 最小的文档频率,默认为 5。max_doc_freq 最大文档频率。min_word length 单词的最小长度。max_word length 单词的最大长度。stop_words 停用词列表。analyzer 分词器。minimum_should_match 文档应匹配的最小词项数,默认为 query 分词后词项数的 30%。boost terms 词项的权重。include 是否把输入文档作为结果返回。boost 整个 query 的权重,默认为 1.0。
*/
GET index_name/_search
{"query": {"more_like_this" : {"fields" : ["title", "description"],"like" : "Once upon a time","min_term_freq" : 1,"max_query_terms" : 12}}
}
script查询
ES是支持脚本查询,但是常用的就是painless语法,我曾有个业务场景是对已有的index增加字段,当修改了mapping之后,ES只会影响修改后新插入的数据,而已有的历史数据是没有新增字段的,那我就需要给历史数据的新增字段上刷上默认值。此时用到了scrpit语法来更新。有关painless的介绍参考官方文档
//查询indexName下type=num1的 且value>1的数据
GET indexName/_search
{"query": {"bool" : {"filter" : {"script" : {"script" : {"source": "doc['num1'].value > 1","lang": "painless"}}}}}
}//这个例子就是刚才提到的刷默认值
POST indexName/_update_by_query
{"script": {"lang": "painless","source": "if ((!ctx._source.containsKey(\"marketId\")) && (ctx._source.type== 5 || ctx._source.type== 6)) {ctx._source.marketId= 0} else if ((!ctx._source.containsKey(\"regionId\")) && (ctx._source.type== 3)) {ctx._source.regionId= 0}"}
}
聚合查询
Elasticsearch 的聚合功能十分强大,可在数据上做复杂的分析统计。它提供的聚合分析功能有指标聚合(metrics aggregations)、桶聚合(bucket aggregations)、管道聚合(pipeline aggregations)和矩阵聚合(matrix aggregations)四大类,但是矩阵聚合ES的官方文档中提示道将来的版本可能废弃,而且不常用,此处不做记录。
聚合的目录结构如下:
"aggregations" : { //最外层的聚合键,也可以缩写为 aggs"" : { //聚合的名字 随便写"" : { //聚合的类型,指标相关的,如 max、min、avg、sum,桶相关的 terms、filter 等<aggregation_body>}[,"meta" : { [<meta_data_body>] } ]?[,"aggregations" : { [<sub_aggregation>]+ } ] //在聚合里面在定义子聚合 }[,"" : { ... } ]*
}
指标聚合
指标聚合常用于统计,比如查询每个业务类型下的数据的最小日期和最大日期、分组求某个数据的最大值、最小值、平均值、数量和等等。
avg聚合
// 查询grade字段的均值
GET /exams/_search?size=0
{"aggs" : {"avg_grade" : { "avg" : { "field" : "grade","missing": 60} }}
}
min聚合
// 查询price的最小值
GET /sales/_search?size=0
{"aggs" : {"min_price" : { "min" : { "field" : "price" } }}
}
max聚合
//查询price的最大值 和最大值乘2的结果
GET /sales/_search?size=0
{"aggs" : {"max_price" : { "max" : { "field" : "price" } },"max_price_2" : { "max" : { "field" : "price","script": {"source": "_value * 2.0"} } }}
}
sum聚合
// 查询type匹配hat的文档然后对price字段求和
GET /exams/_search?size=0
{"query" : {"constant_score" : {"filter" : {"match" : { "type" : "hat" }}}},"aggs" : {"hat_prices" : { "sum" : { "field" : "price" } }}
}
value_count统计
//查询document中包含times字段的数量,ES的document是动态的,当有新字段的document动态写入到ES中去,历史数据是不包含这个新字段的,所以有时需要统计。
GET /books/_search?size=0
{"size": 0,"query": {"match_all": {}},"aggs": {"aggName": {"value_count": {"field": "times"}}}
}
cardinality统计
//相当于对language字段执行去重count统计。当字段类型为kext时,要通过字段名.keyword来转为keyword类型
GET /books/_search?size=0
{"aggs" : {"all_lan" : { "cardinality" : { "field" : "language" } },"title_cnt" : { "cardinality" : { "field" : "title.keyword" } }}
}
stats统计
//stats查询可以一次性查询出grade字段的 count、max、min、avg 和 sum 这 5 个指标
GET /exams/_search?size=0
{"aggs" : {"grades_stats" : { "stats" : { "field" : "grade" } }}
}
//聚合结果如下
{..."aggregations": {"grades_stats": {"count": 2,"min": 50.0,"max": 100.0,"avg": 75.0,"sum": 150.0}}
}
Extend stats统计
//Extended Stats Aggregation 用于高级统计,和基本统计功能类似,但是会比基本统计多出以下几个统计结果,sum_of_squares(平方和)、variance(方差)、std_deviation(标准差)、std_deviation_bounds(平均值加/减两个标准差的区间)。在 exams 索引中对 grade 字段进行分数相关的高级统计
GET /exams/_search?size=0
{"aggs" : {"grades_stats" : { "extended_stats" : { "field" : "grade" } }}
}
//聚合结果如下
{..."aggregations": {"grades_stats": {"count": 2,"min": 50.0,"max": 100.0,"avg": 75.0,"sum": 150.0,"sum_of_squares": 12500.0,"variance": 625.0,"std_deviation": 25.0,"std_deviation_bounds": {"upper": 125.0,"lower": 25.0}}}
}
top_hits 查询
//top_hits查询是用于取TopN的,下面的查询index为sales的document,按照Type字段的值分成3组,每组根据date倒序排序取Top1的date和price字段。top_hit查询还经常用于分桶查询,下面会详细讲到。
POST /sales/_search?size=0
{"aggs": {"top_tags": {"terms": {"field": "type","size": 3},"aggs": {"top_sales_hits": {"top_hits": {"sort": [{"date": {"order": "desc"}}],"_source": {"includes": [ "date", "price" ]},"size" : 1}}}}}
}
上面我们记录了指标聚合,下面继续讲一下常用的分桶聚合。指标聚合实际上也是分桶聚合,它是把整个index看做一个桶然后进行各种统计,而分桶聚合是包含了继承关系的一种聚合。
分桶聚合
分桶聚合是把满足条件的数据分成多个桶,大桶套小桶的感觉。比如我们将学校的学生分桶聚合,按年级分桶分成了1~5个桶,再每个桶内还可以按性别分成男女两个子桶,在子桶内还能再按照姓氏分桶。上面讲到的指标聚合实际上是在学校这个层面进行分桶聚合汇总统计,有时不能满足我们的需求,因此使用分桶聚合来统计。
分桶聚合的DSL看起来和指标聚合其实是一模一样的,区别就是桶内可以继续分子桶,ES官方文档中讲解的其实就是按照什么条件来划分子桶,此处记录几种常用的划分子桶的方式。
terms分桶
/**
* 直接一步到位记录一个比较复杂全面的分桶取TopN
*/
{"size": 0,//分桶前我们可以先进行查询,根据查询条件查出符合预期的数据,注意上一行的size,如果不需要query得到的原始文档,只是用于查询出之后聚合使用,那size要设置为0"query": {"bool": {"filter": [{"terms": {"businessType": [3],"boost": 1}},{"range":{"dataDate":{"gte":20210901}}},{"terms": {"businessTypeTabId": [10628,9253,7669,9251,6878],"boost": 1}},{"terms": {"indexPriceType": [2],"boost": 1}}],"adjust_pure_negative": true,"boost": 1}},//将上面query得到的文档进行分桶"aggregations": {//aggregations也可以简写为aggs"agg_business_type": { //这是第一层的桶名称"terms": { //根据businessType字段分成了1个桶,注意我上面query中businessType中就写了一个数字3"field": "businessType",//如果size比实际的桶数要大则没啥影响,但如果比实际的桶数要小则数据会查出来不全"size": 1},"aggregations": {//在agg_business_type桶的基础下再继续分子桶"agg_business_id": {"terms": {//此处我们有在每个agg_business_type的桶内按照businessTypeTabId字段的不同值分成了1000个桶"field": "businessTypeTabId",//注意此处size的大小要结合你实际数据的量,如果写小了会造成数据丢失"size": 1000},"aggregations": {"aggs_date": {//此处继续按照dataDate日期来分桶倒序排序取Top1的documnet中的部分字段"top_hits": {"from": 0,"size": 1,"version": false,"explain": false,"sort": [{"dataDate": {"order": "desc"}}],//这里写了返回值中都包含哪些字段"_source": ["businessTypeTabId","varietiesName","dataValue","remark","regionName"]}}}}}}}
}
filter分桶和filters分桶
Filter Aggregation 是过滤器聚合,可以把符合过滤器中的条件的文档分到一个桶中,即是单分组聚合。
Filters Aggregation 是多过滤器聚合,可以把符合多个过滤条件的文档分到不同的桶中,即每个分组关联一个过滤条件,并收集所有满足自身过滤条件的文档。
filter分桶是上来就先把所有数据放到一个大桶内再过滤出符合条件的继续分桶。除此之外我们也可以像文章上面terms分桶中提到的先查询后分桶。
{"aggs": {"age_terms": {"filter": {"match":{"gender":"F"}},"aggs": {"avg_age": {"avg": {"field": "age"}}}}}
}
//filters 聚合
{"size": 0,"aggs": {"messages": {"filters": {"filters": {"errors": { "match": { "body": "error" } },"warnings": { "match": { "body": "warning" } }}}}}
}
Range分桶
按照年龄范围分桶,25以下一组,25~35一组,35以上一组。
{"aggs": {"age_range": {"range": {"field": "age","ranges": [{"to": 25},{"from": 25,"to": 35},{"from": 35}]},"aggs": {"bmax": {"max": {"field": "balance"}}}}}}
}
Pipeline aggregations
Pipeline aggregations作用是在分桶的基础上进行再排序、汇总、过滤之类的操作。有些像SQL里的select * from (Select * from group by xxx) as child_table where xxx。目前我的业务场景用到的较少,有兴趣的参考下官方文档。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
