我的商店中的每个用户都可以进行多次购买,并且有关购买的信息都存储在ElasticSearch 6.5中。
我想计算有多少用户在我的商店中进行了特定数量的购买-一到三。我们将基于这样的名为user_purchases
的文档进行搜索:
{
"user_id" : 1,
"total_value" : 111.56,
"total_products" : 2
}
如果要使用SQL进行此操作,我们将执行以下操作:
SELECT COUNT(u.id) FROM users u WHERE u.id IN (SELECT up.user_id FROM user_purchases up WHERE COUNT(up.id) = 1 GROUP BY up.user_id);
SELECT COUNT(u.id) FROM users u WHERE u.id IN (SELECT up.user_id FROM user_purchases up WHERE COUNT(up.id) = 3 GROUP BY up.user_id);
我已经使用ElasticSearch已有一段时间了,但是这种情况让我头疼不已。我试图找到一些类似的案例,但google合作不力,并且ES文档没有太多示例可以弄清楚。
似乎最好对每个案例进行三个单独的查询-一个或三个案例,并可能使用无痛+ ctx脚本,但我只是不知道从哪里开始。我用它做了一些不太复杂的操作,“如果包含此操作,然后执行该操作”,但这将花费我很长的时间。
如果任何人都可以使用search或multisearch API(或其他任何可行的API)提供帮助并给出查询示例,我将非常感激!
答案 0 :(得分:1)
如果您在user_id
字段上运行terms
aggregation,则将获得购买最多的用户到仅购买最多的用户的存储桶列表一次购买。然后,您可以使用bucket_selector
pipeline aggregation过滤掉所有不需要的存储桶:
{
"size": 0,
"aggs": {
"users": {
"terms": {
"field": "user_id"
},
"aggs": {
"1_3": {
"bucket_selector": {
"buckets_path": {
"nb_purchases": "_count"
},
"script": "params.nb_purchases == 1 || params.nb_purchases == 3"
}
}
}
}
}
}
答案 1 :(得分:0)
这是我设法完成所需工作的方式。假设我们有一个名为visitor_carts
的索引,其中包含诸如此类的文档:
{
"visitor_id" : 1,
"total_value" : 111,
"total_products" : 2
}
{
"visitor_id" : 1,
"total_value" : 199.99,
"total_products" : 1
}
{
"visitor_id" : 1,
"total_value" : 890.56,
"total_products" : 2
}
{
"visitor_id" : 2,
"total_value" : 223.56,
"total_products" : 2
}
{
"visitor_id" : 3,
"total_value" : 4.56,
"total_products" : 2
}
有一个解决方案-称为scripted metric aggregation。有了它,您几乎可以构建任何想要的东西,缺点是您必须熟悉painless脚本。在这方面的文档很难理解,最重要的是,似乎特定版本的维护不佳,因为无痛文档中的内容不适用于我的ElasticSearch 6.5版本(尽管它应该根据所述文档)。因此请注意-如果不起作用,请继续寻找更多示例。我发现usage examples here非常有用。无论如何,这里是有效的解决方案:
POST visitor_carts/_search
{
"query" : {
"match_all" : {}
},
"aggs": {
"purchases": {
"scripted_metric": {
"init_script" : "state['visitorPurchases'] = [:]",
"map_script" : "if (state['visitorPurchases'].containsKey(doc['visitor_id'].value)) {state['visitorPurchases'][doc['visitor_id'].value]++} else {state['visitorPurchases'][doc['visitor_id'].value] = 1}",
"combine_script": "def combine = [:]; for (visitor in state['visitorPurchases'].entrySet()) {if (combine.containsKey(visitor.getValue().toString())) {combine[visitor.getValue().toString()]++} else {combine[visitor.getValue().toString()] = 1}} return combine",
"reduce_script": "def reduce = [:]; for (shard in states) { for (count in shard.entrySet()) {if (reduce.containsKey(count.getKey())) {reduce[count.getKey()] += count.getValue()} else {reduce[count.getKey()] = count.getValue()}}} return reduce"
}
}
}
}
在map_script
中,它查询与查询匹配的所有文档,并计数每个visitor_id
的出现次数。然后在combine_script
中,使用map_script
之前准备的内容,并按出现次数对结果分组。由于combine_script
每个分片都有效,因此我们需要让reduce_script
将每个分片的所有结果集汇总在一起,然后像以下那样返回它:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 5,
"max_score" : 0.0,
"hits" : [ ]
},
"aggregations" : {
"purchases" : {
"value" : {
"1" : 2,
"3" : 1
}
}
}
}
请参阅scripted metric aggregation文档,找出每种脚本类型的作用,然后按照usage examples here构造所需的内容。
我对ElasticSearch太陌生,以至于无法断定该解决方案的效率。它与我测试过的几千个文档一起使用时效果很好,但我不知道它在成千上万的记录中的表现如何。如果有人想测试一下-请成为我的客人:)