一、精确查询
当进行精确值查找时, 我们会使用过滤器(filters)
1.term 精确值查找
{
"term" : {
"price" : 20
}
}
2.bool 布尔过滤器
{
"bool" : {
"must" : [], 所有的语句都 必须(must) 匹配,与 AND 等价。
"should" : [], 所有的语句都 不能(must not) 匹配,与 NOT 等价。
"must_not" : [], 至少有一个语句要匹配,与 OR 等价。
}
}
3.terms 查找多个精确值
{
"terms" : {
"price" : [20, 30]
}
}
4.range 查找处于某个范围内的文档
"range" : {
"price" : {
"gte" : 20,
"lte" : 40
}
}
gt: > 大于(greater than)
lt: < 小于(less than)
gte: >= 大于或等于(greater than or equal to)
lte: <= 小于或等于(less than or equal to)
5.exists 返回那些在指定字段有任何值的文档
{
"exists" : { "field" : "tags" }
}
6.missing 它返回某个特定 无 值字段的文档
{
"missing" : { "field" : "tags" }
}
二、匹配查询
1.match 主要的应用场景就是进行全文搜索
{
"query": {
"match": {
"title": "QUICK!"
}
}
}
Elasticsearch 执行上面这个 match 查询的步骤是:
1)检查字段类型 。
标题 title 字段是一个 string 类型( analyzed )已分析的全文字段,这意味着查询字符串本身也应该被分析。
2)分析查询字符串 。
将查询的字符串 QUICK! 传入标准分析器中,输出的结果是单个项 quick 。因为只有一个单词项,所以 match 查询执行的是单个底层 term 查询。
3)查找匹配文档 。
用 term 查询在倒排索引中查找 quick 然后获取一组包含该项的文档。
4)为每个文档评分 。
用 term 查询计算每个文档相关度评分 _score ,这是种将词频(term frequency,即词 quick 在相关文档的 title 字段中出现的频率)和反向文档频率(inverse document frequency,即词 quick 在所有文档的 title 字段中出现的频率),以及字段的长度(即字段越短相关度越高)相结合的计算方式。
operator match 查询还可以接受 operator 操作符作为输入参数,默认情况下该操作符是 or 。我们可以将它修改成 and 让所有指定词项都必须匹配
{
"query": {
"match": {
"title": {
"query": "BROWN DOG!",
"operator": "and"
}
}
}
}
minimum_should_match 最小匹配参数
{
"query": {
"match": {
"title": {
"query": "quick brown dog",
"minimum_should_match": "75%"
}
}
}
}
boost 控制任何查询语句的相对的权重, boost 的默认值为 1 ,大于 1 会提升一个语句的相对权重
{
"query": {
"bool": {
"must": {
"match": {
"content": {
"query": "full text search",
"operator": "and"
}
}
},
"should": [
{ "match": {
"content": {
"query": "Elasticsearch",
"boost": 3
}
}},
{ "match": {
"content": {
"query": "Lucene",
"boost": 2
}
}}
]
}
}
}
2.dis_max 不使用 bool 查询,可以使用 dis_max 即分离 最大化查询(Disjunction Max Query) 。分离(Disjunction)的意思是 或(or) ,这与可以把结合(conjunction)理解成 与(and) 相对应。分离最大化查询(Disjunction Max Query)指的是: 将任何与任一查询匹配的文档作为结果返回,但只将最佳匹配的评分作为查询的评分结果返回
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
查出评分最高的两条相关数据
tie_breaker 将其他匹配语句的评分也考虑其中
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "body": "Quick pets" }}
],
"tie_breaker": 0.3
}
}
}
3.multi_match 查询为能在多个字段上反复执行相同查询提供了一种便捷方式。
multi_match 多匹配查询的类型有多种,其中的三种恰巧与 了解我们的数据 中介绍的三个场景对应,即:
1)best_fields 最佳字段(默认值,可以不指定;这表示它会为每个字段生成一个 match 查询,然后将它们组合到 dis_max 查询的内部)、
{
"dis_max": {
"queries": [
{
"match": {
"title": {
"query": "Quick brown fox",
"minimum_should_match": "30%"
}
}
},
{
"match": {
"body": {
"query": "Quick brown fox",
"minimum_should_match": "30%"
}
}
},
],
"tie_breaker": 0.3
}
}
上面这个查询用 multi_match 重写成更简洁的形式:
{
"multi_match": {
"query": "Quick brown fox",
"type": "best_fields",
"fields": [ "title", "body" ],
"tie_breaker": 0.3,
"minimum_should_match": "30%"
}
}
字段名称可以用模糊匹配的方式给出:
{
"multi_match": {
"query": "Quick brown fox",
"fields": "*_title" //字段名称包含title的
}
}
可以使用 ^ 字符语法为单个字段提升权重,在字段名称的末尾添加 ^boost ,其中 boost 是一个浮点数
{
"multi_match": {
"query": "Quick brown fox",
"fields": [ "*_title", "chapter_title^2" ]
}
}
2)most_fields 多数字段、
{
"query": {
"bool": {
"should": [
{ "match": { "street": "Poland Street W1V" }},
{ "match": { "city": "Poland Street W1V" }},
{ "match": { "country": "Poland Street W1V" }},
{ "match": { "postcode": "Poland Street W1V" }}
]
}
}
}
为每个字段重复查询字符串会使查询瞬间变得冗长,可以采用 multi_match 查询,将 type 设置成 most_fields 然后告诉 Elasticsearch 合并所有匹配字段的评分:
{
"query": {
"multi_match": {
"query": "Poland Street W1V",
"type": "most_fields",
"fields": [ "street", "city", "country", "postcode" ]
}
}
}
用 most_fields 这种方式搜索也存在某些问题,这些问题并不会马上显现:
它是为多数字段匹配 任意 词设计的,而不是在 所有字段 中找到最匹配的。
它不能使用 operator 或 minimum_should_match 参数来降低次相关结果造成的长尾效应。
词频对于每个字段是不一样的,而且它们之间的相互影响会导致不好的排序结果。
3)cross_fields 跨字段查询。
cross_fields 使用词中心式(term-centric)的查询方式,它将所有字段当成一个大字段,并在 每个字段 中查找 每个词。
{
"query": {
"multi_match": {
"query": "peter smith",
"type": "cross_fields",
"operator": "and",
"fields": [ "first_name", "last_name" ]
}
}
}
cross_fields 类型首先分析查询字符串并生成一个词列表,然后它从所有字段中依次搜索每个词。这种不同的搜索方式很自然的解决了 字段中心式 查询三个问题中的二个。剩下的问题是逆向文档频率不同。
copy_to 复制
{
"mappings": {
"person": {
"properties": {
"first_name": {
"type": "string",
"copy_to": "full_name"
},
"last_name": {
"type": "string",
"copy_to": "full_name"
},
"full_name": {
"type": "string"
}
}
}
}
}
first_name 和 last_name 字段中的值会被复制到 full_name 字段。
三、短句匹配
match_phrase
match_phrase 查询首先将查询字符串解析成一个词项列表,然后对这些词项进行搜索,但只保留那些包含 全部 搜索词项,且 位置 与搜索词项相同的文档。
{
"query": {
"match_phrase": {
"title": "quick brown fox"
}
}
}
match_phrase 查询首先将查询字符串解析成一个词项列表,然后对这些词项进行搜索,但只保留那些包含 全部 搜索词项,且 位置 与搜索词项相同的文档。 比如对于 quick fox 的短语搜索可能不会匹配到任何文档,因为没有文档包含的 quick 词之后紧跟着 fox 。
slop 参数告诉 match_phrase 查询词条相隔多远时仍然能将文档视为匹配 。 相隔多远的意思是为了让查询和文档匹配你需要移动词条多少次?
{
"query": {
"match_phrase": {
"title": {
"query": "quick fox",
"slop": 1
}
}
}
}
尽管在使用了 slop 短语匹配中所有的单词都需要出现, 但是这些单词也不必为了匹配而按相同的序列排列。 有了足够大的 slop 值, 单词就能按照任意顺序排列了。
slop比较耗性能
position_increment_gap 告诉 Elasticsearch 应该为数组中每个新元素增加当前词条 position 的指定值。
{
"properties": {
"names": {
"type": "string",
"position_increment_gap": 100
}
}
}
所以现在当我们再索引 names 数组时,会产生如下的结果:
Position 1: john
Position 2: abraham
Position 103: lincoln
Position 104: smith
四、部分匹配
1.prefix 前缀查询
{
"query": {
"prefix": {
"postcode": "W1"
}
}
}
2.wildcard 通配符与正则表达式查询
{
"query": {
"wildcard": {
"postcode": "W?F*HW"
}
}
}
这个查询会匹配包含 W1F 7HW 和 W2F 8HW 的文档
? 匹配 1 和 2 , * 与空格及 7 和 8 匹配。
3.regexp 正则式查询允许写出这样更复杂的模式:
{
"query": {
"regexp": {
"postcode": "W[0-9].+"
}
}
}
这个正则表达式要求词必须以 W 开头,紧跟 0 至 9 之间的任何一个数字,然后接一或多个其他字符。
4.match_phrase_prefix 查询时输入即搜索
{
"match_phrase_prefix" : {
"brand" : "johnnie walker bl"
}
}
这种查询的行为与 match_phrase 查询一致,不同的是它将查询字符串的最后一个词作为前缀使用,换句话说,可以将之前的例子看成如下这样:
johnnie
跟着 walker
跟着以 bl 开始的词
如果通过 validate-query API 运行这个查询查询,explanation 的解释结果为:
“johnnie walker bl*”
max_expansions 限制前缀扩展的影响,一个合理的值是可能是 50
{
"match_phrase_prefix" : {
"brand" : {
"query": "johnnie walker bl",
"max_expansions": 50
}
}
}
参数 max_expansions 控制着可以与前缀匹配的词的数量,它会先查找第一个与前缀 bl 匹配的词,然后依次查找搜集与之匹配的词(按字母顺序),直到没有更多可匹配的词或当数量超过 max_expansions 时结束。
Ngrams
{
"filter": {
"autocomplete_filter": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 20
}
}
}
这个配置的意思是:对于这个 token 过滤器接收的任意词项,过滤器会为之生成一个最小固定值为 1 ,最大为 20 的 n-gram 。
keyword 分词器是一个非操作型分词器,这个分词器不做任何事情,它接收的任何字符串都会被原样发出,因此它可以用来处理 not_analyzed 的字段值,但这也需要其他的一些分析转换,如将字母转换成小写。
五、相关度评分
当匹配到一组文档后,需要根据相关度排序这些文档,不是所有的文档都包含所有词,有些词比其他的词更重要。一个文档的相关度评分部分取决于每个查询词在文档中的 权重 。
词频
词在文档中出现的频度是多少?频度越高,权重 越高 。 5 次提到同一词的字段比只提到 1 次的更相关。
{
"mappings": {
"doc": {
"properties": {
"text": {
"type": "string",
"index_options": "docs"
}
}
}
}
}
将参数 index_options 设置为 docs 可以禁用词频统计及词频位置,这个映射的字段不会计算词的出现次数,对于短语或近似查询也不可用。要求精确查询的 not_analyzed 字符串字段会默认使用该设置。
逆向文档频率
词在集合所有文档里出现的频率是多少?频次越高,权重 越低 。常用词如 and 或 the 对相关度贡献很少,因为它们在多数文档中都会出现,一些不常见词如 elastic 或 hippopotamus 可以帮助我们快速缩小范围找到感兴趣的文档。
字段长度归一值( norm )
字段的长度是多少?字段越短,字段的权重 越高 。如果词出现在类似标题 title 这样的字段,要比它出现在内容 body 这样的字段中的相关度更高。
{
"mappings": {
"doc": {
"properties": {
"text": {
"type": "string",
"norms": { "enabled": false }
}
}
}
}
}
这个字段不会将字段长度归一值考虑在内,长字段和短字段会以相同长度计算评分。
标准分词器
whitespace (空白字符)分词器按空白字符 —— 空格、tabs、换行符等等进行简单拆分
standard 分词器使用 Unicode 文本分割算法 来寻找单词 之间 的界限,并且输出所有界限之间的内容。
GET /_analyze?tokenizer=standard
You’re my ‘favorite’.
在这个例子中,You’re 中的撇号被视为单词的一部分,然而 ‘favorite’ 中的单引号则不会被视为单词的一部分, 所以分词结果如下: You’re 、 my 、 favorite 。
uax_url_email 分词器和 standard 分词器工作方式极其相同。 区别只在于它能识别 email 地址和 URLs 并输出为单个语汇单元。 standard 分词器则不一样,会将 email 地址和 URLs 拆分成独立的单词。 例如,email 地址 joe-bloggs@foo-bar.com 的分词结果为 joe 、 bloggs 、 foo 、 bar.com 。
ICU 插件
ICU 插件是处理英语之外语言的必需工具,非常推荐你安装并使用它,不幸的是,因为是基于额外的 ICU 函数库, 不同版本的ICU插件可能并不兼容之前的版本,当更新插件的时候,你需要重新索引你的数据。
icu_分词器
可以把文本分成独立的单词( ??? , ?? , ?? , ??? , ??? ),这使得文档更容易被搜索到。
GET /_analyze?tokenizer=standard
向日葵
GET /_analyze?tokenizer=icu_tokenizer
向日葵
标准分词器 在前面的例子中将每个字符输出为单独的词汇单元: 向 , 日 , 葵 。 icu_分词器 会输出单个词汇单元 向日葵 (sunflower) 。
标准分词器 和 icu_分词器 的另一个不同的地方是后者会将不同书写方式的字符(例如,βeta )拆分成独立的词汇单元 — β 和 eta— ,而前者则会输出单个词汇单元: βeta 。
六、聚合
桶:满足特定条件的文档的集合
指标:简单的数学运算(例如最小值、平均值、最大值,还有汇总)
GET /cars/transactions/_search
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
}
}
}
}
聚合操作被置于顶层参数 aggs 之下(如果你愿意,完整形式 aggregations 同样有效)。
然后,可以为聚合指定一个我们想要名称,本例中是: popular_colors 。
最后,定义单个桶的类型 terms 。
嵌套桶
GET /cars/transactions/_search
{
"size" : 0,
"aggs": {
"colors": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
},
"make": {
"terms": {
"field": "make"
}
}
}
}
}
}
注意前例中的 avg_price 度量仍然保持原位。
另一个聚合 make 被加入到了 color 颜色桶中。
这个聚合是 terms 桶,它会为每个汽车制造商生成唯一的桶。
让我们看看返回的响应(为了简单我们只显示部分结果):
{
...
"aggregations": {
"colors": {
"buckets": [
{
"key": "red",
"doc_count": 4,
"make": {
"buckets": [
{
"key": "honda",
"doc_count": 3
},
{
"key": "bmw",
"doc_count": 1
}
]
},
"avg_price": {
"value": 32500
}
},
...
}
正如期望的那样,新的聚合嵌入在每个颜色桶中。
现在我们看见按不同制造商分解的每种颜色下车辆信息。
最终,我们看到前例中的 avg_price 度量仍然维持不变。
GET /cars/transactions/_search
{
"size" : 0,
"aggs": {
"colors": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": { "avg": { "field": "price" }
},
"make" : {
"terms" : {
"field" : "make"
},
"aggs" : {
"min_price" : { "min": { "field": "price"} },
"max_price" : { "max": { "field": "price"} }
}
}
}
}
}
}
我们需要增加另外一个嵌套的 aggs 层级。
然后包括 min 最小度量。
以及 max 最大度量。
得到以下输出(只显示部分结果):
{
...
"aggregations": {
"colors": {
"buckets": [
{
"key": "red",
"doc_count": 4,
"make": {
"buckets": [
{
"key": "honda",
"doc_count": 3,
"min_price": {
"value": 10000
},
"max_price": {
"value": 20000
}
},
{
"key": "bmw",
"doc_count": 1,
"min_price": {
"value": 80000
},
"max_price": {
"value": 80000
}
}
]
},
"avg_price": {
"value": 32500
}
},
...
有了这两个桶,我们可以对查询的结果进行扩展并得到以下信息:
有四辆红色车。
红色车的平均售价是 $32,500 美元。
其中三辆红色车是 Honda 本田制造,一辆是 BMW 宝马制造。
最便宜的红色本田售价为 $10,000 美元。
最贵的红色本田售价为 $20,000 美元。
histogram 条形图
GET /cars/transactions/_search
{
"size" : 0,
"aggs":{
"price":{
"histogram":{
"field": "price",
"interval": 20000
},
"aggs":{
"revenue": {
"sum": {
"field" : "price"
}
}
}
}
}
}
histogram 桶要求两个参数:一个数值字段以及一个定义桶大小间隔。
sum 度量嵌套在每个售价区间内,用来显示每个区间内的总收入。
date_histogram 按时间统计(日期格式字段)
GET /cars/transactions/_search
{
“size” : 0,
“aggs”: {
“sales”: {
“date_histogram”: {
“field”: “sold”,
“interval”: “month”,
“format”: “yyyy-MM-dd”
}
}
}
}
时间间隔要求是日历术语month (如每个 bucket 1 个月)。
我们提供日期格式(format)以便 buckets 的键值便于阅读。
min_doc_count 这个参数强制返回空 buckets。
extended_bounds 这个参数强制返回整年。
GET /cars/transactions/_search
{
“size” : 0,
“aggs”: {
“sales”: {
“date_histogram”: {
“field”: “sold”,
“interval”: “month”,
“format”: “yyyy-MM-dd”,
“min_doc_count” : 0,
“extended_bounds” : {
“min” : “2014-01-01”,
“max” : “2014-12-31”
}
}
}
}
}