ES基本查询
- 基本匹配模式:
ES支持的查询语法中的匹配模式比较多,主要包括以下几种:
- term查询:精确匹配,不会分词。
- terms查询:精确匹配多个值。
- match查询:对字段进行全文本搜索并分词,允许模糊匹配。
- match_phrase查询:对字段进行短语全文本搜索,要求匹配的词条必须按照原始文本顺序相邻出现。
- prefix查询:对字段进行前缀搜索。
- regexp查询:使用正则表达式匹配。
- wildcard查询:使用通配符进行模糊匹配。
- range查询:对数值、日期等范围进行匹配,包括大于、小于、大于等于、小于等于、等于等。
- exists查询:匹配指定字段存在的文档。
- bool查询:将其他查询进行逻辑组合,包括must、must_not、should、filter等。
- nested查询:查询嵌套文档中的字段。
- fuzzy查询:支持单词拼写错误的匹配。
- geo查询:查询地理坐标范围内的文档。
- ids查询:根据指定的文档ID进行匹配。
- span查询:一系列用于匹配词语之间距离或顺序的查询,包括span_term、span_multi等。
以上是ES支持的一些常见的查询语法中的匹配模式,不同的查询语法和匹配模式可以组合使用,以实现更复杂的查询。
给出每一个匹配模式的示例:
term
查询:
{ "query": { "term": { "age": 18 } } }
terms
查询:
{ "query": { "terms": { "age": [18, 19] } } }
match
查询:
{ "query": { "match": { "title": "elasticsearch" } } }
match_phrase
查询:
{ "query": { "match_phrase": { "content": "elasticsearch tutorial" } } }
prefix
查询:
{ "query": { "prefix": { "name": "jo" } } }
regexp
查询:
{ "query": { "regexp": { "name": "joh?n" } } }
wildcard
查询:
{ "query": { "wildcard": { "name": "j*hn" } } }
range
查询:
{ "query": { "range": { "age": { "gte": 18, "lte": 25 } } } }
exists
查询:
{ "query": { "exists": { "field": "age" } } }
bool
查询:
{ "query": { "bool": { "must": [ { "match": { "title": "elasticsearch" } }, { "match": { "content": "tutorial" } } ] } } }
这样写也正确:
{ "query": { "must": [ { "match": { "name": "elasticsearch" } }, { "match": { "content": "搜索引擎" } } ] } }
举个bool查询的复杂示例:{
"query": {
"bool": {
"must": [
{
"bool": {
"should": [
{
"match": {
"title": {
"query": "elasticsearch"
}
}
},
{
"match": {
"content": {
"query": "elasticsearch"
}
}
}
]
}
},
{
"bool": {
"should": [
{
"match": {
"title": {
"query": "tutorial"
}
}
},
{
"match": {
"content": {
"query": "tutorial"
}
}
}
]
}
},
{
"bool": {
"should": [
{
"match": {
"title": {
"query": "beginner"
}
}
},
{
"match": {
"content": {
"query": "beginner"
}
}
}
]
}
}
],
"must_not": [
{
"match": {
"category": {
"query": "A"
}
}
}
],
"filter": {
"term": {
"author": "John"
}
},
"minimum_should_match": 2
}
}
}
这个查询使用了三个bool查询套嵌,每个bool查询都使用should子句将title和content字段与关键词匹配,然后用must子句将三个bool查询组合起来,要求至少匹配两个关键词。使用must_not子句排除类别为A的文章,使用filter子句限制作者为John。
nested
查询:
{ "query": { "nested": { "path": "comments", "query": { "match": { "comments.author": "john" } } } } }
fuzzy
查询:
{ "query": { "fuzzy": { "name": { "value": "john", "fuzziness": 2 } } } }
geo
查询:
{ "query": { "bool": { "must": { "match_all": {} }, "filter": { "geo_distance": { "distance": "10km", "pin.location": { "lat": 40, "lon": -70 } } } } } }
ids
查询:
{ "query": { "ids": { "values": ["1", "2", "3"] } } }
span
查询:
{ "query": { "span_near": { "clauses": [ { "span_term": { "field": "quick" } }, { "span_term": { "field": "brown" } }, { "span_term": { "field": "fox" } } ], "slop": 3, "in_order": false } } }
简单地解释一下这些示例的含义:
-
term
查询:查询age字段的值为18的文档,这里不会分词,只对整个字段进行精确匹配。 -
terms
查询:查询age字段的值为18或19的文档,这里可以同时对多个值进行匹配。 - match查询:对title字段进行全文本搜索并分词,查找包含单词"elasticsearch"的文档。
-
match_phrase
查询:对content字段进行短语全文本搜索,要求匹配的词条必须按照原始文本顺序相邻出现,查找包含短语"elasticsearch tutorial"的文档。 -
prefix
查询:查询name字段以"jo"开头的文档。 -
regexp
查询:使用正则表达式查找name字段中匹配"joh?n"模式的文档,这里"?"表示匹配前面字符0次或1次。 -
wildcard
查询:使用通配符查找name字段中匹配"jhn"模式的文档,这里""表示匹配0个或多个任意字符。 -
range
查询:查询age字段大于等于18小于等于25的文档。 -
exists
查询:查询存在age字段的文档。 -
bool
查询:将两个match查询组合,必须同时满足两个条件才能匹配。 -
nested
查询:查询包含特定作者("john")的一篇文章的评论。 -
fuzzy
查询:查找name字段拼写跟"john"相似的文档,这里"fuzziness"表示允许编辑的最大距离为2(即可以通过2次编辑变成"john")。 -
geo
查询:查找距离经纬度为(40,-70)坐标不超过10km的地理位置文档。 -
ids
查询:查找ID为1、2、3的文档。 -
span
查询:查询包含短语"quick brown fox"的文档的数量,这里"slop"表示相邻单词的最大距离为3。
filter
和must
都是bool查询中的查询子句,它们的作用不同:
filter
查询与must
查询类似,都用于对查询进行过滤和匹配。但不同的是,filter
不计算查询得分并且可以使用缓存,它是用于过滤大量数据的一个非常有效的机制,通常用于在水平方向上增加查询效率。它可以和其他查询子句(例如must
、should
、must_not
)一起使用,如果多种查询子句一起使用,则必须满足所有的must
和filter
语句,而should
则只需满足其任意一个。例如:
{
"query": {
"bool": {
"filter": {
"range": {
"price": {
"gte": 100,
"lte": 1000
}
}
},
"must": [
{
"match": {
"title": "elasticsearch"
}
}
]
}
}
}
上述查询用到了filter
和must
两个查询子句,它首先使用filter
子句过滤出价格在[100,1000]
区间的文档,然后再在这个结果中使用must
查询子句匹配title
字段中的"elasticsearch",从而得到我们需要的结果。
must
查询用于执行多个查询的布尔“与”操作,并结合各查询子句的结果进行打分排序。这意味着必须满足must
查询子句中的所有条件才能使文档获得更高的得分并返回。例如:
{
"query": {
"bool": {
"must": [
{
"match": {
"user": "kimchy"
}
},
{
"match": {
"title": "elasticsearch"
}
}
]
}
}
}
上述查询中,两个match
查询子句的查询结果都必须为真,才能匹配到符合条件的文档,两个查询条件是“与”的关系。在实际使用中,我们要根据不同的需求选择不同的查询子句来处理查询,尽可能提高查询效率和准确性。如果在查询的大多数操作中使用了filter
,通常推荐在其他查询中使用`must,这样可以带来更快的查询性能。
- 相关性得分计算在 Elasticsearch 中非常复杂,在这里简单介绍一下简单模式下的得分计算方法。
以match
查询为例,需要考虑的因素有文档中查询关键字在也出现的域的数量、文档中出现查询关键字的频率、查询关键字在一个域中的重要性、查询关键字在所有文档中出现的频率等。Elasticsearch为每个索引分配一个默认的相关性评分器,该评分器使用了基于TF-IDF(词频-逆文档频率)算法的BM25算法计算文档得分。BM25考虑了词项的频度、文档长度等因素,并通过对文档长度进行归一化处理来对文档的“重要性”进行加权。
简而言之,BM25算法计算文档得分的大致方式如下:
- 计算查询中每个词的TF-IDF得分
- 对于每个查询词,则通过考虑所有包含这个词的文档计算文档频率(IDF)
- 计算查询与文档中每个匹配的词项之间的匹配度(term frequency)。匹配度用于调整词项的重要性,即一些重要的关键词应该有更高的权值。
- 计算文档得分,再将文档得分与其他相关性评分算法产生的得分相加,获得最终的文档评分。
BM25算法实现了一种有效地对文档打分的方法,能够准确地反映查询与文档之间的相关性。通过修改算分公式和相关性评分器,甚至可以更好地优化该算法,以满足不同场景的需求。
假设我们有一个文档,它包含下面这些字段:
{ "title":"Elasticsearch 入门", "content":"这是一篇关于 Elasticsearch 入门的文章,用以介绍 Elasticsearch 的基本特性、查询语法和常见应用场景" }
假设我们进行下面这个查询:
GET /my_index/_search { "query": { "bool": { "should": [ { "match": { "title": "elasticsearch" } }, { "match": { "content": "elasticsearch" } } ] } }, "sort": { "_score": "desc" } }
Elasticsearch会计算所有匹配到的文档与查询之间的相关性得分。假设在上面的示例中,"elasticsearch"在title字段和content字段中都出现了一次,那么我们可以使用BM25算法来计算相关性得分。
假设我们参数配置为k1 = 1.2、b = 0.75、avgdl=9、idf=1、tf=1在。那么对上述文档进行评分时,BM25算法的基本公式如下:
score(D,Q) = IDF × (tf(Q_i,D) × (k1 + 1)) / (tf(Q_i,D) + k1 * (1 - b + b * (doc_len/avgdl)))
其中score(D,Q) 表示文档D和查询Q之间的相关得分,IDF是词项的逆向文档频率,tf(Q_i,D) 表示词项Q_i出现在文档D中的频率,k1 和 b 是两个参量,avgdl 表示所有文档的平均长度。
我们可以通过填入合适的参数来计算该文档的相关得分,得到最终的评分结果。在实际中,Elasticsearch 会根据文档的长度、查询的倒入、各种噪声词筛选、参数调整等多种因素对得分公式进行优化,结果的细节也比简化版更加复杂,涉及到更多的数学和算法知识。
- IDF(Inverse Document Frequency)表示逆文档频率,用于衡量一个词的重要程度。IDF 值越大,则表示该词的重要性越高,因此文档中包含该词的相关得分就越高。
IDF 的计算方式是,首先统计所有文档中包含该词的文档数,再用总文档数除以包含该词的文档数,得到一个标量,再对其取对数得到 IDF。
具体来讲,IDF 的计算公式为:
IDF = log( (total_number_of_documents+1) / (number_of_documents_containing_term + 1) )
其中total_number_of_documents表示总文档数,number_of_documents_containing_term表示包含该词的文档数。
需要注意的是,IDF 值的计算是基于总文档数的,而不是查询中的文档数。IDF 值越大,表示该词的重要程度越高,因此文档中包含该词的相关得分就越高。
例如,假设我们有一个包含 10,000 篇文档的索引,其中包含 1,000 篇文档包含词语"elasticsearch",那么"elasticsearch"的 IDF 值就是:
IDF = log((10000+1)/(1000+1)) = 1.38629
以下是一个根据相关性得分排序的示例,假设我们的索引中有两篇文档,它们的内容如下:
[ { "title": "Elastic Stack入门指南", "content": "在本文中,我们将介绍如何使用Elastic Stack处理和分析日志数据。Elastic Stack是一组功能强大且易于使用的开源工具,被广泛应用于日志分析、安全分析、网络分析、指标分析等各种场景。" }, { "title": "如何使用Elasticsearch进行全文搜索", "content": "Elasticsearch是一个分布式的搜索引擎,它非常适合处理大量的结构化和非结构化数据,尤其是文本数据。在本文中,我们将介绍如何使用Elasticsearch进行全文搜索,包括构建索引、查询语法、相关性得分等方面的内容。" } ]
我们的目标是搜索所有包含“全文搜索”的文档,然后将它们根据相关性得分从高到低进行排序。我们可以使用以下查询:
GET my_index/_search { "query": { "multi_match": { "query": "全文搜索", "fields": ["title", "content"] } }, "sort": [ { "_score": { "order": "desc" } } ] }
在这个查询中,我们使用了multi_match
查询,将查询关键字“全文搜索”在title
字段和content
字段中的匹配都纳入了考虑范围,它们将被视为在进行排序时一起考虑的两个独立的查询条件。使用相关性得分对搜索结果进行排序,文档将按照相关性得分从高到低进行排序,得分高的文档将排在前面。我们在查询中使用了_score
字段进行排序,Elasticsearch将根据每个文档与查询之间的相关性得分来计算它们的排名。
假设第一篇文档的相关性得分为1.5,第二篇文档的相关性得分为1.0。那么最终排序后的结果将是:
[ { "title": "如何使用Elasticsearch进行全文搜索", "content": "Elasticsearch是一个分布式的搜索引擎,它非常适合处理大量的结构化和非结构化数据,尤其是文本数据。在本文中,我们将介绍如何使用Elasticsearch进行全文搜索,包括构建索引、查询语法、相关性得分等方面的内容。", "_score": 1.5 }, { "title": "Elastic Stack入门指南", "content": "在本文中,我们将介绍如何使用Elastic Stack处理和分析日志数据。Elastic Stack是一组功能强大且易于使用的开源工具,被广泛应用于日志分析、安全分析、网络分析、指标分析等各种场景。", "_score": 1.0 } ]
可以看到,我们得到了符合预期的结果,根据相关性得分将包含“全文搜索”的文档排序输出。
multi_match
是Elasticsearch中一个可以同时对多个字段进行搜索的查询语句。它常用于针对同一关键字对多个字段进行搜索,并将多个字段的搜索结果合并起来。
multi_match
查询可以使用各种类型的匹配询子句,比如match
、match_phrase
、prefix
、wildcard
等,在进行多个字段的查询时,还可以为每个字段指定不同的查询类型。在multi_match
语句中,查询条件会被自动扩展到所有的索引字段中,不需要为每个字段单独写一个match
子句。
以下是一个multi_match
查询的示例,通过同时针对两个字段进行搜索:
GET my_index/_search { "query": { "multi_match": { "query": "elasticsearch 入门", "fields": ["title", "content"] } } }
在这个示例中,我们将查询关键字 "elasticsearch 入门" 在 "title" 和 "content" 两个字段中搜索。由于没有指定任何查询类型,Elasticsearch会自动为这两个字段选择一个合适的查询类型。
当我们想要在搜索结果中使用相关性得分对结果进行排序时,通常会使用以下类似的查询:
GET my_index/_search { "query": { "multi_match": { "query": "elasticsearch 入门", "fields": ["title", "content"] } }, "sort": [ { "_score": { "order": "desc" } } ] }
在这个查询中,我们将multi_match
查询和sort
排序语句组合起来,按照相关性得分从高到低进行排序(使用了_score
字段),相关性得分高的文档将排在前面。
multi_match
查询可以理解为多个 match
查询组成的 should
语句。这意味着,如果文档至少满足其中一个 match
查询的条件,则可以匹配。举个例子,下面两个查询是等效的:
使用 multi_match
:
{
"query": {
"multi_match": {
"query": "Shanghai China",
"fields": ["title", "content"]
}
}
}
使用 should
:
{
"query": {
"bool": {
"should": [
{
"match": {
"title": "Shanghai China"
}
},
{
"match": {
"content": "Shanghai China"
}
}
]
}
}
}
不过需要注意,multi_match
子句可以选择性地指定查询运算符,例如 and
或 or
,这可以在处理不同的查询场景时提供更多的控制选项。
在 multi_match
查询中使用指定的查询运算符,例如 AND
或 OR
,可以控制其多个查询子句的相对重要性,以及如何在查询中组合并排除符合条件的文档。例如:
{
"query": {
"multi_match": {
"query": "电视",
"fields": ["标题", "内容"],
"type": "best_fields",
"operator": "AND"
}
}
}
假设上面的查询语句是针对 商品
索引的,其中包含 标题
和 内容
两个字段。该查询意味着,“在所有文档中,返回所有包含 电视
的文档,并且该文档必须同时包含 标题
字段和 内容
字段中的 电视
关键字。