我想过滤一个列表并根据聚合对其进行排序;在SQL中表达相当简单的东西,但我对使用迭代Map Reduce的最佳方法感到困惑。我特别使用Cloudant的“dbcopy”添加到CouchDB,但我认为这种方法可能与其他map / reduce架构类似。
伪代码SQL可能如下所示:
SELECT grouping_field, aggregate(*)
FROM data
WHERE #{filter}
GROUP BY grouping_field
ORDER BY aggregate(*), grouping_field
LIMIT page_size
过滤器可能正在寻找匹配,或者可能会在范围内搜索;例如field in ('foo', 'bar')
或field between 37 and 42
。
作为一个具体的例子,考虑一下电子邮件的数据集;分组字段可能是“List-id”,“Sender”或“Subject”;聚合函数可能是count(*)
,或max(date)
或min(date)
; filter子句可能会考虑标志,日期范围或邮箱ID。文件可能如下:
{
"id": "foobar", "mailbox": "INBOX", "date": "2013-03-29",
"sender": "foo@example.com", "subject": "Foo Bar"
}
使用相同的发件人计算电子邮件数量非常简单:
"map": "function (doc) { emit(doc.sender, null) }",
"reduce": "_count"
Cloudant has a good example of sorting by count on the second pass of a map reduce。 但是,当我也想要过滤(例如通过邮箱)时,事情会变得很乱。
如果我将过滤器添加到视图键(例如,最终结果看起来像{"key": ["INBOX", 1234, "foo@example.com"], "value": null}
),那么在单个过滤器值中按count 排序是微不足道的。但是按计数对数据进行排序使用多个过滤器需要遍历整个数据集(每个键),这对大型数据集来说太慢了。
或者我可以为每个潜在的过滤器选择创建一个索引;例如最终结果看起来像{"key": [["mbox1", "mbox2"], 1234, "foo@example.com"], "value": null},
(当选择“mbox1”和“mbox2”时)或{"key": [["mbox1"], 1234, "foo@example.com"], "value": {...}},
(仅当选择“mbox1”时)。这很容易查询,而且速度快。但似乎索引的磁盘大小将呈指数级增长(具有不同过滤字段的数量)。对于开放式数据(例如日期范围)进行过滤似乎完全站不住脚。
最后,我可以动态生成只在需要时动态处理所需过滤器的视图,并在不再使用它们之后将其拆除(以节省磁盘空间)。这里的缺点是代码复杂性大幅增加,每次选择新过滤器时前期成本都很高。
有更好的方法吗?
答案 0 :(得分:0)
近一天我一直在考虑这个问题,我认为没有比你提出的更好的办法了。您面临的挑战如下:
1)聚合工作(计数,总和等)只能通过物化视图引擎(mapreduce)在CouchDB / Cloudant API中完成。
2)虽然group_level API在查询时提供了一些指定变量粒度的灵活性,但它对于任意布尔查询都不够灵活。
3)通过基于lucene的_search API,Cloudant API中可以进行任意布尔查询。但是,_search API不支持聚合 post 查询。对你想要做的事情的有限支持只能在使用faceting的lucene中实现,而Cloudant尚不支持。即使这样,我相信它可能只支持count
而不支持sum
或更复杂的聚合。
我认为您面临的最佳选择是使用_search API并使用sort,group_by或group_sort,然后在客户端上进行聚合。一些要测试的示例网址如下所示:
GET / db / _design / ddoc / _search / indexname?q = name:mike AND age:[1.2 TO 4.5]& sort = [“age”,“name”]
GET / db / _design / ddoc / _search / indexname?q = name:mike AND group_by =“mailbox”AND group_sort = [“age”,“name”]