ElasticSearch常用结构化搜索
最近,需要用到ES的一些常用的结构化搜索命令,因此,看了一些官方的文档,学习了一下。结构化查询指的是查询那些具有内在结构的数据,比如日期、时间、数字都是结构化的。
它们都有精确的格式,我们可以对这些数据进行逻辑操作,比较常见的操作包括比较时间区间,或者获取两个数字间的较大值。
精确查询
当进行精确查询时,过滤器filter是十分重要的,因为它们效率非常高,过滤器不计算相关性(直接跳过了整个记分阶段)而且很容易进行缓存。
过滤数字
我们首先看 term filter,它最常用,可以用来处理数字,布尔值,日期和文本。
例如我们有一些产品:
POST /my_store/products/_bulk
{ "index": { "_id": 1 }}
{ "price" : 10, "productID" : "XHDK-A-1293-#fJ3" }
{ "index": { "_id": 2 }}
{ "price" : 20, "productID" : "KDKE-B-9947-#kL5" }
{ "index": { "_id": 3 }}
{ "price" : 30, "productID" : "JODL-X-1937-#pV7" }
{ "index": { "_id": 4 }}
{ "price" : 30, "productID" : "QQPX-R-3956-#aD8" }
我们想要做的是要查询具有某个价格的所有产品,如果对于SQL熟悉,那么它的表达式是:
SELECT * FROM products WHERE price = 20
在ES查询中,我们使用 term 达到相同的目的:
{
"term" : {
"price" : 20
}
}
但是在ES里,term 不能单独使用,search API期望的是一个 query 而不是 filter,所以,我们需要把 term 放在一个filter query里进行使用:
GET /my_store/products/_search
{
"query" : {
"filtered" : { #filtered 查询同时接受一个 query 和 filter
"query" : {
"match_all" : {} #match_all 会返回所有匹配的文件,这是个默认行为
},
"filter" : {
"term" : { #term 过滤我们之前说到的,需要注意的是这里 term块 是处于 filter 之内的
"price" : 20
}
}
}
}
}
执行结果正如我们期望一样,它只会返回文档2,这里我们称为命中hit。
"hits" : [
{
"_index" : "my_store",
"_type" : "products",
"_id" : "2",
"_score" : 1.0, #1
"_source" : {
"price" : 20,
"productID" : "KDKE-B-9947-#kL5"
}
}
]
之前我们说到filter不会进行记分或相关性计算,这里的分数来自于我们查询时使用的关键字 match_all ,它会同等对待所有的文件,并对所有的结果都给以1的记分。
过滤文本
term 同样可以用来过滤文本,如果我们想要查询某个具体UPC id的产品,SQL语句会是下面这样:
SELECT product FROM products WHERE productID = "XHDK-A-1293-#fJ3"
转换成ES查询,同样使用 term 来查询:
GET /my_store/products/_search
{
"query" : {
"filtered" : {
"filter" : {
"term" : {
"productID" : "XHDK-A-1293-#fJ3"
}
}
}
}
}
但这里有个小问题,我们没有如预期得到想要的结果!为什么呢?问题并不出在 term 查询上,问题出在数据索引的方式。如果使用 analyze API(Test Analyzers),我们可以看到这里的UPC码以及被拆分成多个小的token:
GET /my_store/_analyze?field=productID
XHDK-A-1293-#fJ3
结果
{
"tokens" : [ {
"token" : "xhdk",
"start_offset" : 0,
"end_offset" : 4,
"type" : "<ALPHANUM>",
"position" : 1
}, {
"token" : "a",
"start_offset" : 5,
"end_offset" : 6,
"type" : "<ALPHANUM>",
"position" : 2
}, {
"token" : "1293",
"start_offset" : 7,
"end_offset" : 11,
"type" : "<NUM>",
"position" : 3
}, {
"token" : "fj3",
"start_offset" : 13,
"end_offset" : 16,
"type" : "<ALPHANUM>",
"position" : 4
} ]
}
所以,当我们用 term 去过滤值 XHDK-A-1293-#fJ3 的时候,找不到任何文件,因为这个token不在我们的反向索引(inverted index)之中,正如上面呈现的,索引里面有4个token。
显然,这种对于id码或其他任何精确值的处理方式不是我们想要的。
为了避免这种问题,我们需要告诉ElasticSearch这个字段具有精确值,需要被设置成 not_analyzed 。 我们可以在定制化字段mapping中找到相关内容。为了修正这个问题,我们需要首先删除老的index,然后再创建一个新的
DELETE /my_store #1
PUT /my_store #2
{
"mappings" : {
"products" : {
"properties" : {
"productID" : {
"type" : "string",
"index" : "not_analyzed" #3
}
}
}
}
}
然后我们就可以对文件重索引了:
POST /my_store/products/_bulk
{ "index": { "_id": 1 }}
{ "price" : 10, "productID" : "XHDK-A-1293-#fJ3" }
{ "index": { "_id": 2 }}
{ "price" : 20, "productID" : "KDKE-B-9947-#kL5" }
{ "index": { "_id": 3 }}
{ "price" : 30, "productID" : "JODL-X-1937-#pV7" }
{ "index": { "_id": 4 }}
{ "price" : 30, "productID" : "QQPX-R-3956-#aD8" }
组合过滤器
上面的两个例子都是单个filter的使用方式,在实际中,我们很多情况下会同时会对多个值或字段使用filter。例如,在ElasticSearch中,如何标识下面这个SQL?
SELECT product FROM products WHERE (price = 20 OR productID = "XHDK-A-1293-#fJ3") AND (price != 30)
在这种情况下,我们需要 bool filter。这是一个复合过滤器可以接收多个参数,然后将他们组合成布尔组合。
布尔过滤器(Bool Filter)
bool filter包括三部分:
{
"bool" : {
"must" : [],
"should" : [],
"must_not" : [],
}
}
-
must:所有的语句必须匹配,与 AND 等价。
-
must_not:所有的语句都不能匹配,与 NOT 等价。
-
should:至少有一个语句匹配,与 OR 等价。
用ES查询实现我们上面SQL里的查询:
GET /my_store/products/_search
{
"query" : {
"filtered" : {
"filter" : {
"bool" : {
"should" : [
{ "term" : {"price" : 20}},
{ "term" : {"productID" : "XHDK-A-1293-#fJ3"}}
],
"must_not" : {
"term" : {"price" : 30}
}
}
}
}
}
}
我们搜索的结果返回了2个hits,两个文件各满足其中一个条件:
"hits" : [
{
"_id" : "1",
"_score" : 1.0,
"_source" : {
"price" : 10,
"productID" : "XHDK-A-1293-#fJ3"
}
},
{
"_id" : "2",
"_score" : 1.0,
"_source" : {
"price" : 20,
"productID" : "KDKE-B-9947-#kL5"
}
}
]
嵌套布尔过滤器(Nesting Boolean Filters)
尽管 bool 是一个复合的过滤器,可以接受多个子过滤器,需要注意的是 bool 过滤器本身仍然是一个过滤器(filter)。这意味着我们可以将一个bool过滤器置于另外一个bool过滤器内部,这为我们提供了复杂布尔逻辑的处理能力:
对于一个SQL语句:
SELECT document FROM products WHERE productID = "KDKE-B-9947-#kL5" OR ( productID = "JODL-X-1937-#pV7" AND price = 30 )
我们将其转换成一个嵌套的 bool 过滤器:
GET /my_store/products/_search
{
"query" : {
"filtered" : {
"filter" : {
"bool" : {
"should" : [
{ "term" : {"productID" : "KDKE-B-9947-#kL5"}}, #1
{ "bool" : { #2
"must" : [
{ "term" : {"productID" : "JODL-X-1937-#pV7"}}, #3
{ "term" : {"price" : 30}} #4
]
}}
]
}
}
}
}
}
得到的结果有两个文件,他们各满足 should 中的一个条件:
"hits" : [
{
"_id" : "2",
"_score" : 1.0,
"_source" : {
"price" : 20,
"productID" : "KDKE-B-9947-#kL5" #1
}
},
{
"_id" : "3",
"_score" : 1.0,
"_source" : {
"price" : 30, #2
"productID" : "JODL-X-1937-#pV7" #3
}
}
]