我想过滤一个列表并根据聚合对其进行排序;在 SQL 中表达起来相当简单,但我对使用迭代 Map Reduce 的最佳方法感到困惑。我专门在 CouchDB 中使用 Cloudant 的“dbcopy”,但我认为这种方法可能与其他 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有一个很好的示例,可以在 map reduce 的第二遍中按计数进行排序。但是当我也想过滤(例如通过邮箱)时,事情很快就会变得一团糟。
如果我将过滤器添加到视图键(例如最终结果看起来像{"key": ["INBOX", 1234, "foo@example.com"], "value": null}
,那么在单个过滤器值中按计数排序是微不足道的。但是使用多个过滤器按计数对数据进行排序将需要遍历整个数据集(每个键),这在大型数据集上太慢了。
或者我可以为每个潜在的过滤器选择创建一个索引;例如,最终结果看起来像{"key": [["mbox1", "mbox2"], 1234, "foo@example.com"], "value": null},
(对于同时选择“mbox1”和“mbox2”时)或{"key": [["mbox1"], 1234, "foo@example.com"], "value": {...}},
(对于仅选择“mbox1”时)。这很容易查询,而且速度很快。但似乎索引的磁盘大小将呈指数增长(随着不同过滤字段的数量)。而且对于开放式数据(例如日期范围)进行过滤似乎完全站不住脚。
最后,我可以动态生成处理所需过滤器的视图,仅在需要的基础上,并在不再使用它们后将其拆除(以节省磁盘空间)。这里的缺点是代码复杂性的巨大飞跃,并且每次选择新过滤器时都会产生巨大的前期成本。
有没有更好的办法?