elastic search query DSL 关键字很多,什么场景对应选择合适的DSL并不容易。
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html
查询语句其可以分为两种类型:
子查询语句 Leaf query clauses
子查询子句在特定字段中查找特定值,例如match,term或range查询。
复合查询语句 Compound query clauses
复合查询子句包装其他子查询或复合查询,并用于以逻辑方式组合多个查询(例如bool或dis_max查询),或更改其行为(例如constant_score查询)。
查询语句的使用场景分为两种:query context 以及 filter context
官方文档:Query and filter context
query context
解决问题:该文档与该查询子句的匹配程度如何
其使用相关度评分 relevance score
filter context
解决问题:该文档与该查询子句匹配吗?
其不使用相关度评分 relevance score
另外的一组关键概念:exact value & full text
在创建索引mapping的时候,不同类型的field,可能有的就是full text,有的就是exact value
对于full text,其会创建倒排索引,并且对每一个词进行分词(类似mapping type=text)
对于exact value,其也会创建倒排索引,但是不会进行分词(类似mapping type=keyword)
对于query context,处理的就是类似sql中like的含义,是否包含,其处理的是full text
对于filter context,其处理的就是类似sql中 = 的含义,是否相等,其处理的是exact value
召回率recall
比如你搜索一个java spark,总共有100个doc,能返回多少个doc作为结果,就是召回率,recall
精准度precision
比如你搜索一个java spark,能不能尽可能让包含java spark,或者是java和spark离的很近的doc,排在最前面,precision
下面以 需求 <-> 对应的Query Dsl为例
需求:match 单个field 单值包含
语句:
GET /test_index/test_type/_search
{
"query": {
"match": {
"test_field": "test"
}
}
}
match query是会对查询字段进行分词的
需求:multi_match 多fields 单值包含
语句:
GET /test_index/test_type/_search
{
"query": {
"multi_match": {
"query": "test",
"fields": ["test_field", "test_field1"]
}
}
}
需求:range query 单field范围查询
语句:
GET /company/employee/_search
{
"query": {
"range": {
"age": {
"gte": 30
}
}
}
}
需求:term query 单field单值相等
语句:
GET /test_index/test_type/_search
{
"query": {
"term": {
"test_field": "test hello"
}
}
}
term query把查询字段当作exact value来查询,这个前提是mapping的时候对应term query的字段,mapping type=keyword 不分词
需求:terms query,单field 多值相等
语句:
GET /_search
{
"query": {
"terms":
{ "tag": [ "search", "full_text", "nosql" ]
}
}
}
需求:filter
语句:
GET /company/employee/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"age": {
"gte": 30
}
}
}
}
}
}
“query”下直接放filter是不支持的,要加上constant_score
其另外一种写法
GET /_search
{
"query" : {
"bool" : {
"filter" : {
"term" : {
"author_id" : 1
}
}
}
}
}
filter是不计算相关度分数的
需求:sort
语句:
GET /company/employee/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"age": {
"gte": 30
}
}
}
}
},
"sort": [
{
"join_date": {
"order": "asc"
}
}
]
}
默认是根据相关度分数降序排列的,可以通过sort语句修改
sort:最好在“日期型”和“数字型”字段上排序
需求:单个field 多值匹配
搜索标题中包含java或elasticsearch的blog
语句:
GET /forum/article/_search
{
"query": {
"match": {
"title": "java elasticsearch"
}
}
}
与之前的term query不同,不是搜索exact value,是进行full text全文检索。
match query,是负责进行全文检索的。当然,如果要检索的field,是not_analyzed 不分词keyword类型,那么match query也相当于term query。
也可以这样实现
{
"bool": {
"should": [
{ "term": { "title": "java" }},
{ "term": { "title": "elasticsearch" }}
]
}
}
实际上es底层最终就是转换为上面的语句进行执行的
需求:搜索标题中包含java和elasticsearch的blog
语句:
GET /forum/article/_search
{
"query": {
"match": {
"title": {
"query": "java elasticsearch",
"operator": "and"
}
}
}
}
灵活使用and关键字,如果你是希望所有的搜索关键字都要匹配的,那么就用and,可以实现单纯match query无法实现的效果
另一种写法must+term
{
"bool": {
"must": [
{ "term": { "title": "java" }},
{ "term": { "title": "elasticsearch" }}
]
}
}
这也是底层实际执行的语句
需求:搜索包含java,elasticsearch,spark,hadoop,4个关键字中,至少3个的blog
语句:
GET /forum/article/_search
{
"query": {
"match": {
"title": {
"query": "java elasticsearch spark hadoop",
"minimum_should_match": "75%"
}
}
}
}
minimum_should_match:指定一些关键字中,必须至少匹配其中的多少个关键字,才能作为结果返回
也可以这样实现
GET /forum/article/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "java" }},
{ "match": { "title": "elasticsearch" }},
{ "match": { "title": "hadoop" }},
{ "match": { "title": "spark" }}
],
"minimum_should_match": 3
}
}
}
如果没有must的话,那么should中必须至少匹配一个才可以
通过minimum_should_match进行精准控制,should的4个条件中,至少匹配几个才能作为结果返回
这是底层实际执行的语句
是否存在: exists query
es无法索引或搜索空值null。 当字段设置为null(或空数组或空值数组)时,它被视为该字段没有值。
参考:exists query官方文档
Returns documents that contain an indexed value for a field.
An indexed value may not exist for a document’s field due to a variety of reasons:
null
or []
"index" : false
set in the mappingignore_above
setting in the mappingignore_malformed
was defined in the mappingGET /_search
{
"query": {
"exists": {
"field": "user"
}
}
}
While a field is deemed non-existent if the JSON value is null
or []
, these values will indicate the field does exist:
""
or "-"
null
and another value, such as [null, "foo"]
null-value
, defined in field mapping使用Elasticsearch查询字段的所有唯一值,参考:Query all unique values of a field with Elasticsearch
select distinct full_name from authors;
-- is equivanlent to
select full_name from authors group by full_name;
Kibana语句
GET index_name/_search
{
"aggs": {
"userId": {
"terms": {
"field": "userId",
"size":30
}
}
},
"size": 0
}
需求
过滤出client=android并且client_id的长度不小于40
kibana语句
GET user-tags/_search
{
"query": {
"bool":{
"must": [
{
"match":{
"client":"android"
}
},
{
"constant_score": {
"filter": {
"script": {
"script": "doc['client_id'].getValue().length() >=40"
}
}
}
}
]
}
}
}
bool查询,是一个或多个查子句的组合,包含四种子句:should, must, must_not, filter,其中2 种会影响算分,2 种不影响算分
相关性并不只是全文本检索的专利。也适用于yes | no 的子句,匹配的子句越多,相关性评分
越高。如果多条查询子句被合并为一条复合查询语句,比如bool 查询,则每个查询子句计算
得出的评分会被合并到总的相关性评分中。
需求:title必须包含elasticsearch,content可以包含elasticsearch也可以不包含,包含更好,author_id必须不为111
语句:
GET /website/article/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "elasticsearch"
}
}
],
"should": [
{
"match": {
"content": "elasticsearch"
}
}
],
"must_not": [
{
"match": {
"author_id": 111
}
}
]
}
}
}
说明:should可以影响相关度评分
需求:
GET /test_index/_search
{
"query": {
"bool": {
"must": { "match": { "name": "tom" }},
"should": [
{ "match": { "hired": true }},
{ "bool": {
"must": { "match": { "personality": "good" }},
"must_not": { "match": { "rude": true }}
}}
],
"minimum_should_match": 1
}
}
}
minimum_should_match:
需求:年龄必须大于等于30,同时join_date必须是2016-01-01
语句:
GET /company/employee/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"join_date": "2016-01-01"
}
}
],
"filter": {
"range": {
"age": {
"gte": 30
}
}
}
}
}
}
每个子查询都会计算一个document针对它的相关度分数,然后bool综合所有分数,合并为一个分数,当然filter是不会计算分数的
需求:title中必须包含java,必须不包含spark,hadoop或者elasticsearch可以包含,也可以不包含,包含更好
语句:
GET /forum/article/_search
{
"query": {
"bool": {
"must": { "match": { "title": "java" }},
"must_not": { "match": { "title": "spark" }},
"should": [
{ "match": { "title": "hadoop" }},
{ "match": { "title": "elasticsearch" }}
]
}
}
}
bool组合多个搜索条件,如何计算relevance score
must和should搜索对应的分数,加起来,除以must和should的总数
must是确保说,谁必须有这个关键字,同时会根据这个must的条件去计算出document对这个搜索条件的relevance score
在满足must的基础之上,should中的条件,不匹配也可以,但是如果匹配的更多,那么document的relevance score就会更高
排名第一:java,同时包含should中所有的关键字,hadoop,elasticsearch
排名第二:java,同时包含should中的某个词
排名第三:java,不包含should中的任何关键字
需求:多个field多个值匹配
搜索title或content中包含java或solution的帖子
语句:
GET /forum/article/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "java solution" }},
{ "match": { "content": "java solution" }}
]
}
}
}
需求:
语句:
GET /forum/article/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "blog"
}
}
],
"should": [
{
"match": {
"title": {
"query": "java"
}
}
},
{
"match": {
"title": {
"query": "hadoop"
}
}
},
{
"match": {
"title": {
"query": "elasticsearch",
"boost": 5
}
}
}
]
}
}
}
默认情况下,搜索条件的boost权重都是一样的,都是1
需求:搜索title或content中包含java或solution的帖子,并且某一个field中匹配到了尽可能多的关键词,被排在前面
语句:
GET /forum/article/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "java solution" }},
{ "match": { "content": "java solution" }}
]
}
}
}
best fields策略,就是说,搜索到的结果,应该是某一个field中匹配到了尽可能多的关键词,被排在前面;而不是尽可能多的field匹配到了少数的关键词,排在了前面
dis_max语法,直接取多个query中,分数最高的那一个query的分数即可
需求:搜索title或content中包含java beginner的帖子
语句:
GET /forum/article/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "java beginner" }},
{ "match": { "body": "java beginner" }}
],
"tie_breaker": 0.3
}
}
}
dis_max只取某一个query最大的分数,完全不考虑其他query的分数
tie_breaker参数的意义,在于说,将其他query的分数,乘以tie_breaker,然后综合与最高分数的那个query的分数,综合在一起进行计算
除了取最高分以外,还会考虑其他的query的分数
tie_breaker的值,在0~1之间,是个小数
需求:
GET /forum/article/_search
{
"query": {
"multi_match": {
"query": "java solution",
"type": "best_fields",
"fields": [ "title^2", "content" ],
"tie_breaker": 0.3,
"minimum_should_match": "50%"
}
}
}
长尾,比如你搜索5个关键词,但是很多结果是只匹配1个关键词的,其实跟你想要的结果相差甚远,这些结果就是长尾
minimum_should_match,控制搜索结果的精准度,只有匹配一定数量的关键词的数据,才能返回,去掉长尾
语句:
GET /forum/article/_search
{
"query": {
"multi_match": {
"query": "learning courses",
"type": "most_fields",
"fields": [ "sub_title", "sub_title.std" ]
}
}
}
most-fields策略,主要是说尽可能返回更多field匹配到某个关键词的doc,优先返回回来
语句
GET /forum/article/_search
{
"query": {
"multi_match": {
"query": "Peter Smith",
"type": "cross_fields",
"operator": "and",
"fields": ["author_first_name", "author_last_name"]
}
}
}
解决most_fields进行cross-fields搜索存在的3个弊端
问题 | 解决方案 |
---|---|
问题1:只是找到尽可能多的field匹配的doc,而不是某个field完全匹配的doc | 解决,要求每个term都必须在任何一个field中出现 |
问题2: most_fields,没办法用minimum_should_match去掉长尾数据,就是匹配的特别少的结果 | 解决,既然每个term都要求出现,长尾肯定被去除掉了 |
问题3:TF/IDF算法,比如Peter Smith和Smith Williams,搜索Peter Smith的时候,由于first_name中很少有Smith的,所以query在所有document中的频率很低,得到的分数很高,可能Smith Williams反而会排在Peter Smith前面 | 计算IDF的时候,将每个query在每个field中的IDF都取出来,取最小值,就不会出现极端情况下的极大值了 |
需求:java spark,就靠在一起,中间不能插入任何其他字符,就要搜索出来这种doc
语句:
GET /forum/article/_search
{
"query": {
"match_phrase": {
"content": "java spark"
}
}
}
phrase match,就是要去将多个term作为一个短语,一起去搜索,只有包含这个短语的doc才会作为结果返回。
match是处理不了短语这种场景的。使用match搜索java spark,java的doc也会返回,spark的doc也会返回。
语句:
GET /forum/article/_search
{
"query": {
"match_phrase": {
"title": {
"query": "java spark",
"slop": 1
}
}
}
}
搜索文本中的几个term,要经过几次移动才能与一个document匹配,这个移动的次数,就是slop
match query的性能比phrase match和proximity match(有slop)要高很多。因为后两者都要计算position的距离。
需求:我们希望的是匹配到几个term中的部分,就可以作为结果出来,这样可以提高召回率。同时也希望用上match_phrase根据距离提升分数的功能,让几个term距离越近分数就越高,优先返回,提高精准度。
语句:
GET /forum/article/_search
{
"query": {
"bool": {
"must": {
"match": {
"title": {
"query":"java spark" --> java或spark或java spark,java和spark靠前,但是没法区分java和spark的距离,也许java和spark靠的很近,但是没法排在最前面
}
}
},
"should": {
"match_phrase": { --> 在slop以内,如果java spark能匹配上一个doc,那么就会对doc贡献自己的relevance score,如果java和spark靠的越近,那么就分数越高
"title": {
"query": "java spark",
"slop": 50
}
}
}
}
}
}
需求:近似匹配情况下,仅对前50个doc进行slop移动去匹配,去贡献自己的分数即可,不需要对全部1000个doc都去进行计算和贡献分数
语句:
GET /forum/article/_search
{
"query": {
"match": {
"content": "java spark"
}
},
"rescore": {
"window_size": 50,
"query": {
"rescore_query": {
"match_phrase": {
"content": {
"query": "java spark",
"slop": 50
}
}
}
}
}
}
语句:
GET my_index/my_type/_search
{
"query": {
"prefix": {
"title": {
"value": "C3"
}
}
}
}
前缀搜索,要遍历整个倒排索引,存在性能问题
wildcard需要使用其keyword字段
语句:
GET index_name/_search
{
"query":{
"wildcard": {
"H5Title.keyword": {
"value": "学习强国-社保*"
}
}
}
}
原理应该是:通配符搜索要使用正排索引,keyword借助doc values,而对于text,没有fielddata=true,无法执行,即便可以执行,也是分词的结果,估计对于通配符也不合适
语句:
GET /my_index/my_type/_search
{
"query": {
"regexp": {
"title": "C[0-9].+"
}
}
}
C[0-9].+
[0-9]:指定范围内的数字
[a-z]:指定范围内的字母
.:一个字符
+:前面的正则表达式可以出现一次或多次
查询长度超过50位的搜索词内容
GET xxx/_search
{
"query":{
"bool":{
"must":{
"match_all":{
}
},
"filter":[
{
"regexp":{
"word":{
"value":".{50,}"
}
}},
{"range": {
"datetime": {
"gte": "2019-01-01T08:00:00.000Z",
"lte": "2020-01-13T07:59:59.999Z"
}
}}
]
}
},
"size":111
}
wildcard和regexp,与prefix原理一致,都会扫描整个索引,性能很差
需求:搜索包含java,不包含spark的doc,但是这样子很死板
搜索包含java,尽量不包含spark的doc,如果包含了spark,不会说排除掉这个doc,而是说将这个doc的分数降低
语句:
GET /forum/article/_search
{
"query": {
"boosting": {
"positive": {
"match": {
"content": "java"
}
},
"negative": {
"match": {
"content": "spark"
}
},
"negative_boost": 0.2
}
}
}
包含了negative term的doc,分数乘以negative boost,分数降低
需求:对帖子搜索得到的相关性分数,跟follower_num进行运算,由follower_num在一定程度上增强帖子的分数看帖子的人越多,那么帖子的分数就越高
语句:
GET /forum/article/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "java spark",
"fields": ["tile", "content"]
}
},
"field_value_factor": {
"field": "follower_num",
"modifier": "log1p",
"factor": 0.5
},
"boost_mode": "sum",
"max_boost": 2
}
}
}
如果只有field,那么会将每个doc的分数都乘以follower_num,如果有的doc follower是0,那么分数就会变为0,效果很不好。因此一般会加个log1p函数,公式会变为,new_score = old_score * log(1 + number_of_votes),这样出来的分数会比较合理
再加个factor,可以进一步影响分数,new_score = old_score * log(1 + factor * number_of_votes)
boost_mode,可以决定分数与指定字段的值如何计算,multiply,sum,min,max,replace
max_boost,限制计算出来的分数不要超过max_boost指定的值
需求:搜索的时候,可能输入的搜索文本会出现误拼写的情况
语句:
GET /my_index/my_type/_search
{
"query": {
"fuzzy": {
"text": {
"value": "surprize",
"fuzziness": 2
}
}
}
}
fuzzy搜索以后,会自动尝试将你的搜索文本进行纠错,然后去跟文本进行匹配
fuzziness,你的搜索文本最多可以纠正几个字母去跟你的数据进行匹配,默认如果不设置,就是2