当前位置:   article > 正文

开始使用 Elasticsearch (2)_elasticsearch profile=true如何使用

elasticsearch profile=true如何使用

上一篇文章中,我们已经介绍了如何使用 REST 接口来在 Elasticsearch 中创建索引,文档以及对它们的操作。在今天的文章里,我们来介绍如何利用 Elasticsearch 来搜索我们的数据。Elasticsearch 是近实时的搜索。我们还是接着我们上次的练习 “开始使用 Elasticsearch (1)

开始使用Elasticsearch (2)

开始使用Elasticsearch (2)_哔哩哔哩_bilibili

在 Elasticsearch 中的搜索中,有两类搜索:

  • queries
  • aggregations

它们之间的区别在于:query 可以帮我们进行全文搜索,而  aggregation 可以帮我们对数据进行统计及分析。我们有时也可以结合 query 及 aggregation一起使用,比如我们可以先对文档进行搜索然后再进行 aggregation:

  1. GET blogs/_search
  2. {
  3. "query": {
  4. "match": {
  5. "title": "community"
  6. }
  7. },
  8. "aggregations": {
  9. "top_authors": {
  10. "terms": {
  11. "field": "author"
  12. }
  13. }
  14. }
  15. }

在上面的搜索中,先搜寻在 title 含有 community 的文档,然后再对数据进行 aggregation。 

在上面的查询中,我们使用了 Query DSL。 Elasticsearch 提供了一个基于 JSON 的完整 Query DSL(Domain Specific Language)来定义查询。 将查询 DSL 视为查询的 AST(抽象语法树)。它提供:

  • 全文搜索
  • 聚合
  • 排序,分页及操控响应 

Elasticsearch 不仅支持简单的搜索功能,还支持考虑多个条件的高级搜索,包括地理空间查询。 Elasticsearch 世界中有两种搜索变体:结构化搜索非结构化搜索

由术语级搜索功能支持的结构化搜索返回的结果与相关性评分无关。如果文档完全匹配,Elasticsearch 会获取文档,并且不关心它们是否紧密匹配或匹配程度如何。例如,搜索一组日期之间的航班、搜索特定促销期间的畅销书等等都属于这一类。执行搜索时,Elasticsearch 只检查匹配是否成功。有或没有落在该数据速率的航班。要么有少数畅销书,要么没有。没有什么属于可能的类别。这种类型的结构化搜索由 Elasticsearch 中的术语级查询提供。

另一方面,在非结构化搜索中,Elasticsearch 检索与查询密切相关的结果。根据结果​​与标准的相关程度对结果进行评分:高度相关的结果得分更高,因此位于结果命中的顶部。搜索文本字段会产生相关结果。 Elasticsearch 提供全文搜索,目的是搜索非结构化数据。

在下面的章节中,我们将具体描述如何使用 DSL 来进行各类的查询。

搜索所有的文档

我们可以使用如下的命令来搜索到所有的文档:

  1. GET /_all/_search
  2. GET /*/_search
  3. GET /_search

在这里我们没有指定任何index,我们将搜索在该 cluster 下的所有的 index。目前默认的返回个数是10个,除非我们设定 size:

GET /_search?size=20

上面的命令也等同于:

GET /_all/_search

我们也可以这样对多个 index 进行搜索:

POST /index1,index2,index3/_search

上面,表明,我们可以针对 index1,index2,index3 索引进行搜索。当然,我们甚至也可以这么写:

POST /index*,-index3/_search

上面表明,我们可以针对所有以 index 为开头的索引来进行搜索,但是排除 index3 索引。

如果我们只想搜索我们特定的 index,比如 twitter,我们可以这么做:

GET twitter/_search

从上面我们可以看出来,在 twitter 索引里我们有7个文档。在上面的 hits 数组里,我们可以看到所有的结果。同时,我们也可以看到一个叫做 _score 的项。它表示我们搜索结果的相关度。这个分数值越高,表明我们搜索匹配的相关度越高。在默认没有 sort 的情况下,所有搜索的结果的是按照分数由大到小来进行排列的。

在上面,我们可以看到 relation 字段的值为 eq,它表明搜索的结果为7个文档。这也是满足条件的所有文档,但是针对许多的大数据搜索情况,有时我们的搜索结果会超过10000个,那么这个返回的字段值将会是 gte:

它表明搜索的结果超过 10000。如果我们想得到所有的结果,我们需要参考我的另外一篇文章 “如何在搜索时得到精确的总 hits 数”。

在默认的情况下,我们可以得到10个结果。我们可以通过设置 size 参数得到我们想要的个数。同时,我们可以也配合 from 来进行分页。

GET twitter/_search?size=2&from=2

并且只要两个文档显示。我们可以通过这个方法让我们的文档进行分页显示。更多关于搜索结果分页的知识,请参阅链接 “Elasticsearch:运用 search_after 来进行深度分页”。

上面的查询类似于 DSL 查询的如下语句: 

  1. GET twitter/_search
  2. {
  3. "size": 2,
  4. "from": 2,
  5. "query": {
  6. "match_all": {}
  7. }
  8. }

我们可以通过 filter_path 来控制输出的较少的字段,比如:

GET twitter/_search?filter_path=hits.total

上面执行的结果将直接从 hits.total 开始进行返回:

  1. {
  2. "hits" : {
  3. "total" : {
  4. "value" : 6,
  5. "relation" : "eq"
  6. }
  7. }
  8. }

我们甚至可以只返回搜索的分数 _score,以及 _source 中的一部分:

  1. GET twitter/_search?filter_path=hits.hits._score,hits.hits._source.city
  2. {
  3. "query": {
  4. "match": {
  5. "city": "上海"
  6. }
  7. }
  8. }

上面返回的结果是:

  1. {
  2. "hits" : {
  3. "hits" : [
  4. {
  5. "_score" : 3.08089,
  6. "_source" : {
  7. "city" : "上海"
  8. }
  9. }
  10. ]
  11. }
  12. }

如果我们只想返回 _source,而不包含 meta 字段,我们可以使用如下的方法:

  1. GET twitter/_search?filter_path=hits.hits._source
  2. {
  3. "query": {
  4. "match": {
  5. "city": "上海"
  6. }
  7. }
  8. }

source filtering

我们可以通过 _source 来定义返回想要的字段:

  1. GET twitter/_search
  2. {
  3. "_source": ["user", "city"],
  4. "query": {
  5. "match_all": {
  6. }
  7. }
  8. }

返回的结果:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "1",
  6. "_score" : 1.0,
  7. "_source" : {
  8. "city" : "北京",
  9. "user" : "张三"
  10. }
  11. },
  12. {
  13. "_index" : "twitter",
  14. "_type" : "_doc",
  15. "_id" : "2",
  16. "_score" : 1.0,
  17. "_source" : {
  18. "city" : "北京",
  19. "user" : "老刘"
  20. }
  21. },
  22. ...
  23. ]

我们也可以使用如下的方法:

  1. GET twitter/_search
  2. {
  3. "_source": {
  4. "includes": ["user", "city"]
  5. },
  6. "query": {
  7. "match_all": {
  8. }
  9. }
  10. }

上面返回的结果和之前的返回的是一样的结果。

在实际的使用中,我们可以使用 fields 来指定返回的字段,而不用 _source。这样做更加高效。上面的搜索可以写成如下的格式:

  1. GET twitter/_search
  2. {
  3. "_source": false,
  4. "fields": ["user", "city"],
  5. "query": {
  6. "match_all": {
  7. }
  8. }
  9. }

详细阅读,可以参阅文章 “Elasticsearch:从搜索中获取选定的字段 fields”。

我们可以看到只有 user 及 city 两个字段在 _source 里返回。我们可以可以通过设置  _source 为 false,这样不返回任何的 _source 信息:

  1. GET twitter/_search
  2. {
  3. "_source": false,
  4. "query": {
  5. "match": {
  6. "user": "张三"
  7. }
  8. }
  9. }

返回的信息:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "1",
  6. "_score" : 3.0808902
  7. }
  8. ]

我们可以看到只有 _id 及 _score 等信息返回。其它任何的 _source 字段都没有被返回。它也可以接收通配符形式的控制,比如:

  1. GET twitter/_search
  2. {
  3. "_source": {
  4. "includes": [
  5. "user*",
  6. "location*"
  7. ],
  8. "excludes": [
  9. "*.lat"
  10. ]
  11. },
  12. "query": {
  13. "match_all": {}
  14. }
  15. }

返回的结果是:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "1",
  6. "_score" : 1.0,
  7. "_source" : {
  8. "location" : {
  9. "lon" : "116.325747"
  10. },
  11. "user" : "张三"
  12. }
  13. },
  14. ...
  15. ]

如果我们把 _source 设置为[],那么就是显示所有的字段,而不是不显示任何字段的功能。

  1. GET twitter/_search
  2. {
  3. "_source": [],
  4. "query": {
  5. "match_all": {
  6. }
  7. }
  8. }

上面的命令将显示 source 的所有字段。

Script fields

有些时候,我们想要的 field 可能在 _source 里根本没有,那么我们可以使用 script field 来生成这些 field。允许为每个匹配返回 script evaluation(基于不同的字段),例如:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match_all": {}
  5. },
  6. "script_fields": {
  7. "years_to_100": {
  8. "script": {
  9. "lang": "painless",
  10. "source": "100-doc['age'].value"
  11. }
  12. },
  13. "year_of_birth":{
  14. "script": "2019 - doc['age'].value"
  15. }
  16. }
  17. }

返回的结果是:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "1",
  6. "_score" : 1.0,
  7. "fields" : {
  8. "years_to_100" : [
  9. 80
  10. ],
  11. "year_of_birth" : [
  12. 1999
  13. ]
  14. }
  15. },
  16. {
  17. "_index" : "twitter",
  18. "_type" : "_doc",
  19. "_id" : "2",
  20. "_score" : 1.0,
  21. "fields" : {
  22. "years_to_100" : [
  23. 70
  24. ],
  25. "year_of_birth" : [
  26. 1989
  27. ]
  28. }
  29. },
  30. ...
  31. ]

必须注意的是这种使用 script 的方法来生成查询的结果对于大量的文档来说,可能会占用大量资源。 在这里大家一定要注意的是:doc 在这里指的是 doc value。否则的话,我们需要使用 ctx._source 来做一些搜索的动作。参照链接,我们可以把上面的命令修改为:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match_all": {}
  5. },
  6. "script_fields": {
  7. "years_to_100": {
  8. "script": {
  9. "lang": "painless",
  10. "source": "100-params._source['age']"
  11. }
  12. },
  13. "year_of_birth":{
  14. "script": "2019 - params._source['age']"
  15. }
  16. }
  17. }

因为 age 是 long 数据类型。它是有 doc value 的,所以,我们可以通过 doc['age'] 来访问,而且这些访问是比较快的。更多关 scripted fields 的学习可以参考文章 “Elasticsearch:Script fields 及其调试”。

从 Elastic Stack 7.11 版发布之后,Elasticsearch 可以添加运行时字段。如果你想了解这个请详细阅读文章 “Elasticsearch:Runtime fields 入门, Elastic 的 schema on read 实现 - 7.11 发布”。 

Count API

我们经常会查询我们的索引里到底有多少文档,那么我们可以使用_count重点来查询:

GET twitter/_count

如果我们想知道满足条件的文档的数量,我们可以采用如下的格式:

  1. GET twitter/_count
  2. {
  3. "query": {
  4. "match": {
  5. "city": "北京"
  6. }
  7. }
  8. }

在这里,我们可以得到 city 为“北京”的所有文档的数量:

  1. {
  2. "count" : 5,
  3. "_shards" : {
  4. "total" : 1,
  5. "successful" : 1,
  6. "skipped" : 0,
  7. "failed" : 0
  8. }
  9. }

修改 settings

我们可以通过如下的接口来获得一个 index 的 settings

GET twitter/_settings

从这里我们可以看到我们的 twitter 索引有多少个 shards 及多少个 replicas。我们也可以通过如下的接口来设置:

  1. PUT twitter
  2. {
  3. "settings": {
  4. "number_of_shards": 1,
  5. "number_of_replicas": 1
  6. }
  7. }

一旦我们把 number_of_shards 定下来了,我们就不可以修改了,除非把 index 删除,并重新 index 它。这是因为每个文档存储到哪一个 shard 是和 number_of_shards这 个数值有关的。一旦这个数值发生改变,那么之后寻找那个文档所在的 shard 就会不准确。

修改索引的 mapping

Elasticsearch 号称是 schemaless,在实际所得应用中,每一个 index 都有一个相应的 mapping。这个 mapping 在我们生产第一个文档时已经生产。它是对每个输入的字段进行自动的识别从而判断它们的数据类型。我们可以这么理解 schemaless:

  1. 不需要事先定义一个相应的 mapping 才可以生产文档。字段类型是动态进行识别的。这和传统的数据库是不一样的
  2. 如果有动态加入新的字段,mapping 也可以自动进行调整并识别新加入的字段

自动识别字段有一个问题,那就是有的字段可能识别并不精确,比如对于我们例子中的位置信息。那么我们需要对这个字段进行修改。

我们可以通过如下的命令来查询目前的 index 的 mapping:

GET twitter/_mapping

它显示的数据如下:

  1. {
  2. "twitter" : {
  3. "mappings" : {
  4. "properties" : {
  5. "address" : {
  6. "type" : "text",
  7. "fields" : {
  8. "keyword" : {
  9. "type" : "keyword",
  10. "ignore_above" : 256
  11. }
  12. }
  13. },
  14. "age" : {
  15. "type" : "long"
  16. },
  17. "city" : {
  18. "type" : "text",
  19. "fields" : {
  20. "keyword" : {
  21. "type" : "keyword",
  22. "ignore_above" : 256
  23. }
  24. }
  25. },
  26. "country" : {
  27. "type" : "text",
  28. "fields" : {
  29. "keyword" : {
  30. "type" : "keyword",
  31. "ignore_above" : 256
  32. }
  33. }
  34. },
  35. "location" : {
  36. "properties" : {
  37. "lat" : {
  38. "type" : "text",
  39. "fields" : {
  40. "keyword" : {
  41. "type" : "keyword",
  42. "ignore_above" : 256
  43. }
  44. }
  45. },
  46. "lon" : {
  47. "type" : "text",
  48. "fields" : {
  49. "keyword" : {
  50. "type" : "keyword",
  51. "ignore_above" : 256
  52. }
  53. }
  54. }
  55. }
  56. },
  57. "message" : {
  58. "type" : "text",
  59. "fields" : {
  60. "keyword" : {
  61. "type" : "keyword",
  62. "ignore_above" : 256
  63. }
  64. }
  65. },
  66. "province" : {
  67. "type" : "text",
  68. "fields" : {
  69. "keyword" : {
  70. "type" : "keyword",
  71. "ignore_above" : 256
  72. }
  73. }
  74. },
  75. "uid" : {
  76. "type" : "long"
  77. },
  78. "user" : {
  79. "type" : "text",
  80. "fields" : {
  81. "keyword" : {
  82. "type" : "keyword",
  83. "ignore_above" : 256
  84. }
  85. }
  86. }
  87. }
  88. }
  89. }
  90. }

从上面的显示中可以看出来 location 里的经纬度是一个 multi-field 的类型。

  1. "location" : {
  2. "properties" : {
  3. "lat" : {
  4. "type" : "text",
  5. "fields" : {
  6. "keyword" : {
  7. "type" : "keyword",
  8. "ignore_above" : 256
  9. }
  10. }
  11. },
  12. "lon" : {
  13. "type" : "text",
  14. "fields" : {
  15. "keyword" : {
  16. "type" : "keyword",
  17. "ignore_above" : 256
  18. }
  19. }
  20. }
  21. }
  22. }

这个显然不是我们所需的。正确的类型应该是:geo_point。我们重新修正我们的 mapping。

注意:我们不能为已经建立好的 index 动态修改 mapping。这是因为一旦修改,那么之前建立的索引就变成不能搜索的了。一种办法是 reindex 从而重新建立我们的索引。如果在之前的 mapping 加入的字段,那么我们可以不用重新建立索引。

为了能够正确地创建我们的 mapping,我们必须先把之前的 twitter 索引删除掉,并同时使用 settings 来创建这个 index。具体的步骤如下:

  1. DELETE twitter
  2. PUT twitter
  3. {
  4. "settings": {
  5. "number_of_shards": 1,
  6. "number_of_replicas": 1
  7. }
  8. }
  9. PUT twitter/_mapping
  10. {
  11. "properties": {
  12. "address": {
  13. "type": "text",
  14. "fields": {
  15. "keyword": {
  16. "type": "keyword",
  17. "ignore_above": 256
  18. }
  19. }
  20. },
  21. "age": {
  22. "type": "long"
  23. },
  24. "city": {
  25. "type": "text",
  26. "fields": {
  27. "keyword": {
  28. "type": "keyword",
  29. "ignore_above": 256
  30. }
  31. }
  32. },
  33. "country": {
  34. "type": "text",
  35. "fields": {
  36. "keyword": {
  37. "type": "keyword",
  38. "ignore_above": 256
  39. }
  40. }
  41. },
  42. "location": {
  43. "type": "geo_point"
  44. },
  45. "message": {
  46. "type": "text",
  47. "fields": {
  48. "keyword": {
  49. "type": "keyword",
  50. "ignore_above": 256
  51. }
  52. }
  53. },
  54. "province": {
  55. "type": "text",
  56. "fields": {
  57. "keyword": {
  58. "type": "keyword",
  59. "ignore_above": 256
  60. }
  61. }
  62. },
  63. "uid": {
  64. "type": "long"
  65. },
  66. "user": {
  67. "type": "text",
  68. "fields": {
  69. "keyword": {
  70. "type": "keyword",
  71. "ignore_above": 256
  72. }
  73. }
  74. }
  75. }
  76. }

重新查看我们的 mapping:

GET twitter/_mapping

我们可以看到我们已经创建好了新的 mapping。我们再次运行之前我们的 bulk 接口,并把我们所需要的数据导入到 twitter 索引中。

  1. POST _bulk
  2. { "index" : { "_index" : "twitter", "_id": 1} }
  3. {"user":"双榆树-张三","message":"今儿天气不错啊,出去转转去","uid":2,"age":20,"city":"北京","province":"北京","country":"中国","address":"中国北京市海淀区","location":{"lat":"39.970718","lon":"116.325747"}}
  4. { "index" : { "_index" : "twitter", "_id": 2 }}
  5. {"user":"东城区-老刘","message":"出发,下一站云南!","uid":3,"age":30,"city":"北京","province":"北京","country":"中国","address":"中国北京市东城区台基厂三条3号","location":{"lat":"39.904313","lon":"116.412754"}}
  6. { "index" : { "_index" : "twitter", "_id": 3} }
  7. {"user":"东城区-李四","message":"happy birthday!","uid":4,"age":30,"city":"北京","province":"北京","country":"中国","address":"中国北京市东城区","location":{"lat":"39.893801","lon":"116.408986"}}
  8. { "index" : { "_index" : "twitter", "_id": 4} }
  9. {"user":"朝阳区-老贾","message":"123,gogogo","uid":5,"age":35,"city":"北京","province":"北京","country":"中国","address":"中国北京市朝阳区建国门","location":{"lat":"39.718256","lon":"116.367910"}}
  10. { "index" : { "_index" : "twitter", "_id": 5} }
  11. {"user":"朝阳区-老王","message":"Happy BirthDay My Friend!","uid":6,"age":50,"city":"北京","province":"北京","country":"中国","address":"中国北京市朝阳区国贸","location":{"lat":"39.918256","lon":"116.467910"}}
  12. { "index" : { "_index" : "twitter", "_id": 6} }
  13. {"user":"虹桥-老吴","message":"好友来了都今天我生日,好友来了,什么 birthday happy 就成!","uid":7,"age":90,"city":"上海","province":"上海","country":"中国","address":"中国上海市闵行区","location":{"lat":"31.175927","lon":"121.383328"}}

至此,我们已经完整地建立了我们所需要的索引。在下面,我们开始使用 DSL(Domain Specifc Lanaguage)来帮我们进行查询。

查询数据

在这个章节里,我们来展示一下从我们的 ES 索引中查询我们所想要的数据。

match query

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match": {
  5. "city": "北京"
  6. }
  7. }
  8. }

从我们查询的结果来看,我们可以看到有5个用户是来自北京的,而且查询出来的结果是按照相关性(relavance)来进行排序的。分数越高,就越排在前面。我们再做如下的一个查询:

  1. GET twitter/_search?filter_path=**.hits
  2. {
  3. "fields": [
  4. "message"
  5. ],
  6. "query": {
  7. "match": {
  8. "message": "出"
  9. }
  10. },
  11. "_source": false
  12. }

上面的查询搜索在 message 中含有 “” 这个字的文档:

  1. {
  2. "hits" : {
  3. "hits" : [
  4. {
  5. "_index" : "twitter",
  6. "_id" : "2",
  7. "_score" : 1.0764678,
  8. "fields" : {
  9. "message" : [
  10. "出发,下一站云南!"
  11. ]
  12. }
  13. },
  14. {
  15. "_index" : "twitter",
  16. "_id" : "1",
  17. "_score" : 0.8456129,
  18. "fields" : {
  19. "message" : [
  20. "今儿天气不错啊,出去转转去"
  21. ]
  22. }
  23. }
  24. ]
  25. }
  26. }

我们可以看到第一个文档的分数比较高,是因为它的句子比较短,虽然两个句子都含有 “”。这个打分的原则可以参考文章 “Elasticsearch:分布式计分”。如果我们想限定一个分数的最小值来减少返回文档的数,我们可以定义 min_score:

  1. GET twitter/_search?filter_path=**.hits
  2. {
  3. "min_score": 1.0,
  4. "fields": [
  5. "message"
  6. ],
  7. "query": {
  8. "match": {
  9. "message": "出"
  10. }
  11. },
  12. "_source": false
  13. }

上面我们指定了 min_score 为 1.0,那么只有一个文档满足要求:

  1. {
  2. "hits" : {
  3. "hits" : [
  4. {
  5. "_index" : "twitter",
  6. "_id" : "2",
  7. "_score" : 1.0764678,
  8. "fields" : {
  9. "message" : [
  10. "出发,下一站云南!"
  11. ]
  12. }
  13. }
  14. ]
  15. }
  16. }

在很多的情况下,我们也可以使用 script query 来完成:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "script": {
  5. "script": {
  6. "source": "doc['city.keyword'].contains(params.name)",
  7. "lang": "painless",
  8. "params": {
  9. "name": "北京"
  10. }
  11. }
  12. }
  13. }
  14. }

上面的 script query 和上面的查询是一样的结果,但是我们不建议大家使用这种方法。相比较而言,script query 的方法比较低效。另外,假如我们的文档是几百万或者 PB 级的数据量,那么上面的运算可能被执行无数次,那么可能需要巨大的计算量。在这种情况下,我们需要考虑在 ingest 的时候做计算。请阅读我的另外一篇文章 “避免不必要的脚本 - scripting”。

在上面,但凡满足条件的文档都将被搜索到。它的分数为 1。我们可以做如下的搜索:

  1. GET twitter/_search?filter_path=**.hits
  2. {
  3. "query": {
  4. "script": {
  5. "script": {
  6. "source": "doc['age'].value > 30",
  7. "lang": "painless"
  8. }
  9. }
  10. }
  11. }

在上面,凡是年龄超过 30 岁的文档将被搜索到。上面运行的结果为:

  1. {
  2. "hits": {
  3. "hits": [
  4. {
  5. "_index": "twitter",
  6. "_id": "4",
  7. "_score": 1,
  8. "_source": {
  9. "user": "朝阳区-老贾",
  10. "message": "123,gogogo",
  11. "uid": 5,
  12. "age": 35,
  13. "city": "北京",
  14. "province": "北京",
  15. "country": "中国",
  16. "address": "中国北京市朝阳区建国门",
  17. "location": {
  18. "lat": "39.718256",
  19. "lon": "116.367910"
  20. }
  21. }
  22. },
  23. {
  24. "_index": "twitter",
  25. "_id": "5",
  26. "_score": 1,
  27. "_source": {
  28. "user": "朝阳区-老王",
  29. "message": "Happy BirthDay My Friend!",
  30. "uid": 6,
  31. "age": 50,
  32. "city": "北京",
  33. "province": "北京",
  34. "country": "中国",
  35. "address": "中国北京市朝阳区国贸",
  36. "location": {
  37. "lat": "39.918256",
  38. "lon": "116.467910"
  39. }
  40. }
  41. },
  42. {
  43. "_index": "twitter",
  44. "_id": "6",
  45. "_score": 1,
  46. "_source": {
  47. "user": "虹桥-老吴",
  48. "message": "好友来了都今天我生日,好友来了,什么 birthday happy 就成!",
  49. "uid": 7,
  50. "age": 90,
  51. "city": "上海",
  52. "province": "上海",
  53. "country": "中国",
  54. "address": "中国上海市闵行区",
  55. "location": {
  56. "lat": "31.175927",
  57. "lon": "121.383328"
  58. }
  59. }
  60. }
  61. ]
  62. }
  63. }

我们甚至可以使用 compound 形式的搜索。这个在下面将要讲到。我们可以在 filter 下使用 script 来进行过滤:

  1. GET twitter/_search?filter_path=**.hits
  2. {
  3. "query": {
  4. "bool": {
  5. "filter": {
  6. "script": {
  7. "script": {
  8. "source": "doc['age'].value > 30"
  9. }
  10. }
  11. }
  12. }
  13. }
  14. }

这个运行的结果和上面的是一模一样的,除了一点之外,那就是它的分数为 0:

  1. {
  2. "hits": {
  3. "hits": [
  4. {
  5. "_index": "twitter",
  6. "_id": "4",
  7. "_score": 0,
  8. "_source": {
  9. "user": "朝阳区-老贾",
  10. "message": "123,gogogo",
  11. "uid": 5,
  12. "age": 35,
  13. "city": "北京",
  14. "province": "北京",
  15. "country": "中国",
  16. "address": "中国北京市朝阳区建国门",
  17. "location": {
  18. "lat": "39.718256",
  19. "lon": "116.367910"
  20. }
  21. }
  22. },
  23. {
  24. "_index": "twitter",
  25. "_id": "5",
  26. "_score": 0,
  27. "_source": {
  28. "user": "朝阳区-老王",
  29. "message": "Happy BirthDay My Friend!",
  30. "uid": 6,
  31. "age": 50,
  32. "city": "北京",
  33. "province": "北京",
  34. "country": "中国",
  35. "address": "中国北京市朝阳区国贸",
  36. "location": {
  37. "lat": "39.918256",
  38. "lon": "116.467910"
  39. }
  40. }
  41. },
  42. {
  43. "_index": "twitter",
  44. "_id": "6",
  45. "_score": 0,
  46. "_source": {
  47. "user": "虹桥-老吴",
  48. "message": "好友来了都今天我生日,好友来了,什么 birthday happy 就成!",
  49. "uid": 7,
  50. "age": 90,
  51. "city": "上海",
  52. "province": "上海",
  53. "country": "中国",
  54. "address": "中国上海市闵行区",
  55. "location": {
  56. "lat": "31.175927",
  57. "lon": "121.383328"
  58. }
  59. }
  60. }
  61. ]
  62. }
  63. }

这个是因为 filter 是不计入分数的。这个在下面的 compound 搜索里将会讲到。

我们也可以使用 URI 的形式来进行搜索:

GET twitter/_search?q=city:"北京"
  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "1",
  6. "_score" : 0.48232412,
  7. "_source" : {
  8. "user" : "双榆树-张三",
  9. "message" : "今儿天气不错啊,出去转转去",
  10. "uid" : 2,
  11. "age" : 20,
  12. "city" : "北京",
  13. "province" : "北京",
  14. "country" : "中国",
  15. "address" : "中国北京市海淀区",
  16. "location" : {
  17. "lat" : "39.970718",
  18. "lon" : "116.325747"
  19. }
  20. }
  21. }
  22. ...
  23. ]

如果你想了解更多,你可以更进一步阅读 “Elasticsearch: 使用 URI Search”。

如果我们不需要这个 score,我们可以选择 filter 来完成。

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "filter": {
  6. "term": {
  7. "city.keyword": "北京"
  8. }
  9. }
  10. }
  11. }
  12. }

这里我们使用了 filter 来过滤我们的搜索,显示的结果如下:

  1. {
  2. "took" : 0,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 5,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 0.0,
  16. "hits" : [
  17. {
  18. "_index" : "twitter",
  19. "_type" : "_doc",
  20. "_id" : "1",
  21. "_score" : 0.0,
  22. "_source" : {
  23. "user" : "双榆树-张三",
  24. "message" : "今儿天气不错啊,出去转转去",
  25. "uid" : 2,
  26. "age" : 20,
  27. "city" : "北京",
  28. "province" : "北京",
  29. "country" : "中国",
  30. "address" : "中国北京市海淀区",
  31. "location" : {
  32. "lat" : "39.970718",
  33. "lon" : "116.325747"
  34. }
  35. }
  36. },
  37. ...
  38. }

从返回的结果来看,_score 项为0。对于这种搜索,只要 yes 或 no。我们并不关心它们是的相关性。在这里我们使用了city.keyword。对于一些刚接触 Elasticsearch 的人来说,这个可能比较陌生。正确的理解是 city 在我们的 mapping 中是一个 multi-field 项。它既是 text 也是 keyword 类型。对于一个 keyword 类型的项来说,这个项里面的所有字符都被当做一个字符串。它们在建立文档时,不需要进行 index。keyword 字段用于精确搜索,aggregation 和排序(sorting)。如果你想了解更多关于 text 及 keyword 的区别,请详细阅读另外一篇文章 “Elasticsearch:Text vs. keyword - 它们之间的差异以及它们的行为方式”。

所以在我们的 filter 中,我们是使用了 term 来完成这个查询。

我们也可以使用如下的办法达到同样的效果:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "constant_score": {
  5. "filter": {
  6. "term": {
  7. "city.keyword": {
  8. "value": "北京"
  9. }
  10. }
  11. }
  12. }
  13. }
  14. }

在我们使用 match query 时,默认的操作是 OR,我们可以做如下的查询:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match": {
  5. "user": {
  6. "query": "朝阳区-老贾",
  7. "operator": "or"
  8. }
  9. }
  10. }
  11. }

上面的查询也和如下的查询是一样的:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match": {
  5. "user": "朝阳区-老贾"
  6. }
  7. }
  8. }

这是因为默认的操作是 or 操作。上面查询的结果是任何文档匹配:“”,“”,“”,“”及“”这5个字中的任何一个将被显示:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "4",
  6. "_score" : 4.4209847,
  7. "_source" : {
  8. "user" : "朝阳区-老贾",
  9. "message" : "123,gogogo",
  10. "uid" : 5,
  11. "age" : 35,
  12. "city" : "北京",
  13. "province" : "北京",
  14. "country" : "中国",
  15. "address" : "中国北京市朝阳区建国门",
  16. "location" : {
  17. "lat" : "39.718256",
  18. "lon" : "116.367910"
  19. }
  20. }
  21. },
  22. {
  23. "_index" : "twitter",
  24. "_type" : "_doc",
  25. "_id" : "5",
  26. "_score" : 2.9019678,
  27. "_source" : {
  28. "user" : "朝阳区-老王",
  29. "message" : "Happy BirthDay My Friend!",
  30. "uid" : 6,
  31. "age" : 50,
  32. "city" : "北京",
  33. "province" : "北京",
  34. "country" : "中国",
  35. "address" : "中国北京市朝阳区国贸",
  36. "location" : {
  37. "lat" : "39.918256",
  38. "lon" : "116.467910"
  39. }
  40. }
  41. },
  42. {
  43. "_index" : "twitter",
  44. "_type" : "_doc",
  45. "_id" : "2",
  46. "_score" : 0.8713734,
  47. "_source" : {
  48. "user" : "东城区-老刘",
  49. "message" : "出发,下一站云南!",
  50. "uid" : 3,
  51. "age" : 30,
  52. "city" : "北京",
  53. "province" : "北京",
  54. "country" : "中国",
  55. "address" : "中国北京市东城区台基厂三条3号",
  56. "location" : {
  57. "lat" : "39.904313",
  58. "lon" : "116.412754"
  59. }
  60. }
  61. },
  62. {
  63. "_index" : "twitter",
  64. "_type" : "_doc",
  65. "_id" : "6",
  66. "_score" : 0.4753614,
  67. "_source" : {
  68. "user" : "虹桥-老吴",
  69. "message" : "好友来了都今天我生日,好友来了,什么 birthday happy 就成!",
  70. "uid" : 7,
  71. "age" : 90,
  72. "city" : "上海",
  73. "province" : "上海",
  74. "country" : "中国",
  75. "address" : "中国上海市闵行区",
  76. "location" : {
  77. "lat" : "31.175927",
  78. "lon" : "121.383328"
  79. }
  80. }
  81. },
  82. {
  83. "_index" : "twitter",
  84. "_type" : "_doc",
  85. "_id" : "3",
  86. "_score" : 0.4356867,
  87. "_source" : {
  88. "user" : "东城区-李四",
  89. "message" : "happy birthday!",
  90. "uid" : 4,
  91. "age" : 30,
  92. "city" : "北京",
  93. "province" : "北京",
  94. "country" : "中国",
  95. "address" : "中国北京市东城区",
  96. "location" : {
  97. "lat" : "39.893801",
  98. "lon" : "116.408986"
  99. }
  100. }
  101. }
  102. ]

我们也可以设置参数 minimum_should_match 来设置至少匹配的 term。比如:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match": {
  5. "user": {
  6. "query": "朝阳区-老贾",
  7. "operator": "or",
  8. "minimum_should_match": 3
  9. }
  10. }
  11. }
  12. }

上面显示我们至少要匹配“”,“”,“”,“” 及 “贾” 这5个中的3个字才可以。显示结果:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "4",
  6. "_score" : 4.4209847,
  7. "_source" : {
  8. "user" : "朝阳区-老贾",
  9. "message" : "123,gogogo",
  10. "uid" : 5,
  11. "age" : 35,
  12. "city" : "北京",
  13. "province" : "北京",
  14. "country" : "中国",
  15. "address" : "中国北京市朝阳区建国门",
  16. "location" : {
  17. "lat" : "39.718256",
  18. "lon" : "116.367910"
  19. }
  20. }
  21. },
  22. {
  23. "_index" : "twitter",
  24. "_type" : "_doc",
  25. "_id" : "5",
  26. "_score" : 2.9019678,
  27. "_source" : {
  28. "user" : "朝阳区-老王",
  29. "message" : "Happy BirthDay My Friend!",
  30. "uid" : 6,
  31. "age" : 50,
  32. "city" : "北京",
  33. "province" : "北京",
  34. "country" : "中国",
  35. "address" : "中国北京市朝阳区国贸",
  36. "location" : {
  37. "lat" : "39.918256",
  38. "lon" : "116.467910"
  39. }
  40. }
  41. }
  42. ]

我们也可以改为 and 操作看看:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match": {
  5. "user": {
  6. "query": "朝阳区-老贾",
  7. "operator": "and"
  8. }
  9. }
  10. }
  11. }

显示的结果是:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "4",
  6. "_score" : 4.4209847,
  7. "_source" : {
  8. "user" : "朝阳区-老贾",
  9. "message" : "123,gogogo",
  10. "uid" : 5,
  11. "age" : 35,
  12. "city" : "北京",
  13. "province" : "北京",
  14. "country" : "中国",
  15. "address" : "中国北京市朝阳区建国门",
  16. "location" : {
  17. "lat" : "39.718256",
  18. "lon" : "116.367910"
  19. }
  20. }
  21. }
  22. ]

在这种情况下,需要同时匹配索引的5个字才可以。显然我们可以通过使用 and 来提高搜索的精度。

Highlighting

突出显示(highlighting)使你能够从搜索结果中的一个或多个字段中获取突出显示的片段,以便向用户显示查询匹配的位置。 当你请求突出显示时,响应包含每个搜索命中的附加突出显示元素,其中包括突出显示的字段和突出显示的片段。

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match": {
  5. "address": "北京"
  6. }
  7. },
  8. "highlight": {
  9. "fields": {
  10. "address": {}
  11. }
  12. }
  13. }

在上面,要使用默认高亮器在每个搜索命中中获取 address 字段的高亮显示,请在请求正文中包含一个 highlight 对象,用于指定内容字段。返回结果如下:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "1",
  6. "_score" : 0.5253035,
  7. "_source" : {
  8. "user" : "双榆树-张三",
  9. "message" : "今儿天气不错啊,出去转转去",
  10. "uid" : 2,
  11. "age" : 20,
  12. "city" : "北京",
  13. "province" : "北京",
  14. "country" : "中国",
  15. "address" : "中国北京市海淀区",
  16. "location" : {
  17. "lat" : "39.970718",
  18. "lon" : "116.325747"
  19. }
  20. },
  21. "highlight" : {
  22. "address" : [
  23. "中国<em>北</em><em>京</em>市海淀区"
  24. ]
  25. }
  26. }
  27. ...

 在返回结果中,我们可以看到:

            "中国<em></em><em></em>市海淀区"

这是在默认情况下的返回结果。它是用 em 来进行高亮显示的。这个格式可以在 HTML 中进行高亮显示。在实际的运用中,我们可能不希望高亮使用 em 来进行表述的。如果是这种情况,我们可以使用如下的方式:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match": {
  5. "address": "北京"
  6. }
  7. },
  8. "highlight": {
  9. "pre_tags": ["<my_tag>"],
  10. "post_tags": ["</my_tag>"],
  11. "fields": {
  12. "address": {}
  13. }
  14. }
  15. }

在上面,我们通过 pre_tags 及 post_tags 来定义我们想要的 tag。上面例子显示的结果为:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "1",
  6. "_score" : 0.5253035,
  7. "_source" : {
  8. "user" : "双榆树-张三",
  9. "message" : "今儿天气不错啊,出去转转去",
  10. "uid" : 2,
  11. "age" : 20,
  12. "city" : "北京",
  13. "province" : "北京",
  14. "country" : "中国",
  15. "address" : "中国北京市海淀区",
  16. "location" : {
  17. "lat" : "39.970718",
  18. "lon" : "116.325747"
  19. }
  20. },
  21. "highlight" : {
  22. "address" : [
  23. "中国<my_tag>北</my_tag><my_tag>京</my_tag>市海淀区"
  24. ]
  25. }
  26. }
  27. ...

Ids  query

我们可以通过 id 来进行查询,比如:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "ids": {
  5. "values": ["1", "2"]
  6. }
  7. }
  8. }

上面的查询将返回 id 为 “1” 和 “2” 的文档。

multi_match

在上面的搜索之中,我们特别指明一个专有的 field 来进行搜索,但是在很多的情况下,我们并不知道是哪一个 field 含有这个关键词,那么在这种情况下,我们可以使用 multi_match 来进行搜索:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "multi_match": {
  5. "query": "朝阳",
  6. "fields": [
  7. "user",
  8. "address^3",
  9. "message"
  10. ],
  11. "type": "best_fields"
  12. }
  13. }
  14. }

在上面,我们可以看到这个 multi_search 的 type 为 best_fields,也就是说它搜索了3个字段。最终的分数 _score 是按照得分最高的那个字段的分数为准。更多类型的定义,请在链接查看。在上面,我们可以同时对三个 fields: user,adress 及 message进行搜索,但是我们对 address 含有 “朝阳” 的文档的分数进行3倍的加权。返回的结果:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "5",
  6. "_score" : 6.1777167,
  7. "_source" : {
  8. "user" : "朝阳区-老王",
  9. "message" : "Happy good BirthDay My Friend!",
  10. "uid" : 6,
  11. "age" : 50,
  12. "city" : "北京",
  13. "province" : "北京",
  14. "country" : "中国",
  15. "address" : "中国北京市朝阳区国贸",
  16. "location" : {
  17. "lat" : "39.918256",
  18. "lon" : "116.467910"
  19. }
  20. }
  21. },
  22. {
  23. "_index" : "twitter",
  24. "_type" : "_doc",
  25. "_id" : "4",
  26. "_score" : 5.9349246,
  27. "_source" : {
  28. "user" : "朝阳区-老贾",
  29. "message" : "123,gogogo",
  30. "uid" : 5,
  31. "age" : 35,
  32. "city" : "北京",
  33. "province" : "北京",
  34. "country" : "中国",
  35. "address" : "中国北京市朝阳区建国门",
  36. "location" : {
  37. "lat" : "39.718256",
  38. "lon" : "116.367910"
  39. }
  40. }
  41. }
  42. ]

Prefix query

返回在提供的字段中包含特定前缀的文档。
 

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "prefix": {
  5. "user": {
  6. "value": "朝"
  7. }
  8. }
  9. }
  10. }

查询 user 字段里以“”为开头的所有文档:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "4",
  6. "_score" : 1.0,
  7. "_source" : {
  8. "user" : "朝阳区-老贾",
  9. "message" : "123,gogogo",
  10. "uid" : 5,
  11. "age" : 35,
  12. "city" : "北京",
  13. "province" : "北京",
  14. "country" : "中国",
  15. "address" : "中国北京市朝阳区建国门",
  16. "location" : {
  17. "lat" : "39.718256",
  18. "lon" : "116.367910"
  19. }
  20. }
  21. },
  22. {
  23. "_index" : "twitter",
  24. "_type" : "_doc",
  25. "_id" : "5",
  26. "_score" : 1.0,
  27. "_source" : {
  28. "user" : "朝阳区-老王",
  29. "message" : "Happy BirthDay My Friend!",
  30. "uid" : 6,
  31. "age" : 50,
  32. "city" : "北京",
  33. "province" : "北京",
  34. "country" : "中国",
  35. "address" : "中国北京市朝阳区国贸",
  36. "location" : {
  37. "lat" : "39.918256",
  38. "lon" : "116.467910"
  39. }
  40. }
  41. }
  42. ]


Term query 

Term query 会在给定字段中进行精确的字词匹配。 因此,您需要提供准确的术语以获取正确的结果。 

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "term": {
  5. "user.keyword": {
  6. "value": "朝阳区-老贾"
  7. }
  8. }
  9. }
  10. }

在这里,我们使用 user.keyword 来对“朝阳区-老贾”进行精确匹配查询相应的文档:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "4",
  6. "_score" : 1.5404451,
  7. "_source" : {
  8. "user" : "朝阳区-老贾",
  9. "message" : "123,gogogo",
  10. "uid" : 5,
  11. "age" : 35,
  12. "city" : "北京",
  13. "province" : "北京",
  14. "country" : "中国",
  15. "address" : "中国北京市朝阳区建国门",
  16. "location" : {
  17. "lat" : "39.718256",
  18. "lon" : "116.367910"
  19. }
  20. }
  21. }
  22. ]

Terms query

如果我们想对多个 terms 进行查询,我们可以使用如下的方式来进行查询:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "terms": {
  5. "user.keyword": [
  6. "双榆树-张三",
  7. "东城区-老刘"
  8. ]
  9. }
  10. }
  11. }

上面查询 user.keyword 里含有“双榆树-张三”或“东城区-老刘”的所有文档。

Terms_set query

查询在提供的字段中包含最少数目的精确术语的文档。除你可以定义返回文档所需的匹配术语数之外,terms_set 查询与术语查询相同。 例如:

  1. PUT /job-candidates
  2. {
  3. "mappings": {
  4. "properties": {
  5. "name": {
  6. "type": "keyword"
  7. },
  8. "programming_languages": {
  9. "type": "keyword"
  10. },
  11. "required_matches": {
  12. "type": "long"
  13. }
  14. }
  15. }
  16. }
  17. PUT /job-candidates/_doc/1?refresh
  18. {
  19. "name": "Jane Smith",
  20. "programming_languages": [ "c++", "java" ],
  21. "required_matches": 2
  22. }
  23. PUT /job-candidates/_doc/2?refresh
  24. {
  25. "name": "Jason Response",
  26. "programming_languages": [ "java", "php" ],
  27. "required_matches": 2
  28. }
  29. GET /job-candidates/_search
  30. {
  31. "query": {
  32. "terms_set": {
  33. "programming_languages": {
  34. "terms": [ "c++", "java", "php" ],
  35. "minimum_should_match_field": "required_matches"
  36. }
  37. }
  38. }
  39. }

在上面,我们为 job-candiates 索引创建了两个文档。我们需要找出在 programming_languages 中同时含有 c++, java 以及 php 中至少有两个 term 的文档。在这里,我们使用了一个在文档中定义的字段 required_matches 来定义最少满足要求的 term 个数。另外一种方式是使用 minimum_should_match_script 来定义,如果没有一个专有的字段来定义这个的话:

  1. GET /job-candidates/_search
  2. {
  3. "query": {
  4. "terms_set": {
  5. "programming_languages": {
  6. "terms": [ "c++", "java", "php" ],
  7. "minimum_should_match_script": {
  8. "source": "2"
  9. }
  10. }
  11. }
  12. }
  13. }

上面标示需要至少同时满足有 2 个及以上的 term。上面搜索的结果为:

  1. {
  2. "took" : 0,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 2,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 1.1005894,
  16. "hits" : [
  17. {
  18. "_index" : "job-candidates",
  19. "_type" : "_doc",
  20. "_id" : "1",
  21. "_score" : 1.1005894,
  22. "_source" : {
  23. "name" : "Jane Smith",
  24. "programming_languages" : [
  25. "c++",
  26. "java"
  27. ],
  28. "required_matches" : 2
  29. }
  30. },
  31. {
  32. "_index" : "job-candidates",
  33. "_type" : "_doc",
  34. "_id" : "2",
  35. "_score" : 1.1005894,
  36. "_source" : {
  37. "name" : "Jason Response",
  38. "programming_languages" : [
  39. "java",
  40. "php"
  41. ],
  42. "required_matches" : 2
  43. }
  44. }
  45. ]
  46. }
  47. }

也就是说之前的两个文档都同时满足条件。当然如果我们使用如下的方式来进行搜索:

  1. GET /job-candidates/_search
  2. {
  3. "query": {
  4. "terms_set": {
  5. "programming_languages": {
  6. "terms": [ "c++", "java", "nodejs" ],
  7. "minimum_should_match_script": {
  8. "source": "2"
  9. }
  10. }
  11. }
  12. }
  13. }

我们将看到只有一个文档是满足条件的。

 更多关于 terms set 的查询,请参阅文章 “Elasticsearch:Terms set 查询

复合查询(compound query)

在上面,我们用到了许多的 leaf 查询,比如:

  1. "query": {
  2. "match": {
  3. "city": "北京"
  4. }
  5. }

什么是复合查询呢?如果说上面的查询是 leaf 查询的话,那么复合查询可以把很多个 leaf 查询组合起来从而形成更为复杂的查询。它一般的格式是:

  1. POST _search
  2. {
  3. "query": {
  4. "bool" : {
  5. "must" : {
  6. "term" : { "user" : "kimchy" }
  7. },
  8. "filter": {
  9. "term" : { "tag" : "tech" }
  10. },
  11. "must_not" : {
  12. "range" : {
  13. "age" : { "gte" : 10, "lte" : 20 }
  14. }
  15. },
  16. "should" : [
  17. { "term" : { "tag" : "wow" } },
  18. { "term" : { "tag" : "elasticsearch" } }
  19. ],
  20. "minimum_should_match" : 1,
  21. "boost" : 1.0
  22. }
  23. }
  24. }

从上面我们可以看出,它是由 bool 下面的 must, must_not, should 及 filter 共同来组成的。你可以使用 minimum_should_match 参数指定返回的文档必须匹配的应当子句的数量或百分比。如果布尔查询包含至少一个 should 子句,并且没有 must 或 filter 子句,则默认值为1。否则,默认值为0。

布尔(bool)查询子句列表
子句解释
mustmust 子句包含查询,其中搜索条件必须在文档中匹配。 正匹配有助于提高相关性分数。 我们可以使用尽可能多的叶子查询来构建 must 子句
must_not在 must_not 子句中,条件不得与文档匹配。 该子句不会对分数做出贡献(它在过滤上下文执行上下文中运行)
should不强制要求在 should 子句中定义的标准必须匹配。 但是,如果匹配,相关性得分就会提高。有一个特殊情况必须匹配:在不含有 must, must_not 及 filter 的情况下,一个或更多的 should 必须有一个匹配才会有结果
filter在 filter 子句中,条件必须与文档匹配,类似于 must 子句。 唯一的区别是分数在过滤子句中是不相关的。 (它在过滤上下文执行上下文中运行)

针对我们的例子,

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {
  7. "match": {
  8. "city": "北京"
  9. }
  10. },
  11. {
  12. "match": {
  13. "age": "30"
  14. }
  15. }
  16. ]
  17. }
  18. }
  19. }

这个查询的是必须是 北京城市的,并且年刚好是30岁的。

  1. {
  2. "took" : 1,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 2,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 1.4823241,
  16. "hits" : [
  17. {
  18. "_index" : "twitter",
  19. "_type" : "_doc",
  20. "_id" : "2",
  21. "_score" : 1.4823241,
  22. "_source" : {
  23. "user" : "东城区-老刘",
  24. "message" : "出发,下一站云南!",
  25. "uid" : 3,
  26. "age" : 30,
  27. "city" : "北京",
  28. "province" : "北京",
  29. "country" : "中国",
  30. "address" : "中国北京市东城区台基厂三条3号",
  31. "location" : {
  32. "lat" : "39.904313",
  33. "lon" : "116.412754"
  34. }
  35. }
  36. },
  37. {
  38. "_index" : "twitter",
  39. "_type" : "_doc",
  40. "_id" : "3",
  41. "_score" : 1.4823241,
  42. "_source" : {
  43. "user" : "东城区-李四",
  44. "message" : "happy birthday!",
  45. "uid" : 4,
  46. "age" : 30,
  47. "city" : "北京",
  48. "province" : "北京",
  49. "country" : "中国",
  50. "address" : "中国北京市东城区",
  51. "location" : {
  52. "lat" : "39.893801",
  53. "lon" : "116.408986"
  54. }
  55. }
  56. }
  57. ]
  58. }
  59. }

如果我们想知道为什么得出来这样的结果,我们可以在搜索的指令中加入"explain" : true

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {
  7. "match": {
  8. "city": "北京"
  9. }
  10. },
  11. {
  12. "match": {
  13. "age": "30"
  14. }
  15. }
  16. ]
  17. }
  18. },
  19. "explain": true
  20. }

这样在我们的显示的结果中,可以看到一些一些解释:

我们的显示结果有2个。同样,我们可以把一些满足条件的排出在外,我们可以使用 must_not。

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must_not": [
  6. {
  7. "match": {
  8. "city": "北京"
  9. }
  10. }
  11. ]
  12. }
  13. }
  14. }

我们想寻找不在北京的所有的文档:

  1. {
  2. "took" : 0,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 1,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 0.0,
  16. "hits" : [
  17. {
  18. "_index" : "twitter",
  19. "_type" : "_doc",
  20. "_id" : "6",
  21. "_score" : 0.0,
  22. "_source" : {
  23. "user" : "虹桥-老吴",
  24. "message" : "好友来了都今天我生日,好友来了,什么 birthday happy 就成!",
  25. "uid" : 7,
  26. "age" : 90,
  27. "city" : "上海",
  28. "province" : "上海",
  29. "country" : "中国",
  30. "address" : "中国上海市闵行区",
  31. "location" : {
  32. "lat" : "31.175927",
  33. "lon" : "121.383328"
  34. }
  35. }
  36. }
  37. ]
  38. }
  39. }

我们显示的文档只有一个。他来自上海,其余的都北京的。

接下来,我们来尝试一下 should。它表述 “” 的意思,也就是有就更好,没有就算了。比如:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {
  7. "match": {
  8. "age": "30"
  9. }
  10. }
  11. ],
  12. "should": [
  13. {
  14. "match_phrase": {
  15. "message": "Happy birthday"
  16. }
  17. }
  18. ]
  19. }
  20. }
  21. }

这个搜寻的意思是,age 必须是30岁,但是如果文档里含有 “Hanppy birthday”,相关性会更高,那么搜索得到的结果会排在前面:

  1. {
  2. "took" : 8,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 2,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 2.641438,
  16. "hits" : [
  17. {
  18. "_index" : "twitter",
  19. "_type" : "_doc",
  20. "_id" : "3",
  21. "_score" : 2.641438,
  22. "_source" : {
  23. "user" : "东城区-李四",
  24. "message" : "happy birthday!",
  25. "uid" : 4,
  26. "age" : 30,
  27. "city" : "北京",
  28. "province" : "北京",
  29. "country" : "中国",
  30. "address" : "中国北京市东城区",
  31. "location" : {
  32. "lat" : "39.893801",
  33. "lon" : "116.408986"
  34. }
  35. }
  36. },
  37. {
  38. "_index" : "twitter",
  39. "_type" : "_doc",
  40. "_id" : "2",
  41. "_score" : 1.0,
  42. "_source" : {
  43. "user" : "东城区-老刘",
  44. "message" : "出发,下一站云南!",
  45. "uid" : 3,
  46. "age" : 30,
  47. "city" : "北京",
  48. "province" : "北京",
  49. "country" : "中国",
  50. "address" : "中国北京市东城区台基厂三条3号",
  51. "location" : {
  52. "lat" : "39.904313",
  53. "lon" : "116.412754"
  54. }
  55. }
  56. }
  57. ]
  58. }
  59. }

在上面的结果中,我们可以看到:同样是年龄30岁的两个文档,第一个文档由于含有 “Happy birthday” 这个字符串在 message 里,所以它的结果是排在前面的,相关性更高。我们可以从它的 _score 中可以看出来。第二个文档里 age 是30,但是它的 message 里没有 “Happy birthday” 字样,但是它的结果还是有显示,只是得分比较低一些。

在使用上面的复合查询时,bool 请求通常是 must,must_not, should 及 filter 的一个或其中的几个一起组合形成的。我们必须注意的是:

查询类型对 hits 及 _score 的影响
Clause影响 #hits影响 _score
mustYesYes
must_notYesNo
shouldNo*Yes
filterYesNo

如上面的表格所示,should 只有在特殊的情况下才会影响 hits。在正常的情况下它不会影响搜索文档的个数。那么在哪些情况下会影响搜索的结果呢?这种情况就是针对只有 should 的搜索情况,也就是如果你在 bool query 里,不含有 must, must_not 及 filter 的情况下,一个或更多的 should 必须有一个匹配才会有结果,比如:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "should": [
  6. {
  7. "match": {
  8. "city": "北京"
  9. }
  10. },
  11. {
  12. "match": {
  13. "city": "武汉"
  14. }
  15. }
  16. ]
  17. }
  18. }
  19. }

上面的查询显示结果为:

  1. "hits" : {
  2. "total" : {
  3. "value" : 5,
  4. "relation" : "eq"
  5. },
  6. "max_score" : 0.48232412,
  7. "hits" : [
  8. {
  9. "_index" : "twitter",
  10. "_type" : "_doc",
  11. "_id" : "1",
  12. "_score" : 0.48232412,
  13. "_source" : {
  14. "user" : "双榆树-张三",
  15. "message" : "今儿天气不错啊,出去转转去",
  16. "uid" : 2,
  17. "age" : 20,
  18. "city" : "北京",
  19. "province" : "北京",
  20. "country" : "中国",
  21. "address" : "中国北京市海淀区",
  22. "location" : {
  23. "lat" : "39.970718",
  24. "lon" : "116.325747"
  25. }
  26. }
  27. },
  28. {
  29. "_index" : "twitter",
  30. "_type" : "_doc",
  31. "_id" : "2",
  32. "_score" : 0.48232412,
  33. "_source" : {
  34. "user" : "东城区-老刘",
  35. "message" : "出发,下一站云南!",
  36. "uid" : 3,
  37. "age" : 30,
  38. "city" : "北京",
  39. "province" : "北京",
  40. "country" : "中国",
  41. "address" : "中国北京市东城区台基厂三条3号",
  42. "location" : {
  43. "lat" : "39.904313",
  44. "lon" : "116.412754"
  45. }
  46. }
  47. },
  48. ...
  49. }

在这种情况下,should 是会影响查询的结果的。如果我们使用 minimum_should_match 为2,也就是:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "should": [
  6. {
  7. "match": {
  8. "city": "北京"
  9. }
  10. },
  11. {
  12. "match": {
  13. "city": "武汉"
  14. }
  15. }
  16. ],
  17. "minimum_should_match": 2
  18. }
  19. }
  20. }

也就是上面的两个 should 都必须同时满足才能被搜索到。上面的查询结果为空,因为我们没有一个 city 同时是 “北京” 和 “武汉” 的。

位置查询

Elasticsearch 最厉害的是位置查询。这在很多的关系数据库里并没有。我们举一个简单的例子:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {
  7. "match": {
  8. "address": "北京"
  9. }
  10. }
  11. ]
  12. }
  13. },
  14. "post_filter": {
  15. "geo_distance": {
  16. "distance": "3km",
  17. "location": {
  18. "lat": 39.920086,
  19. "lon": 116.454182
  20. }
  21. }
  22. }
  23. }

在这里,我们查找在地址栏里有“北京”,并且在以位置(116.454182, 39.920086)为中心的3公里以内的所有文档。

  1. {
  2. "took" : 58,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 1,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 0.48232412,
  16. "hits" : [
  17. {
  18. "_index" : "twitter",
  19. "_type" : "_doc",
  20. "_id" : "5",
  21. "_score" : 0.48232412,
  22. "_source" : {
  23. "user" : "朝阳区-老王",
  24. "message" : "Happy BirthDay My Friend!",
  25. "uid" : 6,
  26. "age" : 50,
  27. "city" : "北京",
  28. "province" : "北京",
  29. "country" : "中国",
  30. "address" : "中国北京市朝阳区国贸",
  31. "location" : {
  32. "lat" : "39.918256",
  33. "lon" : "116.467910"
  34. }
  35. }
  36. }
  37. ]
  38. }
  39. }

在我们的查询结果中只有一个文档满足要求。

下面,我们找出在5公里以内的所有位置信息,并按照远近大小进行排序:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {
  7. "match": {
  8. "address": "北京"
  9. }
  10. }
  11. ]
  12. }
  13. },
  14. "post_filter": {
  15. "geo_distance": {
  16. "distance": "5km",
  17. "location": {
  18. "lat": 39.920086,
  19. "lon": 116.454182
  20. }
  21. }
  22. },
  23. "sort": [
  24. {
  25. "_geo_distance": {
  26. "location": "39.920086,116.454182",
  27. "order": "asc",
  28. "unit": "km"
  29. }
  30. }
  31. ]
  32. }

在这里,我们看到了使用 sort 来对我们的搜索的结果进行排序。按照升序排列。

  1. {
  2. "took" : 5,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 3,
  13. "relation" : "eq"
  14. },
  15. "max_score" : null,
  16. "hits" : [
  17. {
  18. "_index" : "twitter",
  19. "_type" : "_doc",
  20. "_id" : "5",
  21. "_score" : null,
  22. "_source" : {
  23. "user" : "朝阳区-老王",
  24. "message" : "Happy BirthDay My Friend!",
  25. "uid" : 6,
  26. "age" : 50,
  27. "city" : "北京",
  28. "province" : "北京",
  29. "country" : "中国",
  30. "address" : "中国北京市朝阳区国贸",
  31. "location" : {
  32. "lat" : "39.918256",
  33. "lon" : "116.467910"
  34. }
  35. },
  36. "sort" : [
  37. 1.1882901656104885
  38. ]
  39. },
  40. {
  41. "_index" : "twitter",
  42. "_type" : "_doc",
  43. "_id" : "2",
  44. "_score" : null,
  45. "_source" : {
  46. "user" : "东城区-老刘",
  47. "message" : "出发,下一站云南!",
  48. "uid" : 3,
  49. "age" : 30,
  50. "city" : "北京",
  51. "province" : "北京",
  52. "country" : "中国",
  53. "address" : "中国北京市东城区台基厂三条3号",
  54. "location" : {
  55. "lat" : "39.904313",
  56. "lon" : "116.412754"
  57. }
  58. },
  59. "sort" : [
  60. 3.9447355972239952
  61. ]
  62. },
  63. {
  64. "_index" : "twitter",
  65. "_type" : "_doc",
  66. "_id" : "3",
  67. "_score" : null,
  68. "_source" : {
  69. "user" : "东城区-李四",
  70. "message" : "happy birthday!",
  71. "uid" : 4,
  72. "age" : 30,
  73. "city" : "北京",
  74. "province" : "北京",
  75. "country" : "中国",
  76. "address" : "中国北京市东城区",
  77. "location" : {
  78. "lat" : "39.893801",
  79. "lon" : "116.408986"
  80. }
  81. },
  82. "sort" : [
  83. 4.837769064666224
  84. ]
  85. }
  86. ]
  87. }
  88. }

我们可以看到有三个显示的结果。在 sort 里我们可以看到距离是越来越大啊。另外我们可以看出来,如果 _score 不是 sort 的field,那么在使用 sort 后,所有的结果的 _score 都变为 null。如果排序的如果在上面的搜索也可以直接写作为:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": {
  6. "match": {
  7. "address": "北京"
  8. }
  9. },
  10. "filter": {
  11. "geo_distance": {
  12. "distance": "5km",
  13. "location": {
  14. "lat": 39.920086,
  15. "lon": 116.454182
  16. }
  17. }
  18. }
  19. }
  20. },
  21. "sort": [
  22. {
  23. "_geo_distance": {
  24. "location": "39.920086,116.454182",
  25. "order": "asc",
  26. "unit": "km"
  27. }
  28. }
  29. ]
  30. }

如果你想更多了解关于排序方面的知识,请阅读我的另外一篇文章 “Elasticsearch:对搜索结果排序 - Sort”。

范围查询

在 ES 中,我们也可以进行范围查询。我们可以根据设定的范围来对数据进行查询:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "range": {
  5. "age": {
  6. "gte": 30,
  7. "lte": 40
  8. }
  9. }
  10. }
  11. }

在这里,我们查询年龄介于30到40岁的文档:

  1. {
  2. "took" : 0,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 3,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 1.0,
  16. "hits" : [
  17. {
  18. "_index" : "twitter",
  19. "_type" : "_doc",
  20. "_id" : "2",
  21. "_score" : 1.0,
  22. "_source" : {
  23. "user" : "东城区-老刘",
  24. "message" : "出发,下一站云南!",
  25. "uid" : 3,
  26. "age" : 30,
  27. "city" : "北京",
  28. "province" : "北京",
  29. "country" : "中国",
  30. "address" : "中国北京市东城区台基厂三条3号",
  31. "location" : {
  32. "lat" : "39.904313",
  33. "lon" : "116.412754"
  34. }
  35. }
  36. },
  37. {
  38. "_index" : "twitter",
  39. "_type" : "_doc",
  40. "_id" : "3",
  41. "_score" : 1.0,
  42. "_source" : {
  43. "user" : "东城区-李四",
  44. "message" : "happy birthday!",
  45. "uid" : 4,
  46. "age" : 30,
  47. "city" : "北京",
  48. "province" : "北京",
  49. "country" : "中国",
  50. "address" : "中国北京市东城区",
  51. "location" : {
  52. "lat" : "39.893801",
  53. "lon" : "116.408986"
  54. }
  55. }
  56. },
  57. {
  58. "_index" : "twitter",
  59. "_type" : "_doc",
  60. "_id" : "4",
  61. "_score" : 1.0,
  62. "_source" : {
  63. "user" : "朝阳区-老贾",
  64. "message" : "123,gogogo",
  65. "uid" : 5,
  66. "age" : 35,
  67. "city" : "北京",
  68. "province" : "北京",
  69. "country" : "中国",
  70. "address" : "中国北京市朝阳区建国门",
  71. "location" : {
  72. "lat" : "39.718256",
  73. "lon" : "116.367910"
  74. }
  75. }
  76. }
  77. ]
  78. }
  79. }

如上所示,我们找到了3个匹配的文档。同样地,我们也可以对它们进行排序:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "range": {
  5. "age": {
  6. "gte": 30,
  7. "lte": 40
  8. }
  9. }
  10. },
  11. "sort": [
  12. {
  13. "age": {
  14. "order": "desc"
  15. }
  16. }
  17. ]
  18. }

我们对整个搜索的结果按照降序进行排序。我们甚至可以针对多个字段同时进行排序:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "range": {
  5. "age": {
  6. "gte": 30,
  7. "lte": 40
  8. }
  9. }
  10. },
  11. "sort": [
  12. {
  13. "age": {
  14. "order": "desc"
  15. }
  16. },
  17. {
  18. "_geo_distance": {
  19. "location": {
  20. "lat": 39.920086,
  21. "lon": 116.454182
  22. },
  23. "order": "asc"
  24. }
  25. }
  26. ]
  27. }

上面首先以 age 进行降序排序。如果是 age 是一样的话,那么久按照距离来进行排序。

在本实例中,我们没有 date 这样的字段。如果有的话,我们可以添加 format 来显示易于阅读的格式:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "range": {
  5. "age": {
  6. "gte": 30,
  7. "lte": 40
  8. }
  9. }
  10. },
  11. "sort": [
  12. {
  13. "age": {
  14. "order": "desc"
  15. }
  16. },
  17. {
  18. "DOB": {
  19. "order": "desc",
  20. "format": "date"
  21. }
  22. }
  23. ]
  24. }

在上面,我们使用 "format": "date" 来显示如下的格式:

 否则,我们可能看到的是是一个整型值。

针对一些搜索,我们甚至可以使用 _doc 来进行排序。这中排序是基于 _id 来进行排序的:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "range": {
  5. "age": {
  6. "gte": 30,
  7. "lte": 40
  8. }
  9. }
  10. },
  11. "sort": [
  12. "_doc"
  13. ]
  14. }

上面搜索的结果为:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "2",
  6. "_score" : null,
  7. "_source" : {
  8. "user" : "东城区-老刘",
  9. "message" : "出发,下一站云南!",
  10. "uid" : 3,
  11. "age" : 30,
  12. "city" : "北京",
  13. "province" : "北京",
  14. "country" : "中国",
  15. "address" : "中国北京市东城区台基厂三条3号",
  16. "location" : {
  17. "lat" : "39.904313",
  18. "lon" : "116.412754"
  19. }
  20. },
  21. "sort" : [
  22. 1
  23. ]
  24. },
  25. {
  26. "_index" : "twitter",
  27. "_type" : "_doc",
  28. "_id" : "3",
  29. "_score" : null,
  30. "_source" : {
  31. "user" : "东城区-李四",
  32. "message" : "happy birthday!",
  33. "uid" : 4,
  34. "age" : 30,
  35. "city" : "北京",
  36. "province" : "北京",
  37. "country" : "中国",
  38. "address" : "中国北京市东城区",
  39. "location" : {
  40. "lat" : "39.893801",
  41. "lon" : "116.408986"
  42. }
  43. },
  44. "sort" : [
  45. 2
  46. ]
  47. },
  48. {
  49. "_index" : "twitter",
  50. "_type" : "_doc",
  51. "_id" : "4",
  52. "_score" : null,
  53. "_source" : {
  54. "user" : "朝阳区-老贾",
  55. "message" : "123,gogogo",
  56. "uid" : 5,
  57. "age" : 35,
  58. "city" : "北京",
  59. "province" : "北京",
  60. "country" : "中国",
  61. "address" : "中国北京市朝阳区建国门",
  62. "location" : {
  63. "lat" : "39.718256",
  64. "lon" : "116.367910"
  65. }
  66. },
  67. "sort" : [
  68. 3
  69. ]
  70. }
  71. ]

我们可以看到 id 值是从下到大进行排列的。

当我们使用任何字段进行排序时,Elasticsearch 不会计算分数。 但是,有一种方法可以让 Elasticsearch 计算分数,即使你不再按 _score 排序。 为此,你将使用 track_scores 布尔字段。 下面的清单显示了如何为引擎设置 track_score 以计算此实例中的分数。

  1. GET twitter/_search
  2. {
  3. "track_scores": true,
  4. "query": {
  5. "range": {
  6. "age": {
  7. "gte": 30,
  8. "lte": 40
  9. }
  10. }
  11. },
  12. "sort": [
  13. "_doc"
  14. ]
  15. }

在上面的命令中显示的 track_scores 属性为引擎提供了一个提示来计算文档的相关性分数。 但是,它们不会根据 _score 属性进行排序,因为自定义字段 _doc 用于排序。

Exists 查询

我们可以通过 exists 来查询一个字段是否存在。比如我们再增加一个文档:

  1. PUT twitter/_doc/20
  2. {
  3. "user" : "王二",
  4. "message" : "今儿天气不错啊,出去转转去",
  5. "uid" : 20,
  6. "age" : 40,
  7. "province" : "北京",
  8. "country" : "中国",
  9. "address" : "中国北京市海淀区",
  10. "location" : {
  11. "lat" : "39.970718",
  12. "lon" : "116.325747"
  13. }
  14. }

在这个文档里,我们的 city 这一个字段是不存在的,那么一下的这个搜索将不会返回上面的这个搜索。

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "exists": {
  5. "field": "city"
  6. }
  7. }
  8. }

如果文档里只要 city 这个字段不为空,那么就会被返回。反之,如果一个文档里city这个字段是空的,那么就不会返回。

如果查询不含 city 这个字段的所有的文档,可以这样查询:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must_not": {
  6. "exists": {
  7. "field": "city"
  8. }
  9. }
  10. }
  11. }
  12. }

假如我们创建另外一个索引 twitter1,我们打入如下的命令:

  1. PUT twitter1/_doc/1
  2. {
  3. "locale": null
  4. }

然后,我们使用如下的命令来进行查询:

  1. GET twitter1/_search
  2. {
  3. "query": {
  4. "exists": {
  5. "field": "locale"
  6. }
  7. }
  8. }

上面查询的结果显示:

  1. {
  2. "took" : 0,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 0,
  13. "relation" : "eq"
  14. },
  15. "max_score" : null,
  16. "hits" : [ ]
  17. }
  18. }

也就是没有找到。

如果你想找到一个 missing 的字段,你可以使用如下的方法:

  1. GET twitter1/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must_not": [
  6. {
  7. "exists": {
  8. "field": "locale"
  9. }
  10. }
  11. ]
  12. }
  13. }
  14. }

上面的方法返回的数据是:

  1. {
  2. "took" : 0,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 1,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 0.0,
  16. "hits" : [
  17. {
  18. "_index" : "twitter1",
  19. "_type" : "_doc",
  20. "_id" : "1",
  21. "_score" : 0.0,
  22. "_source" : {
  23. "locale" : null
  24. }
  25. }
  26. ]
  27. }
  28. }

显然这个就是我们想要的结果。

匹配短语

我们可以通过如下的方法来查找 happy birthday。

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match": {
  5. "message": "happy birthday"
  6. }
  7. }
  8. }

展示的结果:

  1. {
  2. "took" : 0,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 3,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 1.9936417,
  16. "hits" : [
  17. {
  18. "_index" : "twitter",
  19. "_type" : "_doc",
  20. "_id" : "3",
  21. "_score" : 1.9936417,
  22. "_source" : {
  23. "user" : "东城区-李四",
  24. "message" : "happy birthday!",
  25. "uid" : 4,
  26. "age" : 30,
  27. "city" : "北京",
  28. "province" : "北京",
  29. "country" : "中国",
  30. "address" : "中国北京市东城区",
  31. "location" : {
  32. "lat" : "39.893801",
  33. "lon" : "116.408986"
  34. }
  35. }
  36. },
  37. {
  38. "_index" : "twitter",
  39. "_type" : "_doc",
  40. "_id" : "5",
  41. "_score" : 1.733287,
  42. "_source" : {
  43. "user" : "朝阳区-老王",
  44. "message" : "Happy BirthDay My Friend!",
  45. "uid" : 6,
  46. "age" : 50,
  47. "city" : "北京",
  48. "province" : "北京",
  49. "country" : "中国",
  50. "address" : "中国北京市朝阳区国贸",
  51. "location" : {
  52. "lat" : "39.918256",
  53. "lon" : "116.467910"
  54. }
  55. }
  56. },
  57. {
  58. "_index" : "twitter",
  59. "_type" : "_doc",
  60. "_id" : "6",
  61. "_score" : 0.84768087,
  62. "_source" : {
  63. "user" : "虹桥-老吴",
  64. "message" : "好友来了都今天我生日,好友来了,什么 birthday happy 就成!",
  65. "uid" : 7,
  66. "age" : 90,
  67. "city" : "上海",
  68. "province" : "上海",
  69. "country" : "中国",
  70. "address" : "中国上海市闵行区",
  71. "location" : {
  72. "lat" : "31.175927",
  73. "lon" : "121.383328"
  74. }
  75. }
  76. }
  77. ]
  78. }
  79. }

在默认的情况下,这个匹配是“”的关系,也就是找到文档里含有 “Happy" 或者 “birthday” 的文档。如果我们新增加一个文档:

  1. PUT twitter/_doc/8
  2. {
  3. "user": "朝阳区-老王",
  4. "message": "Happy",
  5. "uid": 6,
  6. "age": 50,
  7. "city": "北京",
  8. "province": "北京",
  9. "country": "中国",
  10. "address": "中国北京市朝阳区国贸",
  11. "location": {
  12. "lat": "39.918256",
  13. "lon": "116.467910"
  14. }
  15. }

那么我们重新进行搜索,我们可以看到这个新增加的 id 为8的也会在搜索出的结果之列,虽然它只含有 “Happy" 在 message 里。

如果我们想得到“”的关系,我们可以采用如下的办法:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match": {
  5. "message": {
  6. "query": "happy birthday",
  7. "operator": "and"
  8. }
  9. }
  10. }
  11. }

经过这样的修改,我们再也看不见那个id为8的文档了,这是因为我们必须在 message 中同时匹配 “happy” 及 “birthday” 这两个词。

我们还有一种方法,那就是:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match": {
  5. "message": {
  6. "query": "happy birthday",
  7. "minimum_should_match": 2
  8. }
  9. }
  10. }
  11. }

在这里,我们采用了 “minimum_should_match” 来表面至少有2个是匹配的才可以。

我们可以看到在搜索到的结果中,无论我们搜索的是大小写字母,在搜索的时候,我们都可以匹配到,并且在 message 中,happy birthday 这两个词的先后顺序也不是很重要。比如,我们把 id 为5的文档更改为:

  1. PUT twitter/_doc/5
  2. {
  3. "user": "朝阳区-老王",
  4. "message": "BirthDay My Friend Happy !",
  5. "uid": 6,
  6. "age": 50,
  7. "city": "北京",
  8. "province": "北京",
  9. "country": "中国",
  10. "address": "中国北京市朝阳区国贸",
  11. "location": {
  12. "lat": "39.918256",
  13. "lon": "116.467910"
  14. }
  15. }

在这里,我们有意识地把 BirthDay 弄到 Happy 的前面。我们再次使用上面的查询看看是否找到 id 为5的文档。

显然,match 查询时时不用分先后顺序的。我们下面使用 match_phrase 来看看。

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match_phrase": {
  5. "message": "Happy birthday"
  6. }
  7. },
  8. "highlight": {
  9. "fields": {
  10. "message": {}
  11. }
  12. }
  13. }

在这里,我们可以看到我们使用了match_phrase。它要求 Happy 必须是在 birthday 的前面。下面是搜寻的结果:

  1. {
  2. "took": 1,
  3. "timed_out": false,
  4. "_shards": {
  5. "total": 1,
  6. "successful": 1,
  7. "skipped": 0,
  8. "failed": 0
  9. },
  10. "hits": {
  11. "total": {
  12. "value": 2,
  13. "relation": "eq"
  14. },
  15. "max_score": 1.9936416,
  16. "hits": [
  17. {
  18. "_index": "twitter",
  19. "_id": "3",
  20. "_score": 1.9936416,
  21. "_source": {
  22. "user": "东城区-李四",
  23. "message": "happy birthday!",
  24. "uid": 4,
  25. "age": 30,
  26. "city": "北京",
  27. "province": "北京",
  28. "country": "中国",
  29. "address": "中国北京市东城区",
  30. "location": {
  31. "lat": "39.893801",
  32. "lon": "116.408986"
  33. }
  34. },
  35. "highlight": {
  36. "message": [
  37. "<em>happy</em> <em>birthday</em>!"
  38. ]
  39. }
  40. },
  41. {
  42. "_index": "twitter",
  43. "_id": "5",
  44. "_score": 1.7332871,
  45. "_source": {
  46. "user": "朝阳区-老王",
  47. "message": "Happy BirthDay My Friend!",
  48. "uid": 6,
  49. "age": 50,
  50. "city": "北京",
  51. "province": "北京",
  52. "country": "中国",
  53. "address": "中国北京市朝阳区国贸",
  54. "location": {
  55. "lat": "39.918256",
  56. "lon": "116.467910"
  57. }
  58. },
  59. "highlight": {
  60. "message": [
  61. "<em>Happy</em> <em>BirthDay</em> My Friend!"
  62. ]
  63. }
  64. }
  65. ]
  66. }
  67. }

假如我们把我们之前的那个 id 为 5 的文档修改为:

  1. PUT twitter/_doc/5
  2. {
  3. "user": "朝阳区-老王",
  4. "message": "Happy Good BirthDay My Friend!",
  5. "uid": 6,
  6. "age": 50,
  7. "city": "北京",
  8. "province": "北京",
  9. "country": "中国",
  10. "address": "中国北京市朝阳区国贸",
  11. "location": {
  12. "lat": "39.918256",
  13. "lon": "116.467910"
  14. }
  15. }

在这里,我们在 Happy 和 Birthday之前加入了一个 Good。如果用我们之前的那个 match_phrase 是找不到这个文档的。为了能够找到上面这个修正的结果,我们可以使用:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match_phrase": {
  5. "message": {
  6. "query": "Happy birthday",
  7. "slop": 1
  8. }
  9. }
  10. },
  11. "highlight": {
  12. "fields": {
  13. "message": {}
  14. }
  15. }
  16. }

注意:在这里,我们使用了 slop 为1,表面 Happy 和 birthday 之前是可以允许一个 token 的差别。上面命令的搜索结果将会显示如下的两个结果:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_id" : "3",
  5. "_score" : 1.6414379,
  6. "_source" : {
  7. "user" : "东城区-李四",
  8. "message" : "happy birthday!",
  9. "uid" : 4,
  10. "age" : 30,
  11. "city" : "北京",
  12. "province" : "北京",
  13. "country" : "中国",
  14. "address" : "中国北京市东城区",
  15. "location" : {
  16. "lat" : "39.893801",
  17. "lon" : "116.408986"
  18. }
  19. },
  20. "highlight" : {
  21. "message" : [
  22. "<em>happy</em> <em>birthday</em>!"
  23. ]
  24. }
  25. },
  26. {
  27. "_index" : "twitter",
  28. "_id" : "5",
  29. "_score" : 0.90043306,
  30. "_source" : {
  31. "user" : "朝阳区-老王",
  32. "message" : "Happy Good BirthDay My Friend!",
  33. "uid" : 6,
  34. "age" : 50,
  35. "city" : "北京",
  36. "province" : "北京",
  37. "country" : "中国",
  38. "address" : "中国北京市朝阳区国贸",
  39. "location" : {
  40. "lat" : "39.918256",
  41. "lon" : "116.467910"
  42. }
  43. },
  44. "highlight" : {
  45. "message" : [
  46. "<em>Happy</em> Good <em>BirthDay</em> My Friend!"
  47. ]
  48. }
  49. }
  50. ]

我们接下来再次修改 id 为 5 的文档如下:

  1. PUT twitter/_doc/5
  2. {
  3. "user": "朝阳区-老王",
  4. "message": "Birthday Good Happy My Friend!",
  5. "uid": 6,
  6. "age": 50,
  7. "city": "北京",
  8. "province": "北京",
  9. "country": "中国",
  10. "address": "中国北京市朝阳区国贸",
  11. "location": {
  12. "lat": "39.918256",
  13. "lon": "116.467910"
  14. }
  15. }

在上面的文档中,我们把 Birthday 和 Happy 进行了交换。运行上面的命令。它将更新 id 为 5 的文档。

我们再次运行如下的搜索:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match_phrase": {
  5. "message": {
  6. "query": "Happy birthday",
  7. "slop": 1
  8. }
  9. }
  10. }
  11. }

这个时候,我们只搜索到如下的一个文档:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_id" : "3",
  5. "_score" : 1.2140245,
  6. "_source" : {
  7. "user" : "东城区-李四",
  8. "message" : "happy birthday!",
  9. "uid" : 4,
  10. "age" : 30,
  11. "city" : "北京",
  12. "province" : "北京",
  13. "country" : "中国",
  14. "address" : "中国北京市东城区",
  15. "location" : {
  16. "lat" : "39.893801",
  17. "lon" : "116.408986"
  18. }
  19. }
  20. }

我们把 slop 设置为 2,我们再次查看搜索的结果:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match_phrase": {
  5. "message": {
  6. "query": "Happy birthday",
  7. "slop": 2
  8. }
  9. }
  10. }
  11. }

这次我们看到如下的两个结果:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_id" : "3",
  5. "_score" : 1.2140245,
  6. "_source" : {
  7. "user" : "东城区-李四",
  8. "message" : "happy birthday!",
  9. "uid" : 4,
  10. "age" : 30,
  11. "city" : "北京",
  12. "province" : "北京",
  13. "country" : "中国",
  14. "address" : "中国北京市东城区",
  15. "location" : {
  16. "lat" : "39.893801",
  17. "lon" : "116.408986"
  18. }
  19. }
  20. },
  21. {
  22. "_index" : "twitter",
  23. "_id" : "6",
  24. "_score" : 0.19463003,
  25. "_source" : {
  26. "user" : "虹桥-老吴",
  27. "message" : "好友来了都今天我生日,好友来了,什么 birthday happy 就成!",
  28. "uid" : 7,
  29. "age" : 90,
  30. "city" : "上海",
  31. "province" : "上海",
  32. "country" : "中国",
  33. "address" : "中国上海市闵行区",
  34. "location" : {
  35. "lat" : "31.175927",
  36. "lon" : "121.383328"
  37. }
  38. }
  39. }
  40. ]

有意思的是我们搜索到了 id 为 6 的文档。它里面含有 Birthday Happy,而不是 Happy Birthday。这是为什么呢?我们再次查看 Elastic 的官方文档。我们发现转置项(transposed terms)的 slop 为 2。也就是说当 Happy 和 Birthday 进行位置转换后,它的 slop 值为 2。这也就说明了为什么上面的 id 为 6 的文档为啥被搜索到。同样当我们把 slop 设置为 3,再来进行如下的搜索:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "match_phrase": {
  5. "message": {
  6. "query": "Happy birthday",
  7. "slop": 3
  8. }
  9. }
  10. }
  11. }

这次我们搜索到的结果是:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_id" : "3",
  5. "_score" : 1.3956282,
  6. "_source" : {
  7. "user" : "东城区-李四",
  8. "message" : "happy birthday!",
  9. "uid" : 4,
  10. "age" : 30,
  11. "city" : "北京",
  12. "province" : "北京",
  13. "country" : "中国",
  14. "address" : "中国北京市东城区",
  15. "location" : {
  16. "lat" : "39.893801",
  17. "lon" : "116.408986"
  18. }
  19. }
  20. },
  21. {
  22. "_index" : "twitter",
  23. "_id" : "5",
  24. "_score" : 0.45847476,
  25. "_source" : {
  26. "user" : "朝阳区-老王",
  27. "message" : "Birthday Good Happy My Friend!",
  28. "uid" : 6,
  29. "age" : 50,
  30. "city" : "北京",
  31. "province" : "北京",
  32. "country" : "中国",
  33. "address" : "中国北京市朝阳区国贸",
  34. "location" : {
  35. "lat" : "39.918256",
  36. "lon" : "116.467910"
  37. }
  38. }
  39. },
  40. {
  41. "_index" : "twitter",
  42. "_id" : "6",
  43. "_score" : 0.22860086,
  44. "_source" : {
  45. "user" : "虹桥-老吴",
  46. "message" : "好友来了都今天我生日,好友来了,什么 birthday happy 就成!",
  47. "uid" : 7,
  48. "age" : 90,
  49. "city" : "上海",
  50. "province" : "上海",
  51. "country" : "中国",
  52. "address" : "中国上海市闵行区",
  53. "location" : {
  54. "lat" : "31.175927",
  55. "lon" : "121.383328"
  56. }
  57. }
  58. }
  59. ]

上面的结果显示:除了之前 slop 设置为 2 的文档都被搜索到了,我们还搜索出来 id 为 5 的这个文档。这是为什么呢?它的原因是转置需要 2 个,而 Birthday 和 Happy 之间还多了一个 Good,所以需要 slop 为 2 + 1 = 3 才能搜索到这个文档。

Match phrase prefix query

以与提供的相同顺序返回包含所提供文本的单词的文档。 所提供文本的最后一个词被视为前缀,匹配以该词开头的任何单词。比如:

  1. GET twitter/_search?filter_path=**.hits
  2. {
  3. "query": {
  4. "match_phrase_prefix": {
  5. "message": {
  6. "query": "happy birthday m"
  7. }
  8. }
  9. }
  10. }

在上面我们搜索 message 里含有 happy birthday,并且 happy 和 birthday 是按照顺序出现的,happy 在 birtday 的前面,而后面的一个单词的首字母是 m。这个类似于在一个搜索引擎里打入如下的词:

上面的搜索结果是:

  1. {
  2. "hits" : {
  3. "hits" : [
  4. {
  5. "_index" : "twitter",
  6. "_id" : "5",
  7. "_score" : 3.6593091,
  8. "_source" : {
  9. "user" : "朝阳区-老王",
  10. "message" : "Happy BirthDay My Friend!",
  11. "uid" : 6,
  12. "age" : 50,
  13. "city" : "北京",
  14. "province" : "北京",
  15. "country" : "中国",
  16. "address" : "中国北京市朝阳区国贸",
  17. "location" : {
  18. "lat" : "39.918256",
  19. "lon" : "116.467910"
  20. }
  21. }
  22. }
  23. ]
  24. }
  25. }

Match boolean prefix query

match_bool_prefix 查询分析其输入并根据这些词构造一个 bool 查询。 除了最后一个术语之外的每个术语都用于术语查询。 最后一个词用于前缀查询。 match_bool_prefix 查询,例如:

  1. GET twitter/_search?filter_path=**.hits
  2. {
  3. "query": {
  4. "match_bool_prefix" : {
  5. "message" : "happy birthday m"
  6. }
  7. }
  8. }

上面查询返回的结果为:

  1. {
  2. "hits" : {
  3. "hits" : [
  4. {
  5. "_index" : "twitter",
  6. "_id" : "5",
  7. "_score" : 2.733287,
  8. "_source" : {
  9. "user" : "朝阳区-老王",
  10. "message" : "Happy BirthDay My Friend!",
  11. "uid" : 6,
  12. "age" : 50,
  13. "city" : "北京",
  14. "province" : "北京",
  15. "country" : "中国",
  16. "address" : "中国北京市朝阳区国贸",
  17. "location" : {
  18. "lat" : "39.918256",
  19. "lon" : "116.467910"
  20. }
  21. }
  22. },
  23. {
  24. "_index" : "twitter",
  25. "_id" : "3",
  26. "_score" : 1.9936416,
  27. "_source" : {
  28. "user" : "东城区-李四",
  29. "message" : "happy birthday!",
  30. "uid" : 4,
  31. "age" : 30,
  32. "city" : "北京",
  33. "province" : "北京",
  34. "country" : "中国",
  35. "address" : "中国北京市东城区",
  36. "location" : {
  37. "lat" : "39.893801",
  38. "lon" : "116.408986"
  39. }
  40. }
  41. },
  42. {
  43. "_index" : "twitter",
  44. "_id" : "6",
  45. "_score" : 0.8476808,
  46. "_source" : {
  47. "user" : "虹桥-老吴",
  48. "message" : "好友来了都今天我生日,好友来了,什么 birthday happy 就成!",
  49. "uid" : 7,
  50. "age" : 90,
  51. "city" : "上海",
  52. "province" : "上海",
  53. "country" : "中国",
  54. "address" : "中国上海市闵行区",
  55. "location" : {
  56. "lat" : "31.175927",
  57. "lon" : "121.383328"
  58. }
  59. }
  60. }
  61. ]
  62. }
  63. }

这个查询的结果想当于如下的查询:

  1. GET twitter/_search?filter_path=**.hits
  2. {
  3. "query": {
  4. "bool" : {
  5. "should": [
  6. { "term": { "message": "happy" }},
  7. { "term": { "message": "birthday" }},
  8. { "prefix": { "message": "f"}}
  9. ]
  10. }
  11. }
  12. }

Named queries

我们可以使用 _name 为一个 filter 或 query 来取一个名字,比如:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {
  7. "match": {
  8. "city": {
  9. "query": "北京",
  10. "_name": "城市"
  11. }
  12. }
  13. },
  14. {
  15. "match": {
  16. "country": {
  17. "query": "中国",
  18. "_name": "国家"
  19. }
  20. }
  21. }
  22. ],
  23. "should": [
  24. {
  25. "match": {
  26. "_id": {
  27. "query": "1",
  28. "_name": "ID"
  29. }
  30. }
  31. }
  32. ]
  33. }
  34. }
  35. }

返回结果:

  1. "hits" : [
  2. {
  3. "_index" : "twitter",
  4. "_type" : "_doc",
  5. "_id" : "1",
  6. "_score" : 1.6305401,
  7. "_source" : {
  8. "user" : "双榆树-张三",
  9. "message" : "今儿天气不错啊,出去转转去",
  10. "uid" : 2,
  11. "age" : 20,
  12. "city" : "北京",
  13. "province" : "北京",
  14. "country" : "中国",
  15. "address" : "中国北京市海淀区",
  16. "location" : {
  17. "lat" : "39.970718",
  18. "lon" : "116.325747"
  19. }
  20. },
  21. "matched_queries" : [
  22. "国家",
  23. "ID",
  24. "城市"
  25. ]
  26. },
  27. {
  28. "_index" : "twitter",
  29. "_type" : "_doc",
  30. "_id" : "2",
  31. "_score" : 0.6305401,
  32. "_source" : {
  33. "user" : "东城区-老刘",
  34. "message" : "出发,下一站云南!",
  35. "uid" : 3,
  36. "age" : 30,
  37. "city" : "北京",
  38. "province" : "北京",
  39. "country" : "中国",
  40. "address" : "中国北京市东城区台基厂三条3号",
  41. "location" : {
  42. "lat" : "39.904313",
  43. "lon" : "116.412754"
  44. }
  45. },
  46. "matched_queries" : [
  47. "国家",
  48. "城市"
  49. ]
  50. },
  51. ...
  52. ]

我们从上面的返回结果可以看出来多了一个叫做 matched_queries 的字段。在它的里面罗列了每个匹配了的查询。第一个返回的查询结果是三个都匹配了的,但是第二个来说就只有两项是匹配的。

通配符查询

我们可以使用 wildcard 查询一个字符串里含有的字符:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "wildcard": {
  5. "city.keyword": {
  6. "value": "*海"
  7. }
  8. }
  9. }
  10. }

上面查询在 city 这个关键字中含有“海”的文档。上面的搜寻结果是:

  1. {
  2. "took" : 0,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 1,
  6. "successful" : 1,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : {
  12. "value" : 1,
  13. "relation" : "eq"
  14. },
  15. "max_score" : 1.0,
  16. "hits" : [
  17. {
  18. "_index" : "twitter",
  19. "_type" : "_doc",
  20. "_id" : "6",
  21. "_score" : 1.0,
  22. "_source" : {
  23. "user" : "虹桥-老吴",
  24. "message" : "好友来了都今天我生日,好友来了,什么 birthday happy 就成!",
  25. "uid" : 7,
  26. "age" : 90,
  27. "city" : "上海",
  28. "province" : "上海",
  29. "country" : "中国",
  30. "address" : "中国上海市闵行区",
  31. "location" : {
  32. "lat" : "31.175927",
  33. "lon" : "121.383328"
  34. }
  35. }
  36. }
  37. ]
  38. }
  39. }

我们可以看到查到 city 为 “上海” 的文档。

Disjunction max 查询

返回与一个或多个包在一起的查询(称为查询子句或子句)匹配的文档。

如果返回的文档与多个查询子句匹配,则 dis_max 查询为该文档分配来自任何匹配子句的最高相关性得分,并为任何其他匹配子查询分配平局打破增量。

你可以使用 dis_max 在以不同  boost 因子映射的字段中搜索术语。比如:

  1. GET twitter/_search
  2. {
  3. "query": {
  4. "dis_max": {
  5. "queries": [
  6. {
  7. "term": {
  8. "city.keyword": "北京"
  9. }
  10. },
  11. {
  12. "match": {
  13. "address": "北京"
  14. }
  15. }
  16. ],
  17. "tie_breaker": 0.7
  18. }
  19. }
  20. }

在上面的 dis_max 查询中,它将返回任何一个在 queries 中所定的查询的文档。每个匹配分分数是按照如下的规则来进行计算的:

  • 如果一个文档匹配其中的一个或多个查询,那么最终的得分将以其中最高的那个得分来进行计算
  • 在默认的情况下,tie_breaker 的值为0。它可以是 0 到 1.0 之间的数

如果文档匹配多个子句,则 dis_max 查询将计算该文档的相关性得分,如下所示:

  • 从具有最高分数的匹配子句中获取相关性分数。
  • 将来自其他任何匹配子句的得分乘以 tie_breaker 值。
  • 将最高分数加到相乘的分数上。

如果 tie_breaker 值大于0.0,则所有匹配子句均计数,但得分最高的子句计数最高。


SQL 查询

对于与很多已经习惯用 RDMS 数据库的工作人员,他们更喜欢使用 SQL 来进行查询。Elasticsearch 也对 SQL 有支持:

  1. GET /_sql?
  2. {
  3. "query": """
  4. SELECT * FROM twitter
  5. WHERE age = 30
  6. """
  7. }

通过这个查询,我们可以找到所有在年龄等于30的用户。在个搜索中,我们使用了 SQL 语句。利用 SQL 端点我们可以很快地把我们的 SQL 知识转化为 Elasticsearch 的使用场景中来。我们可以通过如下的方法得到它对应的 DSL 语句:

  1. GET /_sql/translate
  2. {
  3. "query": """
  4. SELECT * FROM twitter
  5. WHERE age = 30
  6. """
  7. }

我们得到的结果是:

  1. {
  2. "size" : 1000,
  3. "query" : {
  4. "term" : {
  5. "age" : {
  6. "value" : 30,
  7. "boost" : 1.0
  8. }
  9. }
  10. },
  11. "_source" : {
  12. "includes" : [
  13. "address",
  14. "message",
  15. "region",
  16. "script.source",
  17. "user"
  18. ],
  19. "excludes" : [ ]
  20. },
  21. "docvalue_fields" : [
  22. {
  23. "field" : "age"
  24. },
  25. {
  26. "field" : "city"
  27. },
  28. {
  29. "field" : "country"
  30. },
  31. {
  32. "field" : "location"
  33. },
  34. {
  35. "field" : "province"
  36. },
  37. {
  38. "field" : "script.params.value"
  39. },
  40. {
  41. "field" : "uid"
  42. }
  43. ],
  44. "sort" : [
  45. {
  46. "_doc" : {
  47. "order" : "asc"
  48. }
  49. }
  50. ]
  51. }

 如果你想了解更多关于Elasticsearch EQL,请参阅我的另外一篇文章 “Elasticsearch SQL介绍及实例”。

Multi Search API

使用单个 API 请求执行几次搜索。这个 API 的好处是节省 API 的请求个数,把多个请求放到一个 API 请求中来实现。

为了说明问题的方便,我们可以多加一个叫做 twitter1 的 index。它的内容如下:

  1. POST _bulk
  2. {"index":{"_index":"twitter1","_id":1}}
  3. {"user":"张庆","message":"今儿天气不错啊,出去转转去","uid":2,"age":20,"city":"重庆","province":"重庆","country":"中国","address":"中国重庆地区","location":{"lat":"39.970718","lon":"116.325747"}}

这样在我们的 Elasticsearch 中就有两个索引了。我们可以做如下的 _msearch。

  1. GET twitter/_msearch
  2. {"index":"twitter"}
  3. {"query":{"match_all":{}},"from":0,"size":1}
  4. {"index":"twitter"}
  5. {"query":{"bool":{"filter":{"term":{"city.keyword":"北京"}}}}, "size":1}
  6. {"index":"twitter1"}
  7. {"query":{"match_all":{}}}

上面我们通过 _msearch 终点来实现在一个 API 请求中做多个查询,对多个 index 进行同时操作。显示结果为:

Contant score 查询

在上面的一个例子中,我们已经使用过 constant_score 查询。我们知道 filter 查询是不会对结果进行打分的,在默认的情况下,它返回的分数是 1.0,比如:

  1. GET twitter/_search?filter_path=**.hits
  2. {
  3. "query": {
  4. "bool": {
  5. "filter": [
  6. {
  7. "match": {
  8. "city": "北京"
  9. }
  10. }
  11. ]
  12. }
  13. }
  14. }

在上面,我们过滤文档中 city 字段中含有 “北京” 的文档:

  1. {
  2. "hits" : {
  3. "hits" : [
  4. {
  5. "_index" : "twitter",
  6. "_id" : "1",
  7. "_score" : 0.0,
  8. "_source" : {
  9. "user" : "双榆树-张三",
  10. "message" : "今儿天气不错啊,出去转转去",
  11. "uid" : 2,
  12. "age" : 20,
  13. "city" : "北京",
  14. "province" : "北京",
  15. "country" : "中国",
  16. "address" : "中国北京市海淀区",
  17. "location" : {
  18. "lat" : "39.970718",
  19. "lon" : "116.325747"
  20. }
  21. }
  22. },
  23. ...
  24. }

从上面的分数 _score 中,我们可以看出来,它是 0。在在有些情况下,可能并不是我们想要的。那么我改如何返回非 0 的分数呢?costant_score 查询包装 filter 查询 查询并返回每个匹配的文档,其相关性分数等于 boost 参数值。比如:

  1. GET twitter/_search?filter_path=**.hits
  2. {
  3. "query": {
  4. "constant_score": {
  5. "filter": {
  6. "match": {
  7. "city": "北京"
  8. }
  9. },
  10. "boost": 1.2
  11. }
  12. }
  13. }

上面返回的结果为:

  1. {
  2. "hits" : {
  3. "hits" : [
  4. {
  5. "_index" : "twitter",
  6. "_id" : "1",
  7. "_score" : 1.2,
  8. "_source" : {
  9. "user" : "双榆树-张三",
  10. "message" : "今儿天气不错啊,出去转转去",
  11. "uid" : 2,
  12. "age" : 20,
  13. "city" : "北京",
  14. "province" : "北京",
  15. "country" : "中国",
  16. "address" : "中国北京市海淀区",
  17. "location" : {
  18. "lat" : "39.970718",
  19. "lon" : "116.325747"
  20. }
  21. }
  22. },
  23. ...
  24. }

从上面的返回结果中,我们可以看出来,_score 的值为 1.2。boost 的默认值为 1.0。

多个索引操作

在上面我们引入了另外一个索引 twitter1。在实际的操作中,我们可以通过通配符,或者直接使用多个索引来进行搜索:

GET twitter*/_search

上面的操作是对所有的以 twitter 为开头的索引来进行搜索,显示的结果是在所有的 twitter 及 twitter1 中的文档:

GET /twitter,twitter1/_search

也可以做同样的事。在写上面的查询的时候,在两个索引之间不能加入空格,比如:

GET /twitter, twitter1/_search

上面的查询并不能返回你所想要的结果。

两位一种方法就是创建一个基于这两个索引的 index pattern (之前的版本)或 data view (在最新的 Elastic Stack 发布版中)。我们可以做如下的步骤:

 

这样我们就可以使用  twitter* 来同时搜索 twitter 及 twitter1 两个索引了。当然如果我们还有一个叫做 twitter2,twitter3 的索引,同样的 twitter* 将同时可以搜索它们。

Index boost

搜索多个索引时,你可以使用 indices_boost 参数来提升一个或多个指定索引的结果。 当来自某些索引的命中比来自其他索引的命中更重要时,这很有用。 

注意: 你不能针对 data stream 来使用 indices_boost

  1. GET /_search
  2. {
  3. "indices_boost": [
  4. { "twitter": 1.4 },
  5. { "twitter1": 1.3 }
  6. ]
  7. }

上面表明,twitter 的搜索结果的重要性比 twitter1 的来的更重要。它加权的值更多,那么在最终的结果中,它搜索的结果的分数会更靠前。关于多索引操作,可以更进一步阅读 “Elasticsearch:如何把两个索引连在一起”。

Profile API

Profile API 是调试工具。 它添加了有关执行的详细信息搜索请求中的每个组件。 它为用户提供有关搜索的每个步骤的洞察力
请求执行并可以帮助确定某些请求为何缓慢。

  1. GET twitter/_search
  2. {
  3. "profile": "true",
  4. "query": {
  5. "match": {
  6. "city": "北京"
  7. }
  8. }
  9. }

在上面,我们加上了 "profile":"true" 后,除了显示搜索的结果之外,还显示 profile 的信息:

  1. "profile" : {
  2. "shards" : [
  3. {
  4. "id" : "[ZXGhn-90SISq1lePV3c1sA][twitter][0]",
  5. "searches" : [
  6. {
  7. "query" : [
  8. {
  9. "type" : "BooleanQuery",
  10. "description" : "city:北 city:京",
  11. "time_in_nanos" : 1390064,
  12. "breakdown" : {
  13. "set_min_competitive_score_count" : 0,
  14. "match_count" : 5,
  15. "shallow_advance_count" : 0,
  16. "set_min_competitive_score" : 0,
  17. "next_doc" : 31728,
  18. "match" : 3337,
  19. "next_doc_count" : 5,
  20. "score_count" : 5,
  21. "compute_max_score_count" : 0,
  22. "compute_max_score" : 0,
  23. "advance" : 22347,
  24. "advance_count" : 1,
  25. "score" : 16639,
  26. "build_scorer_count" : 2,
  27. "create_weight" : 342219,
  28. "shallow_advance" : 0,
  29. "create_weight_count" : 1,
  30. "build_scorer" : 973775
  31. },
  32. "children" : [
  33. {
  34. "type" : "TermQuery",
  35. "description" : "city:北",
  36. "time_in_nanos" : 107949,
  37. "breakdown" : {
  38. "set_min_competitive_score_count" : 0,
  39. "match_count" : 0,
  40. "shallow_advance_count" : 3,
  41. "set_min_competitive_score" : 0,
  42. "next_doc" : 0,
  43. "match" : 0,
  44. "next_doc_count" : 0,
  45. "score_count" : 5,
  46. "compute_max_score_count" : 3,
  47. "compute_max_score" : 11465,
  48. "advance" : 3477,
  49. "advance_count" : 6,
  50. "score" : 5793,
  51. "build_scorer_count" : 3,
  52. "create_weight" : 34781,
  53. "shallow_advance" : 18176,
  54. "create_weight_count" : 1,
  55. "build_scorer" : 34236
  56. }
  57. },
  58. {
  59. "type" : "TermQuery",
  60. "description" : "city:京",
  61. "time_in_nanos" : 49929,
  62. "breakdown" : {
  63. "set_min_competitive_score_count" : 0,
  64. "match_count" : 0,
  65. "shallow_advance_count" : 3,
  66. "set_min_competitive_score" : 0,
  67. "next_doc" : 0,
  68. "match" : 0,
  69. "next_doc_count" : 0,
  70. "score_count" : 5,
  71. "compute_max_score_count" : 3,
  72. "compute_max_score" : 5162,
  73. "advance" : 15645,
  74. "advance_count" : 6,
  75. "score" : 3795,
  76. "build_scorer_count" : 3,
  77. "create_weight" : 13562,
  78. "shallow_advance" : 1087,
  79. "create_weight_count" : 1,
  80. "build_scorer" : 10657
  81. }
  82. }
  83. ]
  84. }
  85. ],
  86. "rewrite_time" : 17930,
  87. "collector" : [
  88. {
  89. "name" : "CancellableCollector",
  90. "reason" : "search_cancelled",
  91. "time_in_nanos" : 204082,
  92. "children" : [
  93. {
  94. "name" : "SimpleTopScoreDocCollector",
  95. "reason" : "search_top_hits",
  96. "time_in_nanos" : 23347
  97. }
  98. ]
  99. }
  100. ]
  101. }
  102. ],
  103. "aggregations" : [ ]
  104. }
  105. ]
  106. }

从上面我们可以看出来,这个搜索是搜索了“”及“”,而不是把北京作为一个整体来进行搜索的。我们可以在以后的文档中可以学习使用中文分词器来进行分词搜索。有兴趣的同学可以把上面的搜索修改为 city.keyword 来看看。如果你对分词感兴趣的话,请参阅我的文章 “Elastic:菜鸟上手指南” 中的分词器部分。

除了上面的通过命令来进行 profile 以外,我们也可以通过 Kibana 的 UI 对我们的搜索进行 profile:

在很多的时候这个可视化的工具更具直观性。

总结

在今天的文章里,我们介绍了如何使用 Elasticsearch 所提供的 DSL 来对我们的index进行搜索。Elasticsearch 为 index 提供了丰富的搜索方式。在这里就算是抛转引玉。在接下来的文章 “开始使用Elasticsearch (3)” 里我们来重点介绍一下聚合 aggregation 及 analyzer。

如果你想做更多的搜索的练习,可以阅读文章:

如果你想了解更多关于 Elastic Stack 相关的知识,请参阅我们的官方网站:Elastic Stack and Product Documentation | Elastic

你如果需要有更多的练习,请继续阅读 “Elasticsearch:有用的 Elasticsearch 查询示例”。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/934051
推荐阅读
相关标签
  

闽ICP备14008679号