Elasticsearch使用:Search 概括

2021-10-21 09:47:19 浏览数 (1)

简介

在 Elasticsearch 中的搜索中,有两类搜索:queries和aggregations。

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

代码语言:javascript复制
GET blogs/_search
{
  "query": {
    "match": {
      "title": "community"
    }
  },
  "aggregations": {
    "top_authors": {
      "terms": {
        "field": "author"
      }
    }
  }
}

结构化检索

结构化搜索(Structured search) 是指有关探询那些具有内在结构数据的过程。比如日期、时间和数字都是结构化的:它们有精确的格式,我们可以对这些格式进行逻辑操作。比较常见的操作包括比较数字或时间的范围,或判定两个值的大小。

在结构化查询中,我们得到的结果 总是 非是即否,要么存于集合之中,要么存在集合之外。结构化查询不关心文件的相关度或评分;它简单的对文档包括或排除处理。

exists

代码语言:javascript复制
GET twitter/_search
{
  "query": {
    "exists": {
      "field": "city"
    }
  }
}

ids

代码语言:javascript复制
POST sphinx-doctor/_mget
{
   "ids" : [ "9", "10" ]
}

位置查询

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

代码语言:javascript复制
GET twitter/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "address": "北京"
          }
        }
      ]
    }
  },
  "post_filter": {
    "geo_distance": {
      "distance": "3km",
      "location": {
        "lat": 39.920086,
        "lon": 116.454182
      }
    }
  }
}

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

代码语言:javascript复制
GET twitter/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "address": "北京"
          }
        }
      ]
    }
  },
  "post_filter": {
    "geo_distance": {
      "distance": "5km",
      "location": {
        "lat": 39.920086,
        "lon": 116.454182
      }
    }
  },
  "sort": [
    {
      "_geo_distance": {
        "location": "39.920086,116.454182",
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}

GET twitter/_search
{
  "query": {
    "bool": {
      "must": {
        "match": {
          "address": "北京"
        }
      },
      "filter": {
        "geo_distance": {
          "distance": "5km",
          "location": {
            "lat": 39.920086,
            "lon": 116.454182
          }
        }
      }
    }
  },
  "sort": [
    {
      "_geo_distance": {
        "location": "39.920086,116.454182",
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}

全文检索

全文搜索(full-text search) :怎样在全文字段中搜索到最相关的文档。

fuzzy

代码语言:javascript复制
{
  "query": {
    "fuzzy": {
      "hospitalcity.keyword": {
        "value": "北京"
      }
    }
  }
}

term

代码语言:javascript复制
{
  "query": {
    "fuzzy": {
      "hospitalcity.keyword": {
        "value": "北京"
      }
    }
  }
}

match

代码语言:javascript复制
{
    "query": {
        "match": {
            "title": "QUICK!"
        }
    }
}

query_string

代码语言:javascript复制
{
  "query": {
    "query_string": {
      "default_field": "hospitalcity",
      "query": "北京"
    }
  }
}

多字段搜索

通常我们需要用相同或不同的字符串查询一个或多个字段,也就是说,需要对多个查询语句以及它们相关度评分进行合理的合并。

最佳字段

代码语言:javascript复制
{
  "query": {
    "dis_max": {
      "boost": 1.2,
      "queries": [
        {
          "match": {
            "FIELD": "TEXT"
          }
        },
        {
          "match": {
            "FIELD": "TEXT"
          }
        }
      ]
    }
  }
}


{
  "query": {
    "multi_match": {
      "query": "北京",
      "fields": [
        "hospitalprovince^3",
        "hospitalcity"
      ],
      "type": "best_fields",
      "tie_breaker": 0.3,
      "minimum_should_match": "80%"
    }
  }
}

多数字段

代码语言:javascript复制
{
    "query": {
        "bool": {
            "should": [
                { "match": { "title": "Brown fox" }},
                { "match": { "body":  "Brown fox" }}
            ]
        }
    }
}


{
  "query": {
    "dis_max": {
      "tie_breaker": 0.7,
      "boost": 1.2,
      "queries": [
        {
          "match": {
            "FIELD": "TEXT"
          }
        },
        {
          "match": {
            "FIELD": "TEXT"
          }
        }
      ]
    }
  }
}


{
   "query": {
        "multi_match": {
            "query":  "jumping rabbits",
            "type":   "most_fields",   #我们希望将所有匹配字段的评分合并起来,所以使用 most_fields 类型。这让 multi_match 查询用 bool 查询将两个字段语句包在里面,而不是使用 dis_max 查询。 
            "fields": [ "title", "title.std" ]
        }
    }
}

跨字段

代码语言:javascript复制
{
    "query": {
        "multi_match": {
            "query":       "peter smith",
            "type":        "cross_fields",
            "operator":    "and",   #所有词都是必须的。 
            "fields":      [ "first_name", "last_name" ]
        }
    }
}

词中心式:
 (first_name:peter last_name:peter)
 (first_name:smith last_name:smith)

字段中心式:
( first_name:peter  first_name:smith)
( last_name:peter   last_name:smith)

换句话说,它会同时在 first_namelast_name 两个字段中查找 smith 的 IDF ,然后用两者的最小值作为两个字段的 IDF 。

采用 cross_fields 查询与 自定义 _all 字段 相比,其中一个优势就是它可以在搜索时为单个字段提升权重。

短语检索

当你想找到彼此邻近搜索词的查询方法时,就会想到 match_phrase 查询 。

代码语言:javascript复制
Es低版本支持:
{
  "query": {
    "match": {
      "title": {
        "query": "quick brown fox",
        "type": "phrase"
      }
    }
  }
}


{
  "query": {
    "bool": {
      "must": {
        "match": {   #must 子句从结果集中包含或者排除文档
          "title": {
            "query":                "quick brown fox",
            "minimum_should_match": "30%"
          }
        }
      },
      "should": {
        "match_phrase": {   #should 子句增加了匹配到文档的相关度评分。
          "title": {
            "query": "quick brown fox",
            "slop":  50
          }
        }
      }
    }
  }
}


重新评分:
{
    "query": {
        "match": {   #match 查询决定哪些文档将包含在最终结果集中,并通过 TF/IDF 排序。 
            "title": {
                "query":                "quick brown fox",
                "minimum_should_match": "30%"
            }
        }
    },
    "rescore": {
        "window_size": 50,   #window_size 是每一分片进行重新评分的顶部文档数量。
        "query": {      #目前唯一支持的重新打分算法就是另一个查询,但是以后会有计划增加更多的算法。      
            "rescore_query": {
                "match_phrase": {
                    "title": {
                        "query": "quick brown fox",
                        "slop":  50
                    }
                }
            }
        }
    }
}

部分检索

部分匹配 允许用户指定查找词的一部分并找出所有包含这部分片段的词。prefix 、 wildcard 和 regexp 查询是基于词操作的,如果用它们来查询 analyzed 字段,它们会检查字段里面的每个词,而不是将字段作为整体来处理。

prefix 前缀

prefix 查询是一个词级别的底层的查询,它不会在搜索之前分析查询字符串,它假定传入前缀就正是要查找的前缀。

默认状态下, prefix 查询不做相关度评分计算,它只是将所有匹配的文档返回,并为每条结果赋予评分值 1 。它的行为更像是过滤器而不是查询。 prefix 查询和 prefix 过滤器这两者实际的区别就是过滤器是可以被缓存的,而查询不行。

代码语言:javascript复制
{
    "query": {
        "prefix": {
            "postcode": "W1"
        }
    }
}

{
    "match_phrase_prefix" : {
        "brand" : {
            "query": "walker johnnie bl", 
            "max_expansions": 50,
            "slop":  10  #尽管词语的顺序不正确,查询仍然能匹配,因为我们为它设置了足够高的 slop 值使匹配时的词序有更大的灵活性。 
        }
    }
}

wildcard 通配符

wildcard 通配符查询也是一种底层基于词的查询, 与前缀查询不同的是它允许指定匹配的正则式。它使用标准的 shell 通配符查询: ? 匹配任意字符, * 匹配 0 或多个字符。

代码语言:javascript复制
{
    "query": {
        "wildcard": {
            "postcode": "W?F*HW"   #? 匹配 1 和 2 , * 与空格及 7 和 8 匹配。 
        }
    }
}

regexp 正则式

代码语言:javascript复制
{
    "query": {
        "regexp": {
            "postcode": "W[0-9]. "   #这个正则表达式要求词必须以 W 开头,紧跟 0 至 9 之间的任何一个数字,然后接一或多个其他字符。 
        }
    }
}

控制相关度

Doc相关度评分

Index相关度评分

当在多个索引中搜索时, 可以使用参数 indices_boost 来提升整个索引的权重。在下面例子中,当要为最近索引的文档分配更高权重时,可以这么做:

代码语言:javascript复制
GET /docs_2014_*/_search   #这个多索引查询涵盖了所有以字符串 docs_2014_ 开始的索引。 
{
  "indices_boost": {   #其中,索引 docs_2014_10 中的所有文件的权重是 3 ,索引 docs_2014_09 中是 2 ,其他所有匹配的索引权重为默认值 1 。 
    "docs_2014_10": 3,
    "docs_2014_09": 2
  },
  "query": {
    "match": {
      "text": "quick brown fox"
    }
  }
}

相关度检索评分

boosting

它接受 positivenegative 查询。只有那些匹配 positive 查询的文档罗列出来,对于那些同时还匹配 negative 查询的文档将通过文档的原始 _scorenegative_boost 相乘的方式降级后的结果。

为了达到效果, negative_boost 的值必须小于 1.0 。在这个示例中,所有包含负向词的文档评分 _score 都会减半。

代码语言:javascript复制
GET /_search
{
  "query": {
    "boosting": {
      "positive": {
        "match": {
          "text": "apple"
        }
      },
      "negative": {
        "match": {
          "text": "pie tart fruit crumble tree"
        }
      },
      "negative_boost": 0.5
    }
  }
}

constant_score

有时候我们根本不关心 TF/IDF , 只想知道一个词是否在某个字段中出现过。

constant_score 查询中,它可以包含查询或过滤,为任意一个匹配的文档指定评分 1 ,忽略 TF/IDF 信息。

代码语言:javascript复制
GET /_search
{
  "query": {
    "bool": {
      "should": [
        { "constant_score": {
          "query": { "match": { "description": "wifi" }}
        }},
        { "constant_score": {
          "query": { "match": { "description": "garden" }}
        }},
        { "constant_score": {
          "boost":   2   #pool 语句的权重提升值为 2 ,而其他的语句为 1 。 
          "query": { "match": { "description": "pool" }}
        }}
      ]
    }
  }
}

function_score

function_score 查询 是用来控制评分过程的终极武器,它允许为每个与主查询匹配的文档应用一个函数, 以达到改变甚至完全替换原始查询评分 _score 的目的。

代码语言:javascript复制
GET /blogposts/post/_search
{
  "query": {
    "function_score": {   #function_score 查询将主查询和函数包括在内。 
      "query": {   #主查询优先执行。
        "multi_match": {
          "query":    "popularity",
          "fields": [ "title", "content" ]
        }
      },
      "field_value_factor": {   #field_value_factor 函数会被应用到每个与主 query 匹配的文档。 
        1."field": "votes"   #每个文档的 votes 字段都 必须 有值供 function_score 计算。如果 没有 文档的 votes 字段有值,那么就 必须 使用 missing 属性 提供的默认值来进行评分计算。 
        2."modifier": "log1p"   #modifier 为 log1p 。
        3."factor":   2   #双倍效果。factor 值大于 1 会提升效果, factor 值小于 1 会降低效果
      },
      4."boost_mode": "sum" ,  #将函数计算结果值累加到评分 _score 。
      5."max_boost":  1.5   #无论 field_value_factor 函数的结果如何,最终结果都不会大于 1.5 。 
    }
  }
}

1.new_score = old_score * number_of_votes
2.new_score = old_score * log(1   number_of_votes)
3.new_score = old_score * log(1   factor * number_of_votes)
4.new_score = old_score   log(1   0.1 * number_of_votes)

到目前为止,我们展现的都是为所有文档应用单个函数的使用方式,现在会用过滤器将结果划分为多个子集(每个特性一个过滤器),并为每个子集使用不同的函数。

代码语言:javascript复制
GET /_search
{
  "query": {
    "function_score": {
      "filter": {  #function_score 查询有个 filter 过滤器而不是 query 查询。 
        "term": { "city": "Barcelona" }
      },
      "functions": [   #functions 关键字存储着一个将被应用的函数列表。 
        {
          "filter": { "term": { "features": "wifi" }},   #函数会被应用于和 filter 过滤器(可选的)匹配的文档。
          "weight": 1
        },
        {
          "filter": { "term": { "features": "garden" }},   #函数会被应用于和 filter 过滤器(可选的)匹配的文档。
          "weight": 1
        },
        {
          "filter": { "term": { "features": "pool" }},   #函数会被应用于和 filter 过滤器(可选的)匹配的文档。
          "weight": 2   #pool 比其他特性更重要,所以它有更高 weight 。 
        },
        {
          "random_score": {   #random_score 语句没有任何过滤器 filter ,所以会被应用到所有文档。 
            "seed":  "the users session id"   #将用户的会话 ID 作为种子 seed ,让该用户的随机始终保持一致,相同的种子 seed 会产生相同的随机结果。 
          }
        }
      ],
      "score_mode": "sum",   #score_mode 指定各个函数的值进行组合运算的方式。
    }
  }
}

function_score 查询会提供一组 衰减函数(decay functions) , 让我们有能力在两个滑动标准,如地点和价格,之间权衡。

代码语言:javascript复制
GET /_search
{
  "query": {
    "function_score": {
      "functions": [
        {
          "gauss": {
            "location": {   #location 字段以地理坐标点 geo_point 映射。 
              "origin": { "lat": 51.5, "lon": 0.12 },
              "offset": "2km",
              "scale":  "3km"
            }
          }
        },
        {
          "gauss": {
            "price": {   #price 字段是数值。 
              "origin": "50",   #参见 理解价格语句 ,理解 origin 为什么是 50 而不是 100 。
              "offset": "50",
              "scale":  "20"
            }
          },
          "weight": 2   #price 语句是 location 语句权重的两倍。
        },
        {
        "script_score": {
          "params": {   #将这些变量作为参数 params 传递,我们可以查询时动态改变脚本无须重新编译。
            "threshold": 80,
            "discount": 0.1,
            "target": 10
          },
          "script": "price  = doc['price'].value; margin = doc['margin'].value;
          if (price < threshold) { return price * margin / target };
          return price * (1 - discount) * margin / target;"   #JSON 不能接受内嵌的换行符,脚本中的换行符可以用 n 或 ; 符号替代。 
          }
        }
      ]
    }
  }
}

0 人点赞