1

我想过滤一个列表并根据聚合对其进行排序;在 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”时)。这很容易查询,而且速度很快。但似乎索引的磁盘大小将呈指数增长(随着不同过滤字段的数量)。而且对于开放式数据(例如日期范围)进行过滤似乎完全站不住脚。

最后,我可以动态生成处理所需过滤器的视图,仅在需要的基础上,并在不再使用它们后将其拆除(以节省磁盘空间)。这里的缺点是代码复杂性的巨大飞跃,并且每次选择新过滤器时都会产生巨大的前期成本。

有没有更好的办法?

4

1 回答 1

0

我已经考虑了将近一天,我认为没有比你提出的更好的方法了。您面临的挑战如下:

1) 聚合工作(计数、求和等)只能通过物化视图引擎(mapreduce)在 CouchDB/Cloudant API 中完成。

2) 虽然 group_level API 提供了一些在查询时指定可变粒度的灵活性,但它对于任意布尔查询不够灵活。

3) Cloudant API 中可以通过基于 lucene 的 _search API 进行任意布尔查询。但是,_search API 不支持聚合查询。对您想做的事情的有限支持只能在 lucene 中使用 faceting,而 Cloudant 尚不支持。即使那样,我相信它可能只支持count而不支持sum或更复杂的聚合。

我认为您面临的最佳选择是使用 _search API 并使用 sort、group_by 或 group_sort,然后在客户端上进行聚合。一些要测试的示例 URL 如下所示:

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"]

于 2013-04-04T18:24:21.490 回答