弹性搜索中的聚合和排序
aggregating and sorting in elasticsearch
最近我开始使用ElasticSearch,我打算为我正在构建的服务坚持使用它。
基本上我有以下几种:
- 搜索
- 优惠
- 优惠价格
每个搜索都有一组信息加上 SID(搜索 ID),每个报价都有一个 OID(报价 ID)加上搜索的 SID 和一组价格。
我异步接收数据,以避免使用 _update,而不是在报价中包含价格数组并更新它,每个价格都存储在一个单独的文档中,并包含搜索 ID、报价 ID 和价格本身。
我愿意:
- 按 SID 筛选
- 按 OID 聚合
- 按价格对聚合进行排序
我该怎么做?有什么提示吗?我正在阅读有关如何聚合的文档,但我完全不知道:(
编辑:
这里有一个示例数据集
SEARCHES(uuid 是 sid)
{
'sid_1': { 'q': 'bread', 'sid': 'sid_1' },
'sid_2': { 'q': 'milk', 'sid': 'sid_2' },
'sid_3': { 'q': 'donuts', 'sid': 'sid_3' }
}
OFFERS(uuid 是 sid#oid)
{
'sid_1#kamut-bread': { 'name': 'kamut bread', 'sid': 'sid_1', 'oid': 'kamut-bread' },
'sid_1#chocolate-bread': { 'name': 'chocolate bread', 'sid': 'sid_1', 'oid': 'chocolate-bread' },
'sid_1#plastic-bread': { 'name': 'plastic bread', 'sid': 'sid_1', 'oid': 'plastic-bread' },
'sid_2#soya-milk': { 'name': 'soya milk', 'sid': 'sid_2', 'oid': 'soya-milk' },
'sid_2#vaccine-milk': { 'name': 'vaccine milk', 'sid': 'sid_2', 'oid': 'vaccine-milk' },
'sid_2#milk': { 'name': 'milk', 'sid': 'sid_2', 'oid': 'milk' },
'sid_3#cream-donuts': { 'name': 'cream donuts', 'sid': 'sid_3', 'oid': 'cream-donuts' },
'sid_3#chocolate-donuts': { 'name': 'chocolate donuts', 'sid': 'sid_3', 'oid': 'chocolate-donuts' },
'sid_3#square-donuts': { 'name': 'square donuts', 'sid': 'sid_3', 'oid': 'square-donuts' }
}
OFFERS_PRICES(uuid 是 sid#oid#partner)
{
'sid_1#kamut-bread#amazon': { 'partner': 'amazon', 'sid': 'sid_1', 'oid': 'kamut-bread', 'price': 10.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#kamut-bread#store2': { 'partner': 'store2', 'sid': 'sid_1', 'oid': 'kamut-bread', 'price': 11.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#kamut-bread#store3': { 'partner': 'store3', 'sid': 'sid_1', 'oid': 'kamut-bread', 'price': 10.4, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#kamut-bread#store4': { 'partner': 'store4', 'sid': 'sid_1', 'oid': 'kamut-bread', 'price': 10.8, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#chocolate-bread#amazon': { 'partner': 'amazon', 'sid': 'sid_1', 'oid': 'chocolate-bread', 'price': 7.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#chocolate-bread#store2': { 'partner': 'store2', 'sid': 'sid_1', 'oid': 'chocolate-bread', 'price': 7.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#chocolate-bread#store3': { 'partner': 'store3', 'sid': 'sid_1', 'oid': 'chocolate-bread', 'price': 8.4, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#chocolate-bread#store4': { 'partner': 'store4', 'sid': 'sid_1', 'oid': 'chocolate-bread', 'price': 9.8, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#plastic-bread#amazon': { 'partner': 'amazon', 'sid': 'sid_1', 'oid': 'plastic-bread', 'price': 70.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#plastic-bread#store2': { 'partner': 'store2', 'sid': 'sid_1', 'oid': 'plastic-bread', 'price': 75.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#plastic-bread#store3': { 'partner': 'store3', 'sid': 'sid_1', 'oid': 'plastic-bread', 'price': 88.4, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#plastic-bread#store4': { 'partner': 'store4', 'sid': 'sid_1', 'oid': 'plastic-bread', 'price': 97.8, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } }
...
}
出于性能原因,代码不会聚合数据,而是 return 它们分开(搜索、优惠和优惠价格),前端会聚合它们,将允许我(几乎)直接从 elastic 流式传输数据,而无需预先阐述它们。
提取搜索和报价后,我想要:
- 提取 SID 的价格 sid_1
- 按 OID 对价格进行分组
- 按价格(或按价格 + 特定费用,但我可以用 groovy 处理)
我发现了聚合类型 scripted_metric 的存在,并且在使用它之后我想出了这个查询
{
"size": 0,
"query" : {
"match_all" : {}
},
"aggs": {
"offer_prices": {
"scripted_metric": {
"init_script" : "_agg[\"offers_prices\"] = [:].withDefault{[:]}",
"map_script" : "def parent = doc._parent.value; def partner = doc.partner.value; def price = doc.price.value; if (!_agg.offers_prices.containsKey(parent)) { _agg.offers_prices[parent] = [ parent: parent, sid: doc.sid.value, oid: doc.oid.value, bestPrice: Double.MAX_VALUE, bestPartner: null, partners: [:] ]; }; _agg.offers_prices[parent].partners[partner] = [ \"partner\": partner, \"price\": price, \"ccfees\": _source.ccfees ]; if (_agg.offers_prices[parent].bestPrice > price) { _agg.offers_prices[parent].bestPrice = price; _agg.offers_prices[parent].bestPartner = partner; }",
"combine_script" : "return _agg.offers_prices;",
"reduce_script" : "def offers_prices_all = [:]; _aggs.each { offers_prices_per_shard -> offers_prices_per_shard.each { oid, offers_prices -> offers_prices_all[oid] = offers_prices}; }; offers_prices_all = offers_prices_all.sort { a, b -> a.value.bestPrice <=> b.value.bestPrice }; return offers_prices_all;"
}
}
}
}
这不是最终版本,我必须做一些修复,我必须测试性能,但这似乎是一个可能的解决方案:
- 查询使用 _parent
对数据进行分组
- 计算聚合的最佳价格
- 按最佳价格对聚合进行排序
还有待办事项:
- 按最佳价格 + 费用对聚合进行排序
- 单个聚合的合作伙伴列表按价格排序
- 测试性能和资源消耗
注:
- 我已经添加了 _parent 映射,我正在使用文档的 _parent 属性 对数据进行分组,但是可以手动构建它连接 sid 和 oid
- 该脚本使用 属性 ccfees 但在我上面发布的示例数据集中它被称为费用
最近我开始使用ElasticSearch,我打算为我正在构建的服务坚持使用它。
基本上我有以下几种:
- 搜索
- 优惠
- 优惠价格
每个搜索都有一组信息加上 SID(搜索 ID),每个报价都有一个 OID(报价 ID)加上搜索的 SID 和一组价格。
我异步接收数据,以避免使用 _update,而不是在报价中包含价格数组并更新它,每个价格都存储在一个单独的文档中,并包含搜索 ID、报价 ID 和价格本身。
我愿意:
- 按 SID 筛选
- 按 OID 聚合
- 按价格对聚合进行排序
我该怎么做?有什么提示吗?我正在阅读有关如何聚合的文档,但我完全不知道:(
编辑:
这里有一个示例数据集
SEARCHES(uuid 是 sid)
{
'sid_1': { 'q': 'bread', 'sid': 'sid_1' },
'sid_2': { 'q': 'milk', 'sid': 'sid_2' },
'sid_3': { 'q': 'donuts', 'sid': 'sid_3' }
}
OFFERS(uuid 是 sid#oid)
{
'sid_1#kamut-bread': { 'name': 'kamut bread', 'sid': 'sid_1', 'oid': 'kamut-bread' },
'sid_1#chocolate-bread': { 'name': 'chocolate bread', 'sid': 'sid_1', 'oid': 'chocolate-bread' },
'sid_1#plastic-bread': { 'name': 'plastic bread', 'sid': 'sid_1', 'oid': 'plastic-bread' },
'sid_2#soya-milk': { 'name': 'soya milk', 'sid': 'sid_2', 'oid': 'soya-milk' },
'sid_2#vaccine-milk': { 'name': 'vaccine milk', 'sid': 'sid_2', 'oid': 'vaccine-milk' },
'sid_2#milk': { 'name': 'milk', 'sid': 'sid_2', 'oid': 'milk' },
'sid_3#cream-donuts': { 'name': 'cream donuts', 'sid': 'sid_3', 'oid': 'cream-donuts' },
'sid_3#chocolate-donuts': { 'name': 'chocolate donuts', 'sid': 'sid_3', 'oid': 'chocolate-donuts' },
'sid_3#square-donuts': { 'name': 'square donuts', 'sid': 'sid_3', 'oid': 'square-donuts' }
}
OFFERS_PRICES(uuid 是 sid#oid#partner)
{
'sid_1#kamut-bread#amazon': { 'partner': 'amazon', 'sid': 'sid_1', 'oid': 'kamut-bread', 'price': 10.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#kamut-bread#store2': { 'partner': 'store2', 'sid': 'sid_1', 'oid': 'kamut-bread', 'price': 11.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#kamut-bread#store3': { 'partner': 'store3', 'sid': 'sid_1', 'oid': 'kamut-bread', 'price': 10.4, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#kamut-bread#store4': { 'partner': 'store4', 'sid': 'sid_1', 'oid': 'kamut-bread', 'price': 10.8, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#chocolate-bread#amazon': { 'partner': 'amazon', 'sid': 'sid_1', 'oid': 'chocolate-bread', 'price': 7.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#chocolate-bread#store2': { 'partner': 'store2', 'sid': 'sid_1', 'oid': 'chocolate-bread', 'price': 7.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#chocolate-bread#store3': { 'partner': 'store3', 'sid': 'sid_1', 'oid': 'chocolate-bread', 'price': 8.4, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#chocolate-bread#store4': { 'partner': 'store4', 'sid': 'sid_1', 'oid': 'chocolate-bread', 'price': 9.8, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#plastic-bread#amazon': { 'partner': 'amazon', 'sid': 'sid_1', 'oid': 'plastic-bread', 'price': 70.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#plastic-bread#store2': { 'partner': 'store2', 'sid': 'sid_1', 'oid': 'plastic-bread', 'price': 75.1, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#plastic-bread#store3': { 'partner': 'store3', 'sid': 'sid_1', 'oid': 'plastic-bread', 'price': 88.4, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } },
'sid_1#plastic-bread#store4': { 'partner': 'store4', 'sid': 'sid_1', 'oid': 'plastic-bread', 'price': 97.8, 'fees': { 'mastercard': 1, 'visa': 1, 'paypal': 2, 'wiretransfer': 0 } }
...
}
出于性能原因,代码不会聚合数据,而是 return 它们分开(搜索、优惠和优惠价格),前端会聚合它们,将允许我(几乎)直接从 elastic 流式传输数据,而无需预先阐述它们。
提取搜索和报价后,我想要:
- 提取 SID 的价格 sid_1
- 按 OID 对价格进行分组
- 按价格(或按价格 + 特定费用,但我可以用 groovy 处理)
我发现了聚合类型 scripted_metric 的存在,并且在使用它之后我想出了这个查询
{
"size": 0,
"query" : {
"match_all" : {}
},
"aggs": {
"offer_prices": {
"scripted_metric": {
"init_script" : "_agg[\"offers_prices\"] = [:].withDefault{[:]}",
"map_script" : "def parent = doc._parent.value; def partner = doc.partner.value; def price = doc.price.value; if (!_agg.offers_prices.containsKey(parent)) { _agg.offers_prices[parent] = [ parent: parent, sid: doc.sid.value, oid: doc.oid.value, bestPrice: Double.MAX_VALUE, bestPartner: null, partners: [:] ]; }; _agg.offers_prices[parent].partners[partner] = [ \"partner\": partner, \"price\": price, \"ccfees\": _source.ccfees ]; if (_agg.offers_prices[parent].bestPrice > price) { _agg.offers_prices[parent].bestPrice = price; _agg.offers_prices[parent].bestPartner = partner; }",
"combine_script" : "return _agg.offers_prices;",
"reduce_script" : "def offers_prices_all = [:]; _aggs.each { offers_prices_per_shard -> offers_prices_per_shard.each { oid, offers_prices -> offers_prices_all[oid] = offers_prices}; }; offers_prices_all = offers_prices_all.sort { a, b -> a.value.bestPrice <=> b.value.bestPrice }; return offers_prices_all;"
}
}
}
}
这不是最终版本,我必须做一些修复,我必须测试性能,但这似乎是一个可能的解决方案:
- 查询使用 _parent 对数据进行分组
- 计算聚合的最佳价格
- 按最佳价格对聚合进行排序
还有待办事项:
- 按最佳价格 + 费用对聚合进行排序
- 单个聚合的合作伙伴列表按价格排序
- 测试性能和资源消耗
注:
- 我已经添加了 _parent 映射,我正在使用文档的 _parent 属性 对数据进行分组,但是可以手动构建它连接 sid 和 oid
- 该脚本使用 属性 ccfees 但在我上面发布的示例数据集中它被称为费用