Elasticsearch中基于词项的搜索

为了方便我们学习,我们导入kibana为我们提供的范例数据。

Elasticsearch中基于词项的搜索

Elasticsearch中基于词项的搜索

目前为止,我们已经探索了如何将数据放入Elasticsearch,现在来讨论下如何将数据从Elasticsearch中拿出来,那就是通过搜索。毕竟,如果不能搜索数据,那么将其放入搜索引擎的意义又何在呢?幸运的是,Elasticsearch提供了丰富的接口来搜索数据,涵盖了Lucene所有的搜索功能。因为Elasticsearch允许构建搜索请求的格式很灵活,请求的构建有无限的可能性。要了解哪些查询和过滤器的组合适用于你的数据,最佳的方式就是进行实验,因此不要害怕在项目的数据上尝试这些组合,这样才能弄清哪些更适合你的需求。

_search接口

所有的REST搜索请求使用_search接口,既可以是GET请求,也可以是POST请求,也可以通过在搜索URL中指定索引来限制范围。
_search接口有两种请求方法:

  1. 基于URI 的请求方式
  2. 基于请求体的方式,

无论哪种,他们执行的语法都是基于DSL(ES为我们定义的查询语言,基于JSON的查询语言),只是形式上不同。我们会基于请求体的方式来学习。比如说:

get kibana_sample_data_flights/_search
{
  "query": {
    "match_all": {}
  }
}

或者:

get kibana_sample_data_flights/_search
{
  "query": {
    "match_none": {}
  }
}

当然上面的查询没什么太多的用处,因为他们分别代表匹配所有和全不匹配。所以我们经常要使用各种语法来进行查询,一旦选择了要搜索的索引,就需要配置搜索请求中最为重要的模块。这些模块涉及文档返回的数量,选择最佳的文档返回,以及配置不希望哪些文档出现在结果中等等。

_search接口支持的参数:

  • query:这是搜索请求中最重要的组成部分,它配置了基于评分返回的最佳文档,也包括了你不希望返回哪些文档。
  • size:代表了返回文档的数量。
  • from:和size一起使用,from用于分页操作。需要注意的是,为了确定第2页的10项结果,Elasticsearch必须要计算前 20个结果。如果结果集合不断增加,获取某些靠后的翻页将会成为代价高昂的操作。
  • _source:指定_source字段如何返回。默认是返回完整的_source字段。通过配置_source将过滤返回的字段。如果索引的文档很大,而且无须结果中的全部内容,就使用这个功能。请注意,如果想使用它,就不能在索引映射中关闭_ source字段。
  • sort:默认的排序是基于文档的得分。如果并不关心得分,或者期望许多文档的得分相同,添加额外的sort将帮助你控制哪些文档被返回。

结果起始和页面大小

from和size字段用于指定结果的开始位置,以及每“页"结果的数量。举个例子,如果发送的from值是7,size值是5,那么 Elasticsearch将返回第8、9、10、11和12条数据(由于from参数是从0开始,指定7就是从第8条数据开始)。如果没有发送这两个参数,Elasticsearch默认从第一条数据开始(from=0),返回10条数据(size=10)。

例如:

get kibana_sample_data_flights/_search
{
  "from": 10,
  "size": 3,
  "query": {
    "term": {
      "DestCountry": "CN"
    }
  }
}

但是注意,from与size的和不能超过index.max_result_window这个索引配置项设置的值。默认情况下这个配置项的值为 10000,所以如果要查询10000条以后的文档,就必须要增加这个配置值。例如,要检索第10000条开始的200条数据,这个参数的值必须要大于10200,否则将会抛出类似“esult window is too large”的异常。

由此可见,Elasticsearch在使用from和size处理分页问题时会将所有数据全部取出来,然后再截取用户指定范围的数据返回。所以在查询非常靠后的数据时,即使使用了from和size定义的分页机制依然有内存溢出的可能,而max_result_window设置的10000条则是对Elastiesearch的一种保护机制。

那么Elasticsearch为什么要这么设计呢?首先,在互联网时代的数据检索应该通过相似度算法,提高检索结果与用户期望的附和度,而不应该让用户在检索结果中自己挑选满意的数据。以互联网搜索为例,用户在浏览搜索结果时很少会看到第 3页以后的内容。假如用户在翻到第10000条数据时还没有找到需要的结果,那么他对这个搜索引擎一定会非常失望。

_source参数

元字段_source中存储了文档的原始数据。如果请求中没有指定_source,Elasticsearch默认返回整个_source,或者如果_source没有存储,那么就只返回匹配文档的元数据:_id、_type、_index和_score。
例如:

get kibana_sample_data_flights/_search
{
  "query": {
    "match_all": {}
  },
  "_source": [
    "DestCountry",
    "OriginCountry"
  ]
}

你不仅可以返回字段列表,还可以指定通配符。例如,如果想同时返回“DestCountry”和“DestWeather”字段,可以这样配置_source: “Dest*”。也可以使用通配字符串的数组来指定多个通配符,例如_source: [“Origin*”, “*Weather”]。

get kibana_sample_data_flights/_search
{
  "query": {
    "match_all": {}
  },
  "_source": [
    "Dest*",
    "Origin*"
  ]
}

不仅可以指定哪些字段需要返回,还可以指定哪些字段无须返回。比如:

get kibana_sample_data_flights/_search
{
  "query": {
    "match_all": {}
  },
  "_source": {
    "includes" : ["Dest*", "Origin*"],
    "excludes": ["*Location"]
  }
}

排序

大多搜索最后涉及的元素都是结果的排序(sort)。如果没有指定sort排序选项,Elasticsearch返回匹配的文档的时候,按照_score取值的降序来排列,这样最为相关的(得分最高的)文档就会排名在前。为了对字段进行升序或降序排列,指定映射的数组,而不是字段的数组。通过在sort中指定字段列表或者是字段映射,可以在任意数量的字段上进行排序。

例如:

get kibana_sample_data_flights/_search
{
  "from": 100,
  "size": 20,
  "query": {
    "match_all": {}
  },
  "_source": [
    "Origin*",
    "*Weather"
  ],
  "sort": [
    {
      "DistanceKilometers": "asc"
    },
    {
      "FlightNum": "desc"
    }
  ]
}

基于词项的搜索

term查询

对词项做精确匹配,数值、日期等等,如:

get kibana_sample_data_flights/_search
{
  "query": {
    "term": {
      "OriginCountry": "CH"
    }
  }
}

对于字符串而言,字符串的精确匹配是指字符的大小写,字符的数量和位置都是相同的,词条(term)查询使用字符的完全匹配方式进行文本搜索,词条查询不会分析(analyze)查询字符串,给定的字段必须完全匹配词条查询中指定的字符串。

因此可以把term查询理解为SQL语句中where条件的等于号。

terms查询

可以把terms查询理解为SQL语句中where条件的in操作符:

get kibana_sample_data_flights/_search
{
  "query": {
    "terms": {
      "OriginCountry": ["CH","CA"]
    }
  }
}

Elasticsearch在terms查询中还支持跨索引查询,这类似于关系型数据库中的一对多或多对多关系。比如,用户与文章之间就是一对多关系,可以在用户索引中存储文章编号的数组以建立这种对应关系,而将文章的实际内容保存在文章索引中( 当然也可以在文章中保存用户ID)。如果想将ID为1的用户发表的所有文章都找出来,在文章索引中查询时为:

POST /articles/_search
{
  "query": {
    "terms": {
      "_id": {
        "index": "users",
        "id": 1,
        "path": "articles"
      }
    }
  }
}

在上面的例子中,terms要匹配的字段是 id,但匹配值则来自于另一个索引。这里用到了index、id和path三个参数,它们分别代表要引用的索引、文档ID和字段路径。在上面的例子中,先会到users索引中在找id为1的文档,然后取出articles字段的值与articles索引里的_id做对比,这样就将用户1的所有文章都取出来了。

range查询和exists查询

range查询和过滤器的含义是不言而喻的,它们查询介于一定范围之内的值,适用于数字、日期甚至是字符串。

为了使用范围查询,需要指定某个字段的上界和下界值。例如:

get kibana_sample_data_flights/_search
{
  "query": {
    "range": {
      "FlightDelayMin": {
        "gte": 100,
        "lte": 200
      }
    }
  }
}

可以查询出延误时间在100~200之间的航班。

range支持的比较:

  • gte:大于等于 (greater than and equal)
  • gt:大于 (greater than)
  • lte:小于等于 (less than and equal)
  • lt:大于 (less than )
  • boost:相关性评分
  • exists:查询检索字段值不为空的的文档,无论其值是多少,在查询中通过field字段设置检查非空的字段名称,只能有一个。

prefix查询

prefix查询允许你根据给定的前缀来搜索词条,这里前缀在同样搜索之前是没有经过分析的。例如:

get kibana_sample_data_flights/_search
{
  "query": {
    "prefix": {
      "DestCountry": "C"
    }
  }
}

找到航班目的国家中所有以C开头的文档。

wildcard查询和regexp查询

wildcard查询就是通配符查询。*通配符替代任何数量的字符(也可以不含)或者是使用?通配符替代单个字符。

例如,有5个单词:bacon、barn、ban、baboon、bam,ba*n的查询会匹配bacon、barn、ban和baboon,这是因为*号可以匹配任何字符序列,而查询ba?n只会匹配barn,因为?任何时候都需要匹配一个单独字符。也可以混合使用多个*和?字符来匹配更为复杂的通配模板,比如 f*f?x 就可以匹配firefox。

get kibana_sample_data_flights/_search
{
  "query": {
    "wildcard": {
      "Dest": "*Marco*"
    }
  }
}

使用这种查询时,需要注意的是wildcard查询不像match等其他查询那样轻量级。查询词条中越早出现通配符(*或者?), Elasticsearch就需要做更多的工作来进行匹配。例如,对于查询词条“h*”,Elasticsearch必须匹配所有以“h” 开头的词条。如果词条是“hi*” ,Elasticsearch只需搜索所有“hi" 开头的词条,这是“h” 开头的词条集合的子集,规模更小。考虑到额外开支和性能问题,在实际生产环境中使用wildcard查询之前,需要先考虑清楚,并且尽量不要让通配符出现在查询条件的第一位。

当然Elasticsearch也支持正则regexp查询,比如:

get kibana_sample_data_flights/_search
{
  "query": {
    "regexp": {
      "OriginWeather": "[A-Za-z]{4}"
    }
  }
}
上一篇:2021-10-28


下一篇:电商API平台接口(淘宝)