简介
在 Elasticsearch 中的搜索中,有两类搜索:queries和aggregations。
它们之间的区别在于:query 可以帮我们进行全文搜索,而 aggregation 可以帮我们对数据进行统计及分析。我们有时也可以结合 query 及 aggregation 一起使用,比如我们可以先对文档进行搜索然后在进行聚合 :
代码语言:javascript复制GET blogs/_search
{
"query": {
"match": {
"title": "community"
}
},
"aggregations": {
"top_authors": {
"terms": {
"field": "author"
}
}
}
}
结构化检索
结构化搜索(Structured search) 是指有关探询那些具有内在结构数据的过程。比如日期、时间和数字都是结构化的:它们有精确的格式,我们可以对这些格式进行逻辑操作。比较常见的操作包括比较数字或时间的范围,或判定两个值的大小。
在结构化查询中,我们得到的结果 总是 非是即否,要么存于集合之中,要么存在集合之外。结构化查询不关心文件的相关度或评分;它简单的对文档包括或排除处理。
exists
代码语言:javascript复制GET twitter/_search
{
"query": {
"exists": {
"field": "city"
}
}
}
ids
代码语言:javascript复制POST sphinx-doctor/_mget
{
"ids" : [ "9", "10" ]
}
位置查询
Elasticsearch 最厉害的是位置查询。这在很多的关系数据库里并没有。我们举一个简单的例子:
代码语言:javascript复制GET twitter/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "北京"
}
}
]
}
},
"post_filter": {
"geo_distance": {
"distance": "3km",
"location": {
"lat": 39.920086,
"lon": 116.454182
}
}
}
}
下面,我们找出在 5 公里以内的所有位置信息,并按照远近大小进行排序:
代码语言:javascript复制GET twitter/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "北京"
}
}
]
}
},
"post_filter": {
"geo_distance": {
"distance": "5km",
"location": {
"lat": 39.920086,
"lon": 116.454182
}
}
},
"sort": [
{
"_geo_distance": {
"location": "39.920086,116.454182",
"order": "asc",
"unit": "km"
}
}
]
}
GET twitter/_search
{
"query": {
"bool": {
"must": {
"match": {
"address": "北京"
}
},
"filter": {
"geo_distance": {
"distance": "5km",
"location": {
"lat": 39.920086,
"lon": 116.454182
}
}
}
}
},
"sort": [
{
"_geo_distance": {
"location": "39.920086,116.454182",
"order": "asc",
"unit": "km"
}
}
]
}
全文检索
全文搜索(full-text search) :怎样在全文字段中搜索到最相关的文档。
fuzzy
代码语言:javascript复制{
"query": {
"fuzzy": {
"hospitalcity.keyword": {
"value": "北京"
}
}
}
}
term
代码语言:javascript复制{
"query": {
"fuzzy": {
"hospitalcity.keyword": {
"value": "北京"
}
}
}
}
match
代码语言:javascript复制{
"query": {
"match": {
"title": "QUICK!"
}
}
}
query_string
代码语言:javascript复制{
"query": {
"query_string": {
"default_field": "hospitalcity",
"query": "北京"
}
}
}
多字段搜索
通常我们需要用相同或不同的字符串查询一个或多个字段,也就是说,需要对多个查询语句以及它们相关度评分进行合理的合并。
最佳字段
代码语言:javascript复制{
"query": {
"dis_max": {
"boost": 1.2,
"queries": [
{
"match": {
"FIELD": "TEXT"
}
},
{
"match": {
"FIELD": "TEXT"
}
}
]
}
}
}
{
"query": {
"multi_match": {
"query": "北京",
"fields": [
"hospitalprovince^3",
"hospitalcity"
],
"type": "best_fields",
"tie_breaker": 0.3,
"minimum_should_match": "80%"
}
}
}
多数字段
代码语言:javascript复制{
"query": {
"bool": {
"should": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
{
"query": {
"dis_max": {
"tie_breaker": 0.7,
"boost": 1.2,
"queries": [
{
"match": {
"FIELD": "TEXT"
}
},
{
"match": {
"FIELD": "TEXT"
}
}
]
}
}
}
{
"query": {
"multi_match": {
"query": "jumping rabbits",
"type": "most_fields", #我们希望将所有匹配字段的评分合并起来,所以使用 most_fields 类型。这让 multi_match 查询用 bool 查询将两个字段语句包在里面,而不是使用 dis_max 查询。
"fields": [ "title", "title.std" ]
}
}
}
跨字段
代码语言:javascript复制{
"query": {
"multi_match": {
"query": "peter smith",
"type": "cross_fields",
"operator": "and", #所有词都是必须的。
"fields": [ "first_name", "last_name" ]
}
}
}
词中心式:
(first_name:peter last_name:peter)
(first_name:smith last_name:smith)
字段中心式:
( first_name:peter first_name:smith)
( last_name:peter last_name:smith)
换句话说,它会同时在 first_name
和 last_name
两个字段中查找 smith
的 IDF ,然后用两者的最小值作为两个字段的 IDF 。
采用 cross_fields
查询与 自定义 _all
字段 相比,其中一个优势就是它可以在搜索时为单个字段提升权重。
短语检索
当你想找到彼此邻近搜索词的查询方法时,就会想到 match_phrase
查询 。
Es低版本支持:
{
"query": {
"match": {
"title": {
"query": "quick brown fox",
"type": "phrase"
}
}
}
}
{
"query": {
"bool": {
"must": {
"match": { #must 子句从结果集中包含或者排除文档
"title": {
"query": "quick brown fox",
"minimum_should_match": "30%"
}
}
},
"should": {
"match_phrase": { #should 子句增加了匹配到文档的相关度评分。
"title": {
"query": "quick brown fox",
"slop": 50
}
}
}
}
}
}
重新评分:
{
"query": {
"match": { #match 查询决定哪些文档将包含在最终结果集中,并通过 TF/IDF 排序。
"title": {
"query": "quick brown fox",
"minimum_should_match": "30%"
}
}
},
"rescore": {
"window_size": 50, #window_size 是每一分片进行重新评分的顶部文档数量。
"query": { #目前唯一支持的重新打分算法就是另一个查询,但是以后会有计划增加更多的算法。
"rescore_query": {
"match_phrase": {
"title": {
"query": "quick brown fox",
"slop": 50
}
}
}
}
}
}
部分检索
部分匹配 允许用户指定查找词的一部分并找出所有包含这部分片段的词。prefix 、 wildcard 和 regexp 查询是基于词操作的,如果用它们来查询 analyzed 字段,它们会检查字段里面的每个词,而不是将字段作为整体来处理。
prefix 前缀
prefix
查询是一个词级别的底层的查询,它不会在搜索之前分析查询字符串,它假定传入前缀就正是要查找的前缀。
默认状态下, prefix
查询不做相关度评分计算,它只是将所有匹配的文档返回,并为每条结果赋予评分值 1
。它的行为更像是过滤器而不是查询。 prefix
查询和 prefix
过滤器这两者实际的区别就是过滤器是可以被缓存的,而查询不行。
{
"query": {
"prefix": {
"postcode": "W1"
}
}
}
{
"match_phrase_prefix" : {
"brand" : {
"query": "walker johnnie bl",
"max_expansions": 50,
"slop": 10 #尽管词语的顺序不正确,查询仍然能匹配,因为我们为它设置了足够高的 slop 值使匹配时的词序有更大的灵活性。
}
}
}
wildcard 通配符
wildcard 通配符查询也是一种底层基于词的查询, 与前缀查询不同的是它允许指定匹配的正则式。它使用标准的 shell 通配符查询: ? 匹配任意字符, * 匹配 0 或多个字符。
代码语言:javascript复制{
"query": {
"wildcard": {
"postcode": "W?F*HW" #? 匹配 1 和 2 , * 与空格及 7 和 8 匹配。
}
}
}
regexp 正则式
代码语言:javascript复制{
"query": {
"regexp": {
"postcode": "W[0-9]. " #这个正则表达式要求词必须以 W 开头,紧跟 0 至 9 之间的任何一个数字,然后接一或多个其他字符。
}
}
}
控制相关度
Doc相关度评分
Index相关度评分
当在多个索引中搜索时, 可以使用参数 indices_boost
来提升整个索引的权重。在下面例子中,当要为最近索引的文档分配更高权重时,可以这么做:
GET /docs_2014_*/_search #这个多索引查询涵盖了所有以字符串 docs_2014_ 开始的索引。
{
"indices_boost": { #其中,索引 docs_2014_10 中的所有文件的权重是 3 ,索引 docs_2014_09 中是 2 ,其他所有匹配的索引权重为默认值 1 。
"docs_2014_10": 3,
"docs_2014_09": 2
},
"query": {
"match": {
"text": "quick brown fox"
}
}
}
相关度检索评分
boosting
它接受 positive
和 negative
查询。只有那些匹配 positive
查询的文档罗列出来,对于那些同时还匹配 negative
查询的文档将通过文档的原始 _score
与 negative_boost
相乘的方式降级后的结果。
为了达到效果, negative_boost
的值必须小于 1.0
。在这个示例中,所有包含负向词的文档评分 _score
都会减半。
GET /_search
{
"query": {
"boosting": {
"positive": {
"match": {
"text": "apple"
}
},
"negative": {
"match": {
"text": "pie tart fruit crumble tree"
}
},
"negative_boost": 0.5
}
}
}
constant_score
有时候我们根本不关心 TF/IDF , 只想知道一个词是否在某个字段中出现过。
在 constant_score
查询中,它可以包含查询或过滤,为任意一个匹配的文档指定评分 1
,忽略 TF/IDF 信息。
GET /_search
{
"query": {
"bool": {
"should": [
{ "constant_score": {
"query": { "match": { "description": "wifi" }}
}},
{ "constant_score": {
"query": { "match": { "description": "garden" }}
}},
{ "constant_score": {
"boost": 2 #pool 语句的权重提升值为 2 ,而其他的语句为 1 。
"query": { "match": { "description": "pool" }}
}}
]
}
}
}
function_score
function_score
查询 是用来控制评分过程的终极武器,它允许为每个与主查询匹配的文档应用一个函数, 以达到改变甚至完全替换原始查询评分 _score
的目的。
GET /blogposts/post/_search
{
"query": {
"function_score": { #function_score 查询将主查询和函数包括在内。
"query": { #主查询优先执行。
"multi_match": {
"query": "popularity",
"fields": [ "title", "content" ]
}
},
"field_value_factor": { #field_value_factor 函数会被应用到每个与主 query 匹配的文档。
1."field": "votes" #每个文档的 votes 字段都 必须 有值供 function_score 计算。如果 没有 文档的 votes 字段有值,那么就 必须 使用 missing 属性 提供的默认值来进行评分计算。
2."modifier": "log1p" #modifier 为 log1p 。
3."factor": 2 #双倍效果。factor 值大于 1 会提升效果, factor 值小于 1 会降低效果
},
4."boost_mode": "sum" , #将函数计算结果值累加到评分 _score 。
5."max_boost": 1.5 #无论 field_value_factor 函数的结果如何,最终结果都不会大于 1.5 。
}
}
}
1.new_score = old_score * number_of_votes
2.new_score = old_score * log(1 number_of_votes)
3.new_score = old_score * log(1 factor * number_of_votes)
4.new_score = old_score log(1 0.1 * number_of_votes)
到目前为止,我们展现的都是为所有文档应用单个函数的使用方式,现在会用过滤器将结果划分为多个子集(每个特性一个过滤器),并为每个子集使用不同的函数。
代码语言:javascript复制GET /_search
{
"query": {
"function_score": {
"filter": { #function_score 查询有个 filter 过滤器而不是 query 查询。
"term": { "city": "Barcelona" }
},
"functions": [ #functions 关键字存储着一个将被应用的函数列表。
{
"filter": { "term": { "features": "wifi" }}, #函数会被应用于和 filter 过滤器(可选的)匹配的文档。
"weight": 1
},
{
"filter": { "term": { "features": "garden" }}, #函数会被应用于和 filter 过滤器(可选的)匹配的文档。
"weight": 1
},
{
"filter": { "term": { "features": "pool" }}, #函数会被应用于和 filter 过滤器(可选的)匹配的文档。
"weight": 2 #pool 比其他特性更重要,所以它有更高 weight 。
},
{
"random_score": { #random_score 语句没有任何过滤器 filter ,所以会被应用到所有文档。
"seed": "the users session id" #将用户的会话 ID 作为种子 seed ,让该用户的随机始终保持一致,相同的种子 seed 会产生相同的随机结果。
}
}
],
"score_mode": "sum", #score_mode 指定各个函数的值进行组合运算的方式。
}
}
}
function_score
查询会提供一组 衰减函数(decay functions) , 让我们有能力在两个滑动标准,如地点和价格,之间权衡。
GET /_search
{
"query": {
"function_score": {
"functions": [
{
"gauss": {
"location": { #location 字段以地理坐标点 geo_point 映射。
"origin": { "lat": 51.5, "lon": 0.12 },
"offset": "2km",
"scale": "3km"
}
}
},
{
"gauss": {
"price": { #price 字段是数值。
"origin": "50", #参见 理解价格语句 ,理解 origin 为什么是 50 而不是 100 。
"offset": "50",
"scale": "20"
}
},
"weight": 2 #price 语句是 location 语句权重的两倍。
},
{
"script_score": {
"params": { #将这些变量作为参数 params 传递,我们可以查询时动态改变脚本无须重新编译。
"threshold": 80,
"discount": 0.1,
"target": 10
},
"script": "price = doc['price'].value; margin = doc['margin'].value;
if (price < threshold) { return price * margin / target };
return price * (1 - discount) * margin / target;" #JSON 不能接受内嵌的换行符,脚本中的换行符可以用 n 或 ; 符号替代。
}
}
]
}
}
}