Term查询

Term Query / Range Query / Exists Query / Prefix Query / Wildcard Query
特点:

  • 对输入不做分词, 因此应检索keyword
  • 会为每个包含该词项的文档进行相关度算分
  • 可用constant score将查询转换成一个filtering, 避免算分, 并利用缓存, 提高性能 ```json

    term查询

    GET movies/_search { “query”: { “term”: {
    1. "title.keyword": {
    2. "value": "2012"
    3. }
    } } }

利用constant_score转为filter

GET movies/_search { “query”: { “constant_score”: { “filter”: { “term”: { “title.keyword”: “2012” } } } } }

  1. <a name="cI3dY"></a>
  2. ## 全文查询
  3. Match Query / Match Phrase Query / Query String Query <br />特点:
  4. - 索引和搜索时都会分词, 查询字符串会先分词生成一个共查询的词项列表
  5. - 会算分
  6. ```json
  7. # 全文查询
  8. POST movies/_search
  9. {
  10. "query": {
  11. "match": {
  12. "title": {
  13. "query": "Matrix reloaded",
  14. "operator": "and"
  15. }
  16. }
  17. }
  18. }
  19. POST movies/_search
  20. {
  21. "query": {
  22. "match": {
  23. "title": {
  24. "query": "Matrix reloaded",
  25. "minimum_should_match": 2
  26. }
  27. }
  28. }
  29. }
  30. POST movies/_search
  31. {
  32. "query": {
  33. "match_phrase": {
  34. "title": {
  35. "query": "Matrix reloaded",
  36. "slop": 1
  37. }
  38. }
  39. }
  40. }

结构化搜索

结构化搜索是指对结构化数据的搜索, 其中日期, 布尔类型和数字都是结构化的, 文本也可以是结构化的.

  1. # 对布尔类型, 数字类型直接term查询, 可以用constant_scorefilter, 避免算分
  2. # term查询的字段是数组类型时, 是包含关系, 而不是等值
  3. # 如果要求等值, 则需在索引中加一个计数字段, 然后通过bool查询叠加条件实现
  4. #数字类型 terms
  5. POST products/_search
  6. {
  7. "query": {
  8. "constant_score": {
  9. "filter": {
  10. "terms": {
  11. "price": [
  12. "20",
  13. "30"
  14. ]
  15. }
  16. }
  17. }
  18. }
  19. }
  20. #字符类型 terms
  21. POST products/_search
  22. {
  23. "query": {
  24. "constant_score": {
  25. "filter": {
  26. "terms": {
  27. "productID.keyword": [
  28. "QQPX-R-3956-#aD8",
  29. "JODL-X-1937-#pV7"
  30. ]
  31. }
  32. }
  33. }
  34. }
  35. }
  36. #数字 Range 查询
  37. GET products/_search
  38. {
  39. "query" : {
  40. "constant_score" : {
  41. "filter" : {
  42. "range" : {
  43. "price" : {
  44. "gte" : 20,
  45. "lte" : 30
  46. }
  47. }
  48. }
  49. }
  50. }
  51. }
  52. # 日期 range
  53. POST products/_search
  54. {
  55. "query" : {
  56. "constant_score" : {
  57. "filter" : {
  58. "range" : {
  59. "date" : {
  60. "gte" : "now-1y"
  61. }
  62. }
  63. }
  64. }
  65. }
  66. }
  67. #exists查询
  68. POST products/_search
  69. {
  70. "query": {
  71. "constant_score": {
  72. "filter": {
  73. "exists": {
  74. "field": "date"
  75. }
  76. }
  77. }
  78. }
  79. }
  80. # 查询不存在date字段的文档
  81. POST products/_search
  82. {
  83. "query": {
  84. "constant_score": {
  85. "filter": {
  86. "bool": {
  87. "must_not": {
  88. "exists": {
  89. "field": "date"
  90. }
  91. }
  92. }
  93. }
  94. }
  95. }
  96. }

相关性算分

搜索的相关性算分, 描述了一个文档和查询语句匹配的程度. 打分的本质是排序, 需要把最符合用户需求的文档排在前面, es5后默认的相关性算分采用BM25.

  • 词频TF(Term Frequency): 检索词在一篇文档中出现的频率=检索词出现次数/文档总字数

    • 度量一条查询与结果文档相关性的简单方法, 是将搜索中每一个词的TF进行相加
    • Stop Word除外, 比如”的”, “地”, 不贡献相关度, 不应考虑它们的TF
  • 逆文档频率IDF, 词在所有文档中出现的越少, 值越大

    • DF: 检索词在所有文档中出现的频率
    • IDF: Inverse Document Frequency=log(全部文档数/检索词出现过的文档总数)
  • TF-IDF: 本质上将TF求和变成了加权求和

比如搜索”区块链的应用”, TF-IDF=TF(区块链)IDF(区块链)+TF(的)IDF(的)+TF(应用)*IDF(应用)

image.png
image.png

  1. # 可以打开explain了解算分
  2. POST movies/_search
  3. {
  4. "explain": true,
  5. "query": {
  6. "match": {
  7. "title": {
  8. "query": "Matrix reloaded",
  9. "operator": "and"
  10. }
  11. }
  12. }
  13. }
  14. # 通过Boosting来控制相关度
  15. # boost>1, 提升相关性
  16. # 0<boost<1, 降低相关性
  17. # boost<0, 贡献负分
  18. # 1. 设置索引以及mapping时可以设置字段的boost
  19. # 2. 查询时可以设置, 如下
  20. POST testscore/_search
  21. {
  22. "query": {
  23. "boosting" : {
  24. "positive" : {
  25. "term" : {
  26. "content" : "elasticsearch"
  27. }
  28. },
  29. "negative" : {
  30. "term" : {
  31. "content" : "like"
  32. }
  33. },
  34. "negative_boost" : 0.2
  35. }
  36. }
  37. }

Query&Filtering与多字符串多字段查询

  • Query: 查询, 有算分
  • Filter: 过滤, 不需要算分, 可以利用缓存, 提升性能

bool查询, 一个或多个查询子句组合

  • 子查询可以任意顺序
  • 可以嵌套多个
  • 如果没有must条件, 则should必须至少满足一个

image.png

  1. # bool查询
  2. POST /products/_search
  3. {
  4. "query": {
  5. "bool" : {
  6. "must" : {
  7. "term" : { "price" : "30" }
  8. },
  9. "filter": {
  10. "term" : { "avaliable" : "true" }
  11. },
  12. "must_not" : {
  13. "range" : {
  14. "price" : { "lte" : 10 }
  15. }
  16. },
  17. "should" : [
  18. { "term" : { "productID.keyword" : "JODL-X-1937-#pV7" } },
  19. { "term" : { "productID.keyword" : "XHDK-A-1293-#fJ3" } }
  20. ],
  21. "minimum_should_match" :1
  22. }
  23. }
  24. }
  25. #嵌套,实现了 should not 逻辑
  26. POST /products/_search
  27. {
  28. "query": {
  29. "bool": {
  30. "must": {
  31. "term": {
  32. "price": "30"
  33. }
  34. },
  35. "should": [
  36. {
  37. "bool": {
  38. "must_not": {
  39. "term": {
  40. "avaliable": "false"
  41. }
  42. }
  43. }
  44. }
  45. ],
  46. "minimum_should_match": 1
  47. }
  48. }
  49. }
  50. # bool查询嵌套的层级会影响算分
  51. POST /animals/_search
  52. {
  53. "query": {
  54. "bool": {
  55. "should": [
  56. { "term": { "text": "brown" }},
  57. { "term": { "text": "red" }},
  58. { "term": { "text": "quick" }},
  59. { "term": { "text": "dog" }}
  60. ]
  61. }
  62. }
  63. }
  64. POST /animals/_search
  65. {
  66. "query": {
  67. "bool": {
  68. "should": [
  69. { "term": { "text": "quick" }},
  70. { "term": { "text": "dog" }},
  71. {
  72. "bool":{
  73. "should":[
  74. { "term": { "text": "brown" }},
  75. { "term": { "text": "brown" }},
  76. ]
  77. }
  78. }
  79. ]
  80. }
  81. }
  82. }
  83. # Boosting Query
  84. POST news/_search
  85. {
  86. "query": {
  87. "boosting": {
  88. "positive": {
  89. "match": {
  90. "content": "apple"
  91. }
  92. },
  93. "negative": {
  94. "match": {
  95. "content": "pie"
  96. }
  97. },
  98. "negative_boost": 0.5
  99. }
  100. }
  101. }

单字符串多字段查询, Dis Max Query

总结: 这种场景还是要用dis_max+tie_breaker合适

  1. # 文档1
  2. PUT /blogs/_doc/1
  3. {
  4. "title": "Quick brown rabbits",
  5. "body": "Brown rabbits are commonly seen."
  6. }
  7. # 文档2
  8. PUT /blogs/_doc/2
  9. {
  10. "title": "Keeping pets healthy",
  11. "body": "My quick brown fox eats rabbits on a regular basis."
  12. }
  13. # 上面两篇文档
  14. # 单字符串多字段查询如下示例, 通过bool查询来实现
  15. # bool查询的条件为should时, 此时会简单的将所有匹配的得分相加,
  16. # 由于brown在第一篇文档中出现两次, 因此文档1优先于文档2, 但语义上文档2brown fox才更接近
  17. POST /blogs/_search
  18. {
  19. "query": {
  20. "bool": {
  21. "should": [
  22. { "match": { "title": "Brown fox" }},
  23. { "match": { "body": "Brown fox" }}
  24. ]
  25. }
  26. }
  27. }
  28. # 此时, 使用dis_max查询即可达到效果, dis_max会简单返回匹配更高者的分数, 得到文档2优先于1
  29. POST blogs/_search
  30. {
  31. "query": {
  32. "dis_max": {
  33. "queries": [
  34. { "match": { "title": "Brown fox" }},
  35. { "match": { "body": "Brown fox" }}
  36. ]
  37. }
  38. }
  39. }
  40. # 另一种场景, 当查询的字段是Quick pets时, dis_max算法会使得2个文档评分一样
  41. POST blogs/_search
  42. {
  43. "query": {
  44. "dis_max": {
  45. "queries": [
  46. { "match": { "title": "Quick pets" }},
  47. { "match": { "body": "Quick pets" }}
  48. ]
  49. }
  50. }
  51. }
  52. # 这时, 可以引入tie_breaker, tie_breker决定了剩余匹配的贡献度,
  53. # 引入后匹配了2个单词的文档2评分就会高于文档1
  54. POST blogs/_search
  55. {
  56. "query": {
  57. "dis_max": {
  58. "queries": [
  59. { "match": { "title": "Quick pets" }},
  60. { "match": { "body": "Quick pets" }}
  61. ],
  62. "tie_breaker": 0.2
  63. }
  64. }
  65. }

单字符串多字段查询: Multi Match

三种场景:

  1. 最佳字段(Best Fields)

当字段之间互相竞争, 又相互关联, 评分来自最匹配字段

补充: 效果就相当于上面的dis_max query

  1. POST blogs/_search
  2. {
  3. "query": {
  4. "dis_max": {
  5. "queries": [
  6. { "match": { "title": "Quick pets" }},
  7. { "match": { "body": "Quick pets" }}
  8. ],
  9. "tie_breaker": 0.2
  10. }
  11. }
  12. }
  13. POST blogs/_search
  14. {
  15. "query": {
  16. "multi_match": {
  17. "type": "best_fields",
  18. "query": "Quick pets",
  19. "fields": ["title","body"],
  20. "tie_breaker": 0.2,
  21. "minimum_should_match": "20%"
  22. }
  23. }
  24. }
  1. 多数字段(Most Fields)

处理英文内容时, 常见手段是, 在主字段(English Analyzer), 抽取词干, 加入同义词, 以匹配更多的文档, 相同的文本, 加入子字段(Standard Analyzer), 以提供更加精确的匹配, 其他字段作为匹配文档提高相关度的信号, 匹配字段越多则越好

  1. # standard分词器不会对单词进行处理
  2. GET /_analyze
  3. {
  4. "analyzer":"standard",
  5. "text":"barking dogs"
  6. }
  7. ------------------------
  8. {
  9. "tokens" : [
  10. {
  11. "token" : "barking",
  12. "start_offset" : 0,
  13. "end_offset" : 7,
  14. "type" : "<ALPHANUM>",
  15. "position" : 0
  16. },
  17. {
  18. "token" : "dogs",
  19. "start_offset" : 8,
  20. "end_offset" : 12,
  21. "type" : "<ALPHANUM>",
  22. "position" : 1
  23. }
  24. ]
  25. }
  26. # english分词器会去除单词的时态, 单复数等特征
  27. GET /_analyze
  28. {
  29. "analyzer":"english",
  30. "text":"barking dogs"
  31. }
  32. ------------------------
  33. {
  34. "tokens" : [
  35. {
  36. "token" : "bark",
  37. "start_offset" : 0,
  38. "end_offset" : 7,
  39. "type" : "<ALPHANUM>",
  40. "position" : 0
  41. },
  42. {
  43. "token" : "dog",
  44. "start_offset" : 8,
  45. "end_offset" : 12,
  46. "type" : "<ALPHANUM>",
  47. "position" : 1
  48. }
  49. ]
  50. }

案例

  1. # 现有titles这样的索引, title字段设定了english分词器
  2. PUT /titles
  3. {
  4. "mappings": {
  5. "properties": {
  6. "title": {
  7. "type": "text",
  8. "analyzer": "english"
  9. }
  10. }
  11. }
  12. }
  13. POST titles/_bulk
  14. { "index": { "_id": 1 }}
  15. { "title": "My dog barks" }
  16. { "index": { "_id": 2 }}
  17. { "title": "I see a lot of barking dogs on the road " }
  18. # 写入上面两篇文档后, 有如下搜索, 由于两篇文档都有俩单词匹配, 且第一篇文档更短,
  19. # 因此文档1优先于文档2, 但是明显文档2才是我们想要的
  20. GET titles/_search
  21. {
  22. "query": {
  23. "match": {
  24. "title": "barking dogs"
  25. }
  26. }
  27. }
  28. # 解决方式, 修改mapping, title字段, 添加一个使用standard分词器的子字段std
  29. DELETE /titles
  30. PUT /titles
  31. {
  32. "settings": { "number_of_shards": 1 },
  33. "mappings": {
  34. "my_type": {
  35. "properties": {
  36. "title": {
  37. "type": "string",
  38. "analyzer": "english",
  39. "fields": {
  40. "std": {
  41. "type": "string",
  42. "analyzer": "standard"
  43. }
  44. }
  45. }
  46. }
  47. }
  48. }
  49. }
  50. # 再使用most_fieldsmulti_match查询, 此时文档2title以及子字段都有匹配, 因此优先于文档1
  51. GET /titles/_search
  52. {
  53. "query": {
  54. "multi_match": {
  55. "query": "barking dogs",
  56. "type": "most_fields",
  57. "fields": [ "title", "title.std" ]
  58. }
  59. }
  60. }

专业解释如下:
image.png

  1. 混合字段(Cross Field)

对于某些实体, 例如人名, 地址, 图书信息, 需要在多个字段中确定信息, 单个字段只能作为整体的一部分, 希望在任何这些列出的字段中找到尽可能多的词

  1. # Cross Fieldmulti_match常用于跨字段查询, 比如地区字段如下
  2. # 这种情形的另一个解决方式是使用copy_to, 新增一个字段, 但是会占用更多索引空间
  3. # 此外使用Cross Field还可以设定每个字段的权重
  4. PUT address/_doc/1
  5. {
  6. "street": "5 Poland Street",
  7. "city": "London",
  8. "country" : "UK",
  9. "postcode": "W1V 3DG"
  10. }
  11. POST address/_search
  12. {
  13. "query": {"multi_match": {
  14. "query": "Poland Street W1V",
  15. "type": "cross_fields",
  16. "operator": "and",
  17. "fields": ["street","city","country","postcode"]
  18. }}
  19. }