Query DSL 官方文档-ES查询语法
0.基础操作
查看集群的状况:GET /_cat
查看集群健康状态:1 2 # v是用来要求在结果中返回表头 GET / _cat/ health?v
health状态值说明1 2 3 Green - 最佳状态 Yellow - 数据和集群可用,但是集群的备份有的是坏的 Red - 数据和集群都不可用
查看节点状态:GET /_cat/nodes?v
查看所有索引:GET /_cat/indices?v
列出每个Index所包含的Type:1 2 # 增加pretty参数,会让Es美化输出(pretty- print)JSON响应以便更加容易阅读 GET / _mapping?pretty= true
1.索引(index) 注意:索引的名称必须是小写 的,不可重名
查询所有index:GET /_cat/indices?v
查询index:GET /test
or GET /test*
新增index(新增一个名为test的index):PUT /test
删除index(删除test这个index):DELETE /test
2.文档(document) index中的单条记录称之为document。
2.1 新增document 2.1.1 新增时指定ID(用PUT) 格式:/Index/Type/Id
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 # 指定了文档ID= 1 PUT / test/ _doc/ 1 { "name": "lxy1", "ename": "lxy1", "age": 18 , "about": "I am a good coder", "interest": [ "eat", "coding" ], "interest_count": 2 } # 指定了文档ID= 2 PUT / test/ _doc/ 2 { "name": "lxy2", "ename": "lxy2", "age": 29 , "about": "I am a tester", "interest": [ "eat", "testing" ], "interest_count": 2 , "job": null } # 指定了文档ID= 3 PUT / test/ _doc/ 3 { "name": "lxy3", "ename": "lxy3", "age": 3 , "about": "I am a baby", "interest": [ "eat", "play", "sleep" ], "interest_count": 3 , "job": [] }
2.1.2 由ES自动生成ID(用POST新增文档,ES自动为文档生成20位的ID) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 POST / test/ _doc { "name": "小康康", "age": 2 , "about": "I am a cat", "interest": [ "eat" ], "interest_count": 1 , "job": [ null , "get mouse" ] }
2.1.3 指定操作类型参数:op_type 新增文档时,可以同时指定op_type,当es里已经存在相同ID的文档时,就会新增失败
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # 指定操作类型op_type= create # 执行会报:document already exists PUT / test/ _doc/ 3 ?op_type= create { "name": "lxy3", "ename": "lxy3", "age": 3 , "about": "I am a baby", "interest": [ "eat", "play", "sleep" ], "interest_count": 3 , "job": [] } # 等价于:指定是create 文档 PUT / test/ _doc/ 3 / _create { "name": "lxy3", "ename": "lxy3", "age": 3 , "about": "I am a baby", "interest": [ "eat", "play", "sleep" ], "interest_count": 3 , "job": [] }
2.1.4 指定文档版本号version(新版本改为seq_no和primary_term,所以此处直接介绍seq_no方式) 每个文档 的版本号”_version”起始值都为1,每次对当前文档成功操作后都加1
而序列号”_seq_no”则可以看做是索引 的信息,在第一次为索引插入数据时为0,后面每对索引内数据操作成功一次加1,并且文档会记录是第几次操作使它成为现在的情况的。
1 2 3 4 5 6 #先新增一个ID= 100 的文档 PUT / test/ _doc/ 100 { "name": "羽哥" } # 执行结果可以看到_seq_no
当我们要修改时,带上if_seq_no参数(值是我们认定的更新前,当前文档的_seq_no值),如果我们指定的与ES里文档相等,则能成功,否则报异常。
1 2 3 4 5 6 # 指定更新前文档_seq_no= 17 PUT / test/ _doc/ 100 ?if_seq_no= 17 & if_primary_term= 1 { "name": "羽哥2" } # id= 100 的文档的_seq_no= 18 ,所以修改成功,_seq_no变为18
2.2 查询document 2.2.1 查询index的所有document GET /test/_search
2.2.2 根据id查询document(查询id=1的文档) GET /test/_doc/1?pretty
2.2.3 通过size指定返回结果条数 1 2 3 4 5 6 # 只查询一条,默认返回10 条 GET / test/ _search{ "query": {"match_all": {}}, "size": 1 }
2.2.4 用sort排序查询 1 2 3 4 5 6 7 8 9 10 11 12 # 对age进行倒序查询 GET / test/ _search{ "query": {"match_all": {}}, "sort": [{"age": {"order": "desc"}}] } # 或者 GET / test/ _search{ "query": {"match_all": {}}, "sort": {"age": { "order": "desc"}} }
2.2.5 用from和size分页查询 1 2 3 4 5 6 7 8 # 查询前2 条数据, from 是从0 开始的 GET / test/ _search{ "query": {"match_all": {}}, "sort": [{"age": {"order": "desc"}}], "from": 0 , "size": 2 }
2.2.6 根据ID判断是否存在 1 2 # 存在返回200 ,不存在返回404 HEAD / test/ _doc/ 1
2.2.7 用_source参数过滤返回的字段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 # 不返回_source GET / test/ _doc/ 1 ?_source= false # 只返回_source GET / test/ _doc/ 1 / _source# 只返回ID= 1 文档的name,age GET / test/ _doc/ 1 ?_source= name,age# 只返回_source中某些字段 GET / test/ _search{ "_source": { "includes": ["name", "age"] } } # 如果只是_source_includes可以简写如下 GET / test/ _search{ "_source": ["name", "age"] } # 只返回以a开头的字段 GET / test/ _search{ "_source": ["w*"] } # _source_excludes:屏蔽部分字段 GET / test/ _search{ "_source": { "excludes": ["name", "age"] } } # 都有时,excludes优先级> includes优先级 # includes包含了name,但excludes也包含了name。返回结果中只有age。 GET / test/ _search{ "_source": { "includes": ["name", "age"], "excludes": ["name", "interest"] } } # 只返回a开头并且不是e结尾的字段 GET / test/ _search{ "_source": { "includes": "a*", "excludes": "*e" } } # 带上查询条件和_source过滤 GET / test/ _search{ "query": { "bool": { "filter": { "term": { "age": 3 } } } }, "_source": ["name", "age"] } # 只排除某些字段 GET / test/ _doc/ 1 ?_source_excludes= about,interest# 判断_source是否存在,存在返回200 ,不存在返回404 HEAD test/ _doc/ 1 / _source # 在ES7.X中,可以有如下写法 GET test/ _source/ 1 GET test/ _source/ 1 ?_source= name
2.3 修改document 2.3.1 用PUT替换document 1 2 3 4 5 6 7 8 9 10 # 首先查看id= 1 的文档 GET / test/ _doc/ 1 # 用PUT方式更新id= 1 的文档 PUT / test/ _doc/ 1 { "name": "羽哥" } # 更新后再查看id= 1 的文档 GET / test/ _doc/ 1 # 通过上面发现用PUT是替换了整个文档,而不是更新name这一个字段
2.3.2 用POST更新document 先恢复id=1的文档数据,可通过再次执行2.1.1中id=1的语句
1 2 3 4 5 6 7 8 9 10 # 修改name,并新增interesting这个字段 # POST / pigg/ _doc/ 1 / _update,新版本格式改为/ {index}/ _update/ {id} POST / test/ _update/ 1 { "doc":{ "name": "羽哥", "interesting": "watching TV" } } # 这时发现用POST更新的是文档的局部字段,原来有的字段更新,没有的字段则新增这个字段
2.3.3 用script更新document 1 2 3 4 5 # 查询当前id= 1 的人的age是18 ,现在要对age加1 POST / test/ _update/ 1 { "script": "ctx._source.age += 1" }
2.3.4 用script更新document其他例子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 # age-1 POST / test/ _update/ 1 { "script": { "source": "ctx._source.age -= 1" } } # age= 30 POST / test/ _update/ 1 { "script": { "source": "ctx._source.age = 30" } } # name= 'witerking' POST / test/ _update/ 1 { "script": { "source": "ctx._source.name = 'lxyking'" } } # 给数组添加一个值,就算存在也添加,语言用painless POST / test/ _update/ 1 { "script": { "source": "ctx._source.interest.add(params.interest)", "lang": "painless", "params": { "interest": "sleep" } } } # 给数组添加一个值,不存在才添加,语言用painless POST / test/ _update/ 1 { "script": { "source": "if(!ctx._source.interest.contains(params.interest)) {ctx._source.interest.add(params.interest)}", "lang": "painless", "params": { "interest": "sleeping" } } } # 给文档添加一个新字段new_name POST / test/ _update/ 1 { "script": { "source": "ctx._source.new_name = '天才'", "lang": "painless" } } # 字段直接复制 POST / test/ _update/ 1 { "script": { "source": "ctx._source.new_name = ctx._source.name", "lang": "painless" } } # 删除一个字段,不修改mapping POST / test/ _update/ 1 { "script": "ctx._source.remove('new_name')" }
2.4 删除document 2.4.1 根据ID删除 DELETE /test/_doc/1
2.4.2 根据查询结果进行删除(delete_by_query) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # delete_by_query POST / test/ _delete_by_query { "query": { "term": { "_id": "5" } } } # 忽略版本冲突,继续执行删除操作 POST / pig/ _delete_by_query?conflicts= proceed { "query": { "term": { "_id": "5" } } }
3.DSL文档查询 3.1 准备测试数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 PUT / test/ _doc/ 1 { "name": "lxy", "age": 18 , "hometown": "杭州", "gender": "male", "interesting": "watching TV" } PUT / test/ _doc/ 2 { "name": "yfw", "age": 28 , "hometown": "福建", "gender": "female", "interesting": "watching movie" } PUT / test/ _doc/ 3 { "name": "yb", "age": 30 , "hometown": "苏州", "gender": "female" }
3.2 查询部分字段 1 2 3 4 GET / test/ _search{ "_source": ["name", "age"] }
3.3 match 如果有多个搜索关键字,Elastic认为它们是or的关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 查询interesting匹配"watching TV"(查询watching或者TV) GET / test/ _search{ "query": { "match": { "interesting": "watching TV" } } } # 或者通过9200 端口的HTTP形式查询 curl - XPOST 'localhost:9200/test/_search?pretty' - d ' { "query" : { "match" : { "interesting" : "watching TV" }} }'
看到结果也返回了”interesting”= “watching movie”的数据, 其中id=1的_score要比id=2的要高,这个_score说明是匹配的程度,id=1的要比id=2的更加匹配
1 2 3 4 5 6 7 8 9 # 查询interesting匹配"TV"或者"moive" GET / test/ _search{ "query": { "match": { "interesting": "TV movie" } } }
上面结果命中了2个人, “_score”都是0.6931472,说明匹配度两者相同
1 2 3 4 5 6 7 8 9 # 查询age= 30 的 GET / test/ _search{ "query": { "match": { "age": 30 } } }
3.4 match_phrase 1 2 3 4 5 6 7 8 9 # 短语查询,这个会将"watching TV"作为一个短语去进行匹配查询(精确匹配) GET / test/ _search{ "query": { "match_phrase": { "interesting": "watching TV" } } }
3.5 must(且) 1 2 3 4 5 6 7 8 9 10 11 12 # 查询interesting匹配"watching TV",并且gender匹配"female" GET / test/ _search{ "query": { "bool": { "must": [ { "match": {"interesting": "watching TV" }}, { "match": {"gender": "female" }} ] } } }
{ "match": {"interesting": "watching TV" }}
这条件语句能返回id=1或2的数据,{ "match": {"gender": "female" }}
这条件语句能返回id=2或3的数据,这两条语句是且的关系,所有最后返回id=2的数据
3.6 should(或)
3.6.1 查询interesting匹配”watching mobile”,或 gender匹配”female”1 2 3 4 5 6 7 8 9 10 11 12 #查询interesting匹配"watching mobile",或gender匹配"female" GET / test/ _search{ "query": { "bool": { "should": [ { "match": {"interesting": "watching movie" }}, { "match": {"gender": "female" }} ] } } }
从上面结果看,id=2的数据匹配得分最高,另外两个匹配度相同,注意这次查询的是”watching mobile”,不是”watching TV”
3.6.2 minimum_should_match 这个是指或的条件,必须满足多少条1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # 下面的minimum_should_match= 2 ,所以一条都查不到 GET / test/ _search{ "query": { "bool": { "should": [ { "range": { "age": { "gte": 0 , "lte": 3 } } }, { "match": { "hometown.keyword": "杭州" } } ], "minimum_should_match": 2 } } }
3.7 must_not(非) 1 2 3 4 5 6 7 8 9 10 11 12 # 查询interesting不匹配"watching TV",并且gender不匹配"female" GET / test/ _search{ "query": { "bool": { "must_not": [ { "match": {"interesting": "watching TV"} }, { "match": {"gender": "female"} } ] } } }
查询结果是空的,没有匹配的数据3.8 组合多查询bool 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # 用SQL 表示如下:where gender != 'male' and ((age >= 0 and age <= 3 ) or hometown = '福建' ) GET / test/ _search{ "query": { "bool": { "must_not": [{"match": {"gender": "male"}}], "should": [ { "range": { "age": { "gte": 0 , "lte": 3 } } }, { "match": {"hometown.keyword": "福建"} } ] } } }
3.9 不影响评分的filter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 如果不希望age的比较影响评分,可以放到filter 里 GET / test/ _search{ "query": { "bool": { "must": [{"match": {"interesting": "watching TV"}}], "filter": { "range": { "age": { "gte": 10 , "lte": 29 } } } } } }
4.DSL聚合查询 4.1 准备测试数据 数据同3.1,此处略
4.2 group by(举例: 按照性别分组) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # SQL 描述 SELECT gender,COUNT (1 ) FROM test GROUP BY gender ORDER BY COUNT (1 ) DESC # DSL描述 GET / test/ _search{ "size": 0 , "aggs": { "group_by_gender": { "terms": { "field": "gender.keyword" } } } }
4.3 avg(举例: 求平均年龄) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # SQL 描述 SELECT AVG (age) FROM test# DSL描述 # "size":0 不返回查询结果,仅返回聚合结果 GET / test/ _search{ "size":0 , "aggs": { "avg_age": { "avg": { "field": "age" } } } }
4.4 avg和group by组合(举例: 求不同性别的平均年龄) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # SQL 描述 SELECT gender, AVG (age) as 'avg_age' FROM testGROUP BY gender ORDER BY 'avg_age' DESC # DSL描述 GET / test/ _search{ "size": 0 , "aggs": { "group_by_gender":{ "terms": { "field": "gender.keyword", "order": { "avg_age": "desc" } }, "aggs": { "avg_age": { "avg": { "field": "age" } } } } } }
4.5 date_range 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 查询最近7 天的数据 GET / test/ _search{ "size": 0 , "aggs": { "range": { "date_range": { "field": "date", "format": "yyyy-MM-dd", "ranges": [ { "from": "now-7d/d", "to": "now" } ] } } } }
4.6 date_histogram 日期直方图 基于日期类型字段,以日期间隔来分桶聚合 。可用的时间间隔类型为:year、quarter、month、week、day、hour、minute、second,其中,除了year、quarter 和 month,其余可用小数形式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 # 统计每个月的人数 GET / test/ _search{ "size": 0 , "aggs": { "dates": { "date_histogram": { "field": "date", "interval": "month", "format": "yyyy-MM-dd" } } } } # 去除统计的doc_count= 0 的数据 GET / test/ _search{ "size": 0 , "aggs": { "dates": { "date_histogram": { "field": "date", "interval": "month", "format": "yyyy-MM", "min_doc_count": 1 } } } } # 先确定日期范围,然后再统计(统计2021 年1 月之后的数据) GET / test/ _search{ "size": 0 , "query": { "bool": { "filter": { "range": { "date": { "gte": "2021-01-01" } } } } }, "aggs": { "dates": { "date_histogram": { "field": "date", "interval": "month", "format": "yyyy-MM", "min_doc_count": 1 } } } }
4.7 missing:缺失值的桶聚合 1 2 3 4 5 6 7 8 9 # 总人数是7 ,有friend是2 个人,所以返回值是5 POST / test/ _search?size= 0 { "aggs": { "account_without_friend": { "missing": {"field": "friend.keyword"} } } }
4.8 collapse:折叠查询 1 2 3 4 5 6 7 8 9 10 11 12 13 # 按照阵营group 分组,取每个group 里年龄最大的人,同时展示每个group 里年龄前2 名的人。 GET / test/ _search{ "collapse": { "field": "group.keyword", "inner_hits": { "name": "old_age", "size": 2 , "sort": [{"age": "desc"}] } }, "sort": [{"age": {"order": "desc"}}] }
ES的聚合功能非常强大,远不止上面的例子,除了AVG还有MAX、MIN、SUM、COUNT、STATS等
5.桶聚合(Terms Aggregation) 桶聚合的种类很多,一篇短文难以覆盖,这篇先介绍Terms Aggregation(按字段分组),类似MySQL中的Group By,它是最常用的聚合方式。注意用于聚合的字段不能是text类型 。
5.1 准备测试数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 PUT / user / _doc/ 1 { "id": "1", "name": "张一", "dept": "web", "path": "dept1,man1", "birthday": "2008-11-16", "status": "1" } PUT / user / _doc/ 2 { "id": "2", "name": "张二", "dept": "web", "path": "dept1,man2", "birthday": "2008-12-17", "status": "1" } PUT / user / _doc/ 3 { "id": "3", "name": "张三", "dept": "web", "path": "dept1,man3", "birthday": "2009-10-10", "status": "1" } PUT / user / _doc/ 4 { "id": "4", "name": "李四", "dept": "java", "path": "dept2,man4", "birthday": "2012-01-01", "status": "1" } PUT / user / _doc/ 5 { "id": "5", "name": "王五", "dept": "java", "path": "dept2,man5", "birthday": "2012-07-01", "status": "0" } PUT / user / _doc/ 6 { "id": "6", "name": "王六", "dept": "data", "status": "0", "path": "dept3,man6", "birthday": "2009-12-12", "gender": "man" }
5.2 Terms Aggregation(根据字段的值分组) Terms聚合用于分组
5.2.1 count:根据dept分组,求每个部门的数量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # SQL GET _sql?format= txt{ "query": "SELECT dept, COUNT(*) num FROM user GROUP BY dept" } # DSL GET / user / _doc/ _search{ "size": 0 , "aggs": { "depts": { "terms": { "field": "dept.keyword" } } } }
5.2.2 count+order:根据dept分组,求每个部门的数量,并排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # SQL GET _sql?format= txt{ "query": "SELECT dept, COUNT(*) num FROM user GROUP BY dept ORDER BY num DESC" } # DSL("_count"按照数量排序,改成"_key"则按照key(这里就是指dept)排序) GET / user / _doc/ _search{ "size": 0 , "aggs": { "depts": { "terms": { "field": "dept.keyword", "order": {"_count": "desc"} } } } }
5.2.3 having:对统计结果过滤(聚合完,再过滤) 1 2 3 4 5 6 7 8 9 10 11 # SQL GET _sql?format= txt{ "query": "SELECT dept, COUNT(*) num FROM user GROUP BY dept HAVING num > 1 ORDER BY num DESC" } # SQL 转DSL语句 POST / _sql/ translate { "query": "SELECT dept, COUNT(*) num FROM user GROUP BY dept HAVING num > 1 ORDER BY num DESC" }
5.2.4 先过滤后,再聚合(类似where) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # SQL GET _sql?format= txt{ "query": "SELECT dept, COUNT(*) num FROM user WHERE status = 1 GROUP BY dept ORDER BY num DESC" } # DSL GET / user / _doc/ _search{ "size": 0 , "query": { "term": { "status": 1 } }, "aggs": { "dept_count": { "terms": { "field": "dept.keyword", "order": { "_count": "desc" } } } } }
6.Index API 6.1 Index API简介 Index API可以把JSON文档新增或修改到指定的索引(Index)中,从而使该JSON文档能够被搜索到。下面举例在一个叫”dept”的索引(Index)中, 新增一个id为1的部门数据。
1 2 3 4 5 PUT / dept/ _doc/ 1 { "name": "部门1", "code": "dept1" }
执行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "_index" : "dept" , "_type" : "_doc" , "_id" : "1" , "_version" : 1 , "result" : "created" , "_shards" : { "total" : 2 , "successful" : 1 , "failed" : 0 }, "_seq_no" : 0 , "_primary_term" : 1 }
7.Query DSL 查询教程 ES提供了一个基于JOSN格式的查询语句-Query DSL,它包含2种类型:叶子查询 和组合查询
7.1 叶子查询 叶子查询语句是在一个特定字段(field)上查询特定的值(value),例如match,term或range查询,它们可以单独使用。例如:
1 2 3 4 5 6 7 8 GET / test/ _search{ "query": { "term": { "age": 18 } } }
7.2 组合查询 组合查询套在别的叶子查询或组合查询外面,它是用来组合逻辑上复杂多样的查询。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 GET / test/ _search{ "query": { "bool": { "must": [ { "term": { "age": 18 } }, { "match": { "name": "lxy" } } ] } } }
7.3 词项查询(Term Level Query) Term Level Query是在字段上查询精确的值,而不是进行全文匹配(比如match)。通常用到的词项查询有term,terms,ids,range,prefix,exists等等,下面一一介绍。
7.3.1 查看mapping
因为没有提前定义mapping,所以是es自生成的mapping。其中name属性如下,name本身是text类型,内部包含一个keyword类型,用精确查询时,就用name.keyword属性。
7.3.2 term (一定要理解term是包含 的意思,不是等于)
term query判断文档的某个字段(field)是否包含某一个确定的值。一般都是用在结构化数据上,比如keyword,int,long,ip,date等,避免用在text类型的字段上,在text上应该用match匹配查询。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # 准备数据 PUT / test/ _doc/ 10 PUT / test/ _doc/ 10 { "name":"刘一", "age":18 } # 查询age= 18 GET / test/ _search{ "query": { "term": { "age": 18 } } } # 查询name.keyword= "刘一"(能查到结果) GET / test/ _search{ "query": { "term": { "name.keyword": "刘一" } } }
为何要避免在text类型上用Term Level Query
name是text类型,默认下es会分词。”刘一”被分词为”刘”,”一”这2个词项,es会根据这2个词项产生对应的倒排索引。
如果term查询name=”刘一”,因为没有”刘一”这个词项,所以下面语句是查不到数据的。
1 2 3 4 5 6 7 8 9 # 查询name= "刘一" GET / test/ _search{ "query": { "term": { "name": "刘一" } } }
7.3.3 terms - terms和term类似,它判断文档的某个字段(field)是否**包含某一个或多个确定的值**(value)。
- value参数是一个数组,里面包含多个你想要查询的值,**只要有一个值命中就算符合**。
- value数组里的最多能放65536个值,如果想修改这个限制,可以修改setting里index.max_terms_count这个配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 # 包含play或sleep GET / test/ _search{ "query": { "terms": { "interest": ["play", "sleep"] } } } # 用must组合查询interest= ["eat", "sleep"] GET / test/ _search{ "query": { "bool": { "must": [ { "term": { "interest.keyword": "eat" } }, { "term": { "interest.keyword": "sleep" } }, { "term": { "interest_count": 2 } } ] } } }
7.3.4 terms_set terms_set查询和terms类似,但它定义了一个最小命中数 。比如value数组里有3个值,最小命中数定义为2,说明字段里的值至少命中数组里的2个 ,才算这个文档复合。而terms查询只要命中数组里任意1个,就算文档符合查询条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 # 至少命中[“eat”, “play”, “sleep”]中2 个 GET / test/ _search{ "query": { "terms_set": { "interest.keyword": { "terms": ["eat","play","sleep"], "minimum_should_match_script": { "source": "2" } } } } } # 用terms_set解决interest= [“eat”, “sleep”] GET / test/ _search{ "query": { "bool": { "must": [ { "terms_set": { "interest.keyword": { "terms": ["eat","sleep"], "minimum_should_match_script": { "source": "2" } } } }, { "term": { "interest_count": {"value": 2 } } } ] } } } # 用terms_set解决interest= [“eat”, “sleep”]且不评分 # 文章最上面讲过用filter context,这样不评分也提高查询性能,以后如果是无需评分的,最好放到filter 里面。 GET / test/ _search{ "query": { "bool": { "filter": { "bool": { "must": [ { "terms_set": { "interest.keyword": { "terms": ["eat","sleep"], "minimum_should_match_script": { "source": "2" } } } }, { "term": { "interest_count": {"value": 2 } } } ] } } } } }
7.3.5 ids 根据文档ID查询文档
1 2 3 4 5 6 7 8 9 # 下面返回1 和4 两个文档,因为id= 100 的不存在。 GET / test/ _search{ "query": { "ids": { "values": ["1","4","100"] } } }
7.3.6 exists 在MySQL中,常用is null
和is not null
,ES用exists
。 - 下面情况认定字段不存在 - 源JSON中的字段是null或[] - 字段在mapping中设置为”index” : false - 字段值的长度超出了mapping中设置的ignore_above - 字段值格式错误,并且mapping中设置的了ignore_malformed - 下面特殊情况认定字段存在 - 空字符串,例如””或”-“ - 包含null和另一个值的数组,例如[null, “foo”] - 在mapping中设置的自定义null-value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # 查询interesting值存在的文档 GET / test/ _search{ "query": { "exists": { "field": "interesting" } } } # 查询gender值不存在的文档 # 利用must_not+ exists 组合查询,代替之前老版本ES的missing查询 GET / test/ _search{ "query": { "bool": { "must_not": [ { "exists": { "field": "gender" } } ] } } }
7.3.7 range range查询判断某个字段是否在某个范围内。range有如下参数:
参数
说明
gte
>=
gt
>
lte
<=
lt
<
boost
查询权重,默认1.0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 查询age在[3 ,30 )之间的数据 GET / test/ _search{ "query": { "bool": { "filter": { "range": { "age": { "gte": 3 , "lt": 30 } } } } } }
7.3.8 prefix prefix查询返回在指定字段中包含特定前缀的文档。这个就像我们SQL语句的 like “xx%”
。 您可以使用mapping设置中的index_prefixes参数来加快前缀查询的速度。如果启用该参数,Elasticsearch只会为单独的字段索引2到5个字符的前缀。这使Elasticsearch在较大的索引上更有效率执行前缀查询,从而减少成本。
1 2 3 4 5 6 7 8 9 # 查询name以“y”开头的数据。 GET / test/ _search{ "query": { "prefix": { "name.keyword": "y" } } }
7.3.9 wildcard wildcard查询就像MySQL的Like
查询,它查询效率比较低,一般也不用
1 2 3 4 5 6 7 8 9 10 11 12 # 查询hometown里包含“州”的人: GET / test/ _search{ "query": { "wildcard": { "hometown.keyword": { "value": "*州*" } } }, "_source": ["name","hometown"] }
8.Mapping 字段类型(keyword text date numeric) 类似于Mysql的表结构,虽然ES有mapping动态映射,但是它自动生成的不一定是我们期望的。
8.1 keyword
keyword是关键词类型,ES把keyword类型的值当作词根存在倒排索引中 ,不进行分词 。
keyword适合存结构化数据,比如name,age,性别,手机号,status(数据状态),tags(标签),HttpCode(404,200,500)等。
字段常用来精确查询,过滤,排序,聚合 时,应设为keyword,而不是数值型。
最长支持32766个UTF-8类型的字符,但放入倒排索引时,只截取前一段字符串,长度由ignore_above参数决定。8.2 text
text文本类型,如果要对字符串进行分词分析 ,可以设置为text。
ES自带了很多分词器,如果是中文,可以给ES安装ik中文分词插件。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 # 分析name是怎么被分词的 GET / test/ _analyze{ "field": "name", "text": "I am a coder" } # 执行结果:(可以看到这短语被分成4 个词项,其中大写"I"还转换为小写"i") { "tokens" : [ { "token" : "i", "start_offset" : 0 , "end_offset" : 1 , "type" : "<ALPHANUM>", "position" : 0 }, { "token" : "am", "start_offset" : 2 , "end_offset" : 4 , "type" : "<ALPHANUM>", "position" : 1 }, { "token" : "a", "start_offset" : 5 , "end_offset" : 6 , "type" : "<ALPHANUM>", "position" : 2 }, { "token" : "coder", "start_offset" : 7 , "end_offset" : 12 , "type" : "<ALPHANUM>", "position" : 3 } ] }
8.3 Boolean
判断
ES接受的值
真
true,”true”
假
false,”false”,””(空字符串)
8.4 日期类型 date的格式可以被指定的,如果没有特殊指定,默认格式是”strict_date_optional_time||epoch_millis”
这段话可以理解为格式为strict_date_optional_time
或者epoch_millis
8.4.1 什么是epoch_millis? epoch_millis就是从开始纪元(1970-01-01 00:00:00 UTC)开始的毫秒数 -长整型。
8.4.2 什么是strict_date_optional_time? strict_date_optional_time是date_optional_time的严格级别,这个严格指的是年份、月份、天必须分别以4位、2位、2位表示,不足两位的话第一位需用0补齐。
常见的格式有如下:
yyyy
yyyyMM
yyyyMMdd
yyyyMMddHHmmss
yyyy-MM
yyyy-MM-dd
yyyy-MM-ddTHH:mm:ss
yyyy-MM-ddTHH:mm:ss.SSS
yyyy-MM-ddTHH:mm:ss.SSSZ
工作常见到是”yyyy-MM-dd HH:mm:ss”,但是ES是不支持这格式的,需要在dd后面加个T,这个是固定格式。上面最后一个里大写的”Z”表示时区。
如果直接插入”yyyy-MM-dd HH:mm:ss”格式会报错。
8.4.3 如果你就是想用yyyy-MM-dd HH:mm:ss要怎么设置。 date类型,还支持一个参数format,它让我们可以自己定制化日期格式。 比如format配置了“格式A||格式B||格式C”,插入一个值后,会从左往右匹配,直到有一个格式匹配上。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # 创建索引 PUT / test_date_index { "mappings": { "properties": { "birthday": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" } } } } #2020 / 03 / 01 17 :44 :09 的毫秒级时间戳 PUT / test_date_index/ _doc/ 1 { "birthday": 1583055849000 } PUT / test_date_index/ _doc/ 2 { "birthday": "2020-03-01 16:29:41" } PUT / test_date_index/ _doc/ 3 { "birthday": "2020-02-29" } #上面3 条语句都可以保存成功
8.5 数字:Numeric 为了提高性能和减少存储空间,选择一个满足存放你数据的类型就可以,没有必要选择过长的类型。比如各地人口数量,一般用integer存储足够了,没有必要使用long类型。
类型
说明
byte
8位,-128 ~ 127
short
16位,-32768 ~ 32767
integer
32位,-231 ~ 231-1
long
64位,-263 ~ 263-1
float
单精度、32位、符合IEEE 754标准的浮点数
double
双精度、64位、符合IEEE 754标准的浮点数
half_float
16位半精度IEEE 754浮点类型
scaled_float
缩放类型的的浮点数
8.6 二进制类型:binary ES能接受以Base64编码的二进制值,binary字段是不会被分析存储和检索的。因为它的值就是一巨长的乱码,对它分析毫无意义, 它只是被原模原样的存储。
工作中可能用binary存储图像,但情况也不多,用ES存图像不是很好的选择。
8.7 数值范围:range 8.7.1 简介
range字段类型表示一个字段的值是一个数值范围.
例如一个range类型的字段的值是[10, 20], 那么用12来匹配该字段,则会命中该文档.
range字段类型如下:
类型
说明
integer_range
[-231, 231-1 ]
long_range
[-263, 263-1]
float_range
单精度、符合IEEE 754标准的浮点数的范围
double_range
双精度、符合IEEE 754标准的浮点数的范围
date_range
日期范围, 在es内部,日期以64位long类型表示的毫秒数存储
ip_range
IP范围, 支持IPv4和IPv6的范围
区间的开闭
设定一个范围可以有多种选择, 可以包含边界值, 也可以不包含边界值, 还可以一个边界是无界限的.
定义的时候可以用gt, gte, lt, lte表示左右界限的开闭.
下面举例说明:
类型
说明
表达
左闭, 右闭
[10, 20]
gte: 10, lte: 20
左闭, 右开
[10, 20)
gte: 10, lt: 20
左闭, 右无限大
[10, +∞)
gte: 10
8.7.2 准备测试数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 # 创建一个新的索引 PUT test_range { "mappings": { "properties": { "range_of_integer": { "type": "integer_range" }, "range_of_date": { "type": "date_range", "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" } } } } # 新增测试文档 PUT test_range/ _doc/ 1 { "range_of_integer": { "gte": 10 , "lte": 20 }, "range_of_date": { "gte": "2021-05-01 10:20:00", "lte": "2021-05-02" } } PUT test_range/ _doc/ 2 { "range_of_integer": { "gt": 10 , "lt": 20 }, "range_of_date": { "gt": "2021-05-01 10:20:00", "lt": "2021-05-02" } } PUT test_range/ _doc/ 3 { "range_of_integer": { "gt": 20 }, "range_of_date": { "gt": "2021-05-01 10:20:00" } }
8.7.3 使用term查询匹配 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # 查询包含10 的文档, GET test_range/ _search{ "query": { "term": { "range_of_integer": { "value": 10 } } } } # 查询日期范围包含"2021-05-01 10:20:00"的数据 GET test_range/ _search{ "query": { "term": { "range_of_date": { "value": "2021-05-01 10:20:00" } } } }
8.7.4 使用range查询匹配 range查询有个参数relation
,它可以有3个值INTERSECTS, CONTAINS,WITHIN.
类型
说明
INTERSECTS
默认设置, 有交集的文档才匹配
CONTAINS
目标文档的值的范围包含查询条件
WITHIN
查询条件的范围包含目标文档的值的范围
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 GET test_range/ _search{ "query": { "range": { "range_of_integer": { "gte": 18 , "lte": 20 , "relation" : "INTERSECTS" } } } } GET test_range/ _search{ "query": { "range": { "range_of_integer": { "gte": 18 , "lte": 20 , "relation" : "CONTAINS" } } } } GET test_range/ _search{ "query": { "range": { "range_of_integer": { "gte": 18 , "lte": 20 , "relation" : "WITHIN" } } } }
8.8 object elasticsearch支持某个字段存储一个JSON对象, 这个字段类型就是object.
8.9 nested 9.reindex 重建索引(备份数据) reindex不会复制index的设置,所以得先设置好目标索引的setting和mapping,然后再index。
9.1 简单的reindex 1 2 3 4 5 6 7 8 9 10 # source里是源index,dest里是目标索引 POST _reindex { "source": { "index": "test" }, "dest": { "index": "test2" } }
9.2 只创建目标索引中缺少的文档 1 2 3 4 5 6 7 8 9 10 POST _reindex { "source": { "index": "test" }, "dest": { "index": "test2", "op_type": "create" } }
9.3 设置批次大小 1 2 3 4 5 6 7 8 9 10 11 # reindex底层是scroll ,默认批次是1000 条,可以设置多点 POST _reindex { "source": { "index": "test", "size": 5000 }, "dest": { "index": "test2" } }
9.4 遇到冲突继续 1 2 3 4 5 6 7 8 9 10 11 POST _reindex { "conflicts": "proceed", "source": { "index": "test" }, "dest": "index": "test2", "op_type": "create" } }
9.5 只reindex符合条件的数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 POST _reindex { "source": { "index": "test", "query": { "term": { "name.keyword": { "value": "lxy" } } } }, "dest": { "index": "test2" } }
9.6 只同步源index里部分字段 1 2 3 4 5 6 7 8 9 10 POST _reindex { "source": { "index": "test", "_source": ["name", "age"] }, "dest": { "index": "test2" } }
9.7 屏蔽掉不想同步的字段 1 2 3 4 5 6 7 8 9 10 11 12 POST _reindex { "source": { "index": "test", "_source": { "excludes": ["name"] } }, "dest": { "index": "test2" } }
9.8 用script脚本在同步时做数据处理 1 2 3 4 5 6 7 8 9 10 11 12 13 POST _reindex { "source": { "index": "test" }, "dest": { "index": "test2" }, "script": { "source": "ctx._source.age += 2", "lang": "painless" } }
9.9 字段重新命名 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 同样是用script,将name属性重命名为newName POST _reindex { "source": { "index": "test" }, "dest": { "index": "test2" }, "script": { "source": "ctx._source.newName = ctx._source.remove(\"name\")", "lang": "painless" } }