[关闭]
@dungan 2019-12-24T10:37:06.000000Z 字数 9321 阅读 161

Elasticsearch 搜索

Elasticsearch


词项查询

词项查询 能够让你在结构化的数据中精确查找某个值,与全文查询不同,词项查询不分析搜索词。相反,词项查询会精确匹配字段中的字词。

term

term 用来从倒排索引中查找包含某个精确值的文档,非常适合用来查找类型是 keyword(string),数字,日期 的字段。

可以通过 boost 来修改关键词的权重,这样该关键词所在的文档就会优先排在结果集的最前面,boost 的默认值是1.0。

  1. {
  2. "query": {
  3. "bool": {
  4. "should": [
  5. {
  6. "term": {
  7. "status": {
  8. "value": "urgent",
  9. "boost": 2.0
  10. }
  11. }
  12. },
  13. {
  14. "term": {
  15. "status": "normal"
  16. }
  17. }
  18. ]
  19. }
  20. }
  21. }

terms

terms 用来查询文档中包含词项组中任一单词的文档。

  1. {
  2. "query": {
  3. "terms": {
  4. "user": [
  5. "张三",
  6. "李四",
  7. "王五"
  8. ]
  9. }
  10. }
  11. }

terms-set

terms-set 用来查询词项组中最少应包含几个单词这种场景,因此在构建索引时要设置一个字段用来控制最少包含数量。

  1. PUT /my-index
  2. {
  3. "mappings": {
  4. "_doc": {
  5. "properties": {
  6. "required_matches": {
  7. "type": "long"
  8. }
  9. }
  10. }
  11. }
  12. }
  13. # 字段值是数组格式
  14. PUT /my-index/_doc/1?refresh
  15. {
  16. "codes": ["ghi", "jkl"],
  17. "required_matches": 2
  18. }
  19. PUT /my-index/_doc/2?refresh
  20. {
  21. "codes": ["def", "ghi"],
  22. "required_matches": 2
  23. }
  24. # 查询
  25. {
  26. "query": {
  27. "terms_set": {
  28. "codes" : {
  29. "terms" : ["abc", "def", "ghi"],
  30. "minimum_should_match_field": "required_matches"
  31. }
  32. }
  33. }
  34. }
  35. # 可以看到 id = 2 的这条文档被查询出来,因为它的元素值满足 terms-set 条件
  36. {
  37. "took": 13,
  38. "timed_out": false,
  39. "_shards": {
  40. "total": 5,
  41. "successful": 5,
  42. "skipped" : 0,
  43. "failed": 0
  44. },
  45. "hits": {
  46. "total": 1,
  47. "max_score": 0.5753642,
  48. "hits": [
  49. {
  50. "_index": "my-index",
  51. "_type": "_doc",
  52. "_id": "2",
  53. "_score": 0.5753642,
  54. "_source": {
  55. "codes": ["def", "ghi"],
  56. "required_matches": 2
  57. }
  58. }
  59. ]
  60. }
  61. }

只获取特定的字段

通过 _source 可以设置哪些字段出现在结果集中,类似于 mysql 的 select field1,field2 ... from table

  1. {
  2. "query": {
  3. "term": {
  4. "title": "php"
  5. }
  6. },
  7. "_source": [
  8. "title",
  9. "author"
  10. ]
  11. }

范围查询

  1. {
  2. "query": {
  3. "range": {
  4. "postdate": {
  5. "gte": "2017-01-01",
  6. "lte": "2017-12-31",
  7. "format": "yyyy-MM-dd"
  8. }
  9. }
  10. }
  11. }

查询最近一个月发的帖子。

  1. {
  2. "query": {
  3. "filter": {
  4. "range": {
  5. "postDate": {
  6. "gte": "now-30d"
  7. }
  8. }
  9. }
  10. }
  11. }

exists

查询字段中不含 null 或 [] 的文档。

  1. # user 字段不含 null 或 [] 的文档会被查出来 。
  2. {
  3. "query": {
  4. "exists": {
  5. "field": "user"
  6. }
  7. }
  8. }

反之,如果想查询字段中包含 null 或 [] 的文档,可以这样。

  1. {
  2. "query": {
  3. "bool": {
  4. "must_not": {
  5. "exists": {
  6. "field": "user"
  7. }
  8. }
  9. }
  10. }
  11. }

通配符查询(Wildcard)

通配符 * 表示匹配零个或多个字符。

  1. # 查找 user_name 包含以ki开头和y结尾的文档, (kiy, kity,kimchy) 这些项都会被匹配到
  2. {
  3. "query": {
  4. "wildcard": {
  5. "user_name": "ki*y"
  6. }
  7. }
  8. }

正则查询(Regexp)

正则查询 让你可以使用比 通配符查询 更复杂的模式进行查询。

  1. {
  2. "query": {
  3. "regexp": {
  4. "user_name": "ki*y"
  5. }
  6. }
  7. }

更多关于 ES 正则表达式的语法见 这里

模糊查询 (Fuzzy)

因为要查询的输入词有可能有拼写错误,因此这种场景就可以使用模糊查询来修正错误词。

可以通过设置 fuzziness 来控制查询词和文档中词项的模糊字符数,fuzziness 的默认值为 AUTO,当查询词长度大于 5 个字符时,AUTO 的模糊值等同于指定值 2 。

  1. # 插入数据
  2. POST /my_index/my_type/_bulk
  3. { "index": { "_id": 1 }}
  4. { "text": "Surprise me!"}
  5. { "index": { "_id": 2 }}
  6. { "text": "That was surprising."}
  7. { "index": { "_id": 3 }}
  8. { "text": "I wasn't surprised."}
  9. # 为词 surprize 运行一个 fuzzy 查询
  10. GET /my_index/my_type/_search
  11. {
  12. "query": {
  13. "fuzzy": {
  14. "text": "surprize",
  15. "fuzziness": AUTO
  16. }
  17. }
  18. }

上面的例子中,surprise 比较 surprise 和 surprised,都在 2 以内,所以文档 1 和 3 匹配。如果当你将 fuzziness 设置为1后,你会发现只有文档 1 才被匹配出来。

Ids

从文档的元数据字段 _id 中去匹配给定的 id。

  1. {
  2. "query": {
  3. "ids": {
  4. "values": [
  5. "2",
  6. "3"
  7. ]
  8. }
  9. }
  10. }

分数过滤

  1. {
  2. "min_score":"3.0",
  3. "query": {
  4. "term": {
  5. "content": "测试"
  6. }
  7. }
  8. }

高亮关键字

  1. # 将 content 中的关键词 '测试' 高亮
  2. {
  3. "query": {
  4. "match": {
  5. "content": "小米 测试"
  6. }
  7. },
  8. "highlight": {
  9. "fields": {
  10. "content": {}
  11. }
  12. }
  13. }
  14. # 结果
  15. {
  16. ...
  17. "highlight": {
  18. "content": [
  19. "<em>测试</em><em>测试</em><em>测试</em><em>测试</em>"
  20. ]
  21. }
  22. ...
  23. }

es 默认使用 em 标签来高亮关键词,但也支持使用 pre_tagspost_tags 来自定义标签。

  1. {
  2. "query": {
  3. "match": {
  4. "content": "小米 测试"
  5. }
  6. },
  7. "highlight": {
  8. "fields": {
  9. "content": {
  10. "pre_tags" : ["<span font-color='red'>"],
  11. "post_tags": ["</span>"]
  12. }
  13. }
  14. }
  15. }
  16. # 结果
  17. {
  18. ...
  19. "highlight": {
  20. "content": [
  21. "<span font-color='red'>测试</span> hello"
  22. ]
  23. }
  24. ...
  25. }

默认情况下只有包含查询匹配的字段才会高亮显示,但是如果其他字段(例如 title)也有我们要查找的关键字,这时就要对 require_field_match 属性进行设置,这样就能对多个匹配的字段高亮。

require_field_match 默认值为 true,即只对单个字段高亮。

  1. {
  2. "query": {
  3. "match": {
  4. "content": "小米 测试"
  5. }
  6. },
  7. "highlight": {
  8. "require_field_match": false,
  9. "fields": {
  10. "content": {},
  11. "title": {}
  12. }
  13. }
  14. }
  15. # 结果
  16. {
  17. ...
  18. "highlight": {
  19. "title": [
  20. "<em>小米</em> 8 青春版"
  21. ],
  22. "content": [
  23. "<em>测试</em> hello"
  24. ]
  25. }
  26. ...
  27. }

复合查询

Bool 查询

must 等同于 AND,must_not 等同于 NOT,should 等同于 OR。

  1. # 书名包含 ElasticSearch 或者(OR) Solr,并且(AND)它的作者是 Clinton Gormley 不是(NOT)Radu Gheorge
  2. {
  3. "query": {
  4. "bool": {
  5. "must": {
  6. "bool": {
  7. "should": [
  8. {
  9. "match": {
  10. "title": "Elasticsearch"
  11. }
  12. },
  13. {
  14. "match": {
  15. "title": "Solr"
  16. }
  17. }
  18. ],
  19. "must": {
  20. "match": {
  21. "authors": "clinton gormely"
  22. }
  23. },
  24. "must_not": {
  25. "match": {
  26. "authors": "radu gheorge"
  27. }
  28. }
  29. }
  30. }
  31. }
  32. }
  33. }

假设我们要从文章表中查找 已审核,并且文章类型是 科技类,并且发布日期是 9月份 以后的,并且作者是( 张三李四王五) 这几人中的任一一人的满足条件的所有的文章。

在 mysql 中的我们查询条件如下

  1. select * from article where audit=1 and article_type=2 and publish_at >= 2019-09-01 and (author='张三' or author='李四' or author='王五');

在ES中的 Bool 查询构造如下

  1. {
  2. "query": {
  3. "bool": {
  4. "must": [
  5. {
  6. "term": {
  7. "audit": 1
  8. }
  9. },
  10. {
  11. "term": {
  12. "article_type": 2
  13. }
  14. },
  15. {
  16. "range": {
  17. "publish_at": {
  18. "gte": "2019-09-01",
  19. "format": "yyyy-MM-dd"
  20. }
  21. }
  22. },
  23. {
  24. "bool": {
  25. "should": [
  26. {
  27. "match_phrase": {
  28. "author": "张三"
  29. }
  30. },
  31. {
  32. "match_phrase": {
  33. "author": "李四"
  34. }
  35. },
  36. {
  37. "match_phrase": {
  38. "author": "王五"
  39. }
  40. }
  41. ]
  42. }
  43. }
  44. ]
  45. }
  46. }
  47. }

可以看到 Bool 查询的本质就是让我们组装出复杂的查询条件。

Bool + Filter 查询

过滤只在第一次运行,以减少所需的查询面积,并且,在第一次使用后过滤会被缓存,大大提高了性能。

  1. {
  2. "query": {
  3. "filter": {
  4. "query": {
  5. "multi_match": {
  6. "query": "elasticsearch",
  7. "fields": [
  8. "title",
  9. "summary"
  10. ]
  11. }
  12. },
  13. "filter": {
  14. "bool": {
  15. "must": {
  16. "range": {
  17. "num_reviews": {
  18. "gte": 20
  19. }
  20. }
  21. },
  22. "must_not": {
  23. "range": {
  24. "publish_date": {
  25. "lte": "2014-12-31"
  26. }
  27. }
  28. },
  29. "should": {
  30. "term": {
  31. "publisher": "oreilly"
  32. }
  33. }
  34. }
  35. }
  36. }
  37. }
  38. }

多重过滤:filter 嵌套 bool 查询可以实现多重过滤(可以看做是括号中的子查询)。

全文查询

全文检索

搜索标题中含有 java 或 elasticsearch 的 blog。

  1. {
  2. "query": {
  3. "match": {
  4. "title": "java elasticsearch"
  5. }
  6. }
  7. }
  8. # 等同于
  9. {
  10. "bool": {
  11. "should": [
  12. {
  13. "term": {
  14. "title": "java"
  15. }
  16. },
  17. {
  18. "term": {
  19. "title": "elasticsearch"
  20. }
  21. }
  22. ]
  23. }
  24. }

控制搜索精度
如果想搜索标题中同时含有 java 和 elasticsearch 两个关键字的blog,可以通过 operator 实现。

注意: 这种属于同时满足多个关键字的查询,这点要和短语查询有所区分。

  1. {
  2. "query": {
  3. "match": {
  4. "title": {
  5. "query": "java elasticsearch",
  6. "operator": "and"
  7. }
  8. }
  9. }
  10. }
  11. # 等同于
  12. {
  13. "bool": {
  14. "must": [
  15. {
  16. "term": {
  17. "title": "java"
  18. }
  19. },
  20. {
  21. "term": {
  22. "title": "elasticsearch"
  23. }
  24. }
  25. ]
  26. }
  27. }

如果要搜索至少包含三个关键字的blog,可以通过设置 minimum_should_match 实现。

  1. {
  2. "query": {
  3. "match": {
  4. "title": {
  5. "query": "java elasticsearch spark hadoop",
  6. "minimum_should_match": 3
  7. }
  8. }
  9. }
  10. }
  11. # 等同于
  12. {
  13. "bool": {
  14. "should": [
  15. {
  16. "term": {
  17. "title": "java"
  18. }
  19. },
  20. {
  21. "term": {
  22. "title": "elasticsearch"
  23. }
  24. },
  25. {
  26. "term": {
  27. "title": "spark"
  28. }
  29. },
  30. {
  31. "term": {
  32. "title": "hadoop"
  33. }
  34. }
  35. ],
  36. "minimum_should_match": 3
  37. }
  38. }

基于boost的细粒度搜索条件权重控制

假设我们要搜索标题中包含 java 的帖子,同时如果标题中包含 hadoop 和 elasticsearch 就优先搜索出来,同时,如果一个帖子包含 java hadoop,一个帖子包含java elasticsearch,包含 hadoop 的帖子要比 elasticsearch 优先搜索出来。

  1. {
  2. "query": {
  3. "bool": {
  4. "must": [
  5. {
  6. "match": {
  7. "title": "java"
  8. }
  9. }
  10. ],
  11. "should": [
  12. {
  13. "match": {
  14. "title": {
  15. "query": "hadoop",
  16. "boost": 5
  17. }
  18. }
  19. },
  20. {
  21. "match": {
  22. "title": {
  23. "query": "elasticsearch",
  24. "boost": 3
  25. }
  26. }
  27. },
  28. {
  29. "match": {
  30. "title": {
  31. "query": "spark",
  32. "boost": 1
  33. }
  34. }
  35. }
  36. ]
  37. }
  38. }
  39. }

短语查询(Match Phrase)

默认情况下,查询项之间必须紧密相连,但可以设置 slop 值来指定查询项之间可以 间隔多少个词,这样即使短语之间有其他词,也可以被查找出来。

加上slop的 match-phrase就是近似匹配了(proximity-match),近似匹配可以搜索到很多结果,但是距离越近的会优先返回,也就是相关度分数就会越高。

  1. {
  2. "query": {
  3. "match_phrase": {
  4. "title": "hello world",
  5. "slop":1
  6. }
  7. }
  8. }

前缀查询(Prefix)

前缀查询能够进行即时搜索类型的匹配,或者说提供一个查询时的初级自动补全功能,无需以任何方式准备你的数据。和 match_phrase 查询类似,它也支持 slop 参数。

  1. # 查询姓名以 tc 开头的用户
  2. {
  3. "query": {
  4. "match_phrase_prefix": {
  5. "user_name": "tc"
  6. }
  7. }
  8. }

前缀查询也支持给关键字设置权重(boost)。

  1. {
  2. "query": {
  3. "match_phrase_prefix": {
  4. "user_name": {
  5. "value": "tcl",
  6. "boost": 2
  7. }
  8. }
  9. }
  10. }

多字段查找(Multi Match)

multi_match 是 match 的作为在多个字段运行相同操作的一个速记法。fields 指定从哪些字段查找。

在 fields 中我们可以修改字段的权重(boost),从而让字段所在的行出现在结果集的前面。

  1. # 这里我们将 title 字段的分数提高三倍,这样 title 字段所在的结果行会出现在前面
  2. {
  3. "query": {
  4. "multi_match": {
  5. "query": "小米 测试",
  6. "fields": ["title^3", "content"]
  7. }
  8. }
  9. }

multi_match 默认以 best_fields 类型执行,此外还支持 most_fieldsphrase(多字段短语查找) 等类型查询。

best_fields 类型调优

如果一个字段中同时包含了我们要查找的多个关键词,我们希望这个字段所在的文档(我们叫它最佳匹配)出现在结果集的最前面,那么这时候使用 dis_max 就能实现这种效果。

假设我们有一个让用户搜索博客文章的网站。其中有两个文档如下:

  1. PUT /test_index/_create/1
  2. {
  3. "title": "Quick brown rabbits",
  4. "body": "Brown rabbits are commonly seen."
  5. }
  6. PUT /test_index/_create/2
  7. {
  8. "title": "Keeping pets healthy",
  9. "body": "My quick brown fox eats rabbits on a regular basis."
  10. }

进行查询:

  1. GET /test_index/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "should": [
  6. { "match": { "title": "Brown fox" }},
  7. { "match": { "body": "Brown fox" }}
  8. ]
  9. }
  10. }
  11. }
  12. # 输出
  13. {
  14. ...
  15. "hits": [
  16. {
  17. "_index": "test_index",
  18. "_type": "_doc",
  19. "_id": "2",
  20. "_score": 0.77041256,
  21. "_source": {
  22. "title": "Keeping pets healthy",
  23. "body": "My quick brown fox eats rabbits on a regular basis."
  24. }
  25. },
  26. {
  27. "_index": "test_index",
  28. "_type": "_doc",
  29. "_id": "1",
  30. "_score": 0.6931472,
  31. "_source": {
  32. "title": "Quick brown rabbits",
  33. "body": "Brown rabbits are commonly seen."
  34. }
  35. }
  36. ]
  37. ...
  38. }

由于 ES 的文档打分机制,导致文档1出现在结果集的最前面,但文档一的任一个字段都没有同时包含 Brown fox 这两个关键词,相反,文档2的 title 字段却同时包含了这两个关键词,因此对于我们来说 文档2才是最佳匹配

现在换成 dis_max 查询,可以看到文档2出现在了前面:

  1. {
  2. "query": {
  3. "dis_max": {
  4. "queries": [
  5. {
  6. "match": {
  7. "title": "Brown fox"
  8. }
  9. },
  10. {
  11. "match": {
  12. "body": "Brown fox"
  13. }
  14. }
  15. ]
  16. }
  17. }
  18. }
  19. # 输出
  20. {
  21. ...
  22. "hits": [
  23. {
  24. "_index": "test_index",
  25. "_type": "_doc",
  26. "_id": "2",
  27. "_score": 0.77041256,
  28. "_source": {
  29. "title": "Keeping pets healthy",
  30. "body": "My quick brown fox eats rabbits on a regular basis."
  31. }
  32. },
  33. {
  34. "_index": "test_index",
  35. "_type": "_doc",
  36. "_id": "1",
  37. "_score": 0.6931472,
  38. "_source": {
  39. "title": "Quick brown rabbits",
  40. "body": "Brown rabbits are commonly seen."
  41. }
  42. }
  43. ]
  44. ...
  45. }

most_fields 类型
most_fields 会使得匹配的字段数量最多的那个文档出现在最前面,这点和前面的 best_fields 不一样。

  1. # 创建数据
  2. PUT /test_index/_create/1
  3. {
  4. "street": "5 Poland Street",
  5. "city": "Poland",
  6. "country": "United W1V",
  7. "postcode": "W1V 3DG"
  8. }
  9. PUT /test_index/_create/2
  10. {
  11. "street": "5 Poland Street W1V",
  12. "city": "London",
  13. "country": "United Kingdom",
  14. "postcode": "3DG"
  15. }
  16. # 查询
  17. {
  18. "query": {
  19. "multi_match": {
  20. "query": "Poland Street W1V",
  21. "type": "most_fields",
  22. "fields": ["street", "city", "country", "postcode"]
  23. }
  24. }
  25. }
  26. # 输出
  27. # 如果用 best_fields, 那么 doc2 会在 doc1 的前面.
  28. {
  29. ...
  30. "hits": [
  31. {
  32. "_index": "test_index",
  33. "_type": "_doc",
  34. "_id": "1",
  35. "_score": 2.3835402,
  36. "_source": {
  37. "street": "5 Poland Street",
  38. "city": "Poland",
  39. "country": "United W1V",
  40. "postcode": "W1V 3DG"
  41. }
  42. },
  43. {
  44. "_index": "test_index",
  45. "_type": "_doc",
  46. "_id": "2",
  47. "_score": 0.99938464,
  48. "_source": {
  49. "street": "5 Poland Street W1V",
  50. "city": "London",
  51. "country": "United Kingdom",
  52. "postcode": "3DG"
  53. }
  54. }
  55. ]
  56. ...
  57. }

most_fields 的问题

  1. 它被设计用来找到匹配任意单词的多数字段,而不是找到跨越所有字段的最匹配的单词。
  2. 它不能使用operator或者minimum_should_match参数来减少低相关度结果带来的长尾效应。

cross_fields 类型
cross_fields 可以将所有的字段联合成一个大的字段,然后在这个大字段中搜索每个词条,通过混合字段的倒排文档频度来解决词条频度问题,从而完美解决了 most_fields 的问题。

  1. {
  2. "query": {
  3. "multi_match": {
  4. "query": "Poland Street W1V",
  5. "type": "cross_fields",
  6. "operator": "and",
  7. "fields": ["street", "city", "country", "postcode"]
  8. }
  9. }
  10. }

phrase 类型
phrase 类型能够让我们在多个字段间进行短语查找。

  1. {
  2. "query": {
  3. "multi_match" : {
  4. "query": "hello world",
  5. "fields": ["title", "content"],
  6. "type": "phrase",
  7. "slop": 1
  8. }
  9. }
  10. }

参考

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注