作者:刘晓国
在 Elasticsearch 中,我们可以使用 bool query 来说实现一种组合的查询。它可以具有如下的一种形式的搜索:
POST _search { "query": { "bool" : { "must" : { "term" : { "user.id" : "kimchy" } }, "filter": { "term" : { "tags" : "production" } }, "must_not" : { "range" : { "age" : { "gte" : 10, "lte" : 20 } } }, "should" : [ { "term" : { "tags" : "env1" } }, { "term" : { "tags" : "deployed" } } ], "minimum_should_match" : 1, "boost" : 1.0 } } }
在上面,我看到了 must 和 must_not 这样的组合,他们表示必须满足以及禁止满足的意思。我们也同时看到了 should 这个 clause,它表示如果满足就可以增加分数,但是我们看到没有 should_not 这样的表达方法。在我们的实际的搜索中这个其实也是蛮有用的,比如我想对某些不满足一定条件的查询进行加分。
在今天的文章中,我们用一个例子来展示如何实现 shoud_not 这样的查询。
我们首先以我们之前的用过的数据来进行展示:
POST _bulk { "index" : { "_index" : "twitter", "_id": 1} } {"user":"双榆树-张三","message":"今儿天气不错啊,出去转转去","uid":2,"age":20,"city":"北京","province":"北京","country":"中国","address":"中国北京市海淀区","location":{"lat":"39.970718","lon":"116.325747"}} { "index" : { "_index" : "twitter", "_id": 2 }} {"user":"东城区-老刘","message":"出发,下一站云南!","uid":3,"age":30,"city":"北京","province":"北京","country":"中国","address":"中国北京市东城区台基厂三条3号","location":{"lat":"39.904313","lon":"116.412754"}} { "index" : { "_index" : "twitter", "_id": 3} } {"user":"东城区-李四","message":"happy birthday!","uid":4,"age":30,"city":"北京","province":"北京","country":"中国","address":"中国北京市东城区","location":{"lat":"39.893801","lon":"116.408986"}} { "index" : { "_index" : "twitter", "_id": 4} } {"user":"朝阳区-老贾","message":"123,gogogo","uid":5,"age":35,"city":"北京","province":"北京","country":"中国","address":"中国北京市朝阳区建国门","location":{"lat":"39.718256","lon":"116.367910"}} { "index" : { "_index" : "twitter", "_id": 5} } {"user":"朝阳区-老王","message":"Happy BirthDay My Friend!","uid":6,"age":50,"city":"北京","province":"北京","country":"中国","address":"中国北京市朝阳区国贸","location":{"lat":"39.918256","lon":"116.467910"}} { "index" : { "_index" : "twitter", "_id": 6} } {"user":"虹桥-老吴","message":"好友来了都今天我生日,好友来了,什么 birthday happy 就成!","uid":7,"age":90,"city":"上海","province":"上海","country":"中国","address":"中国上海市闵行区","location":{"lat":"31.175927","lon":"121.383328"}}
打入上面的 _bulk 指令,我们就创建了一个叫做 twitter 的索引。
我们首先来测试如下的搜索:
GET twitter/_search { "query": { "bool": { "must": [ { "range": { "age": { "gte": 40 } } } ], "should": [ { "match": { "city": "上海" } } ] } } }
上面的搜索表示的意思是:搜索出年龄大于 40 岁的文档,而且来自上海的文档的排名靠前。搜索的结果就是:
{ "took" : 0, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 2, "relation" : "eq" }, "max_score" : 2.5404449, "hits" : [ { "_index" : "twitter", "_type" : "_doc", "_id" : "6", "_score" : 2.5404449, "_source" : { "user" : "虹桥-老吴", "message" : "好友来了都今天我生日,好友来了,什么 birthday happy 就成!", "uid" : 7, "age" : 90, "city" : "上海", "province" : "上海", "country" : "中国", "address" : "中国上海市闵行区", "location" : { "lat" : "31.175927", "lon" : "121.383328" } } }, { "_index" : "twitter", "_type" : "_doc", "_id" : "5", "_score" : 1.0, "_source" : { "user" : "朝阳区-老王", "message" : "Happy BirthDay My Friend!", "uid" : 6, "age" : 50, "city" : "北京", "province" : "北京", "country" : "中国", "address" : "中国北京市朝阳区国贸", "location" : { "lat" : "39.918256", "lon" : "116.467910" } } } ] } }
从上面的搜索的结果,我们可以看出来 _id 为 6 的来自上海的文档排名靠前。
假如现在我们的问题变为:我们想搜索年龄大于 49 岁,但是不来自上海的排名得分靠前,我们该如何实现这个目的呢?按照这个要求,我们实现如下的 should_not 方法:
GET twitter/_search { "query": { "bool": { "must": [ { "range": { "age": { "gte": 40 } } } ], "should": [ { "bool": { "must_not": [ { "match": { "city": "上海" } } ] } } ] } } }
请注意上面的 should 部分的写法。它使用了另外一个 bool query 来表达一个来自非上海的查询。上面的查询的结果是:
{ "took" : 0, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 2, "relation" : "eq" }, "max_score" : 1.0, "hits" : [ { "_index" : "twitter", "_type" : "_doc", "_id" : "5", "_score" : 1.0, "_source" : { "user" : "朝阳区-老王", "message" : "Happy BirthDay My Friend!", "uid" : 6, "age" : 50, "city" : "北京", "province" : "北京", "country" : "中国", "address" : "中国北京市朝阳区国贸", "location" : { "lat" : "39.918256", "lon" : "116.467910" } } }, { "_index" : "twitter", "_type" : "_doc", "_id" : "6", "_score" : 1.0, "_source" : { "user" : "虹桥-老吴", "message" : "好友来了都今天我生日,好友来了,什么 birthday happy 就成!", "uid" : 7, "age" : 90, "city" : "上海", "province" : "上海", "country" : "中国", "address" : "中国上海市闵行区", "location" : { "lat" : "31.175927", "lon" : "121.383328" } } } ] } }
显然这次的结果,我们并没有改变所返回的数据的数量,但是我们确实改变了返回数据的次序,虽然在本例子中分数还是 1.0(这是因为 must_not 不影响分数)。在这次的查询中,我们可以看到来自北京的文档的排名提前了。