是否建议使用MapReduce来“扁平化”#39; CouchDB中的不规则实体?

时间:2017-04-24 20:34:55

标签: join mapreduce couchdb

在我之前询问的CouchDB问题(Can you implement document joins using CouchDB 2.0 'Mango'?)中,答案提到了创建域对象而不是在Couch中存储关系数据。

然而,我的用例不一定是在Couch中存储关系数据,而是为了压缩关系数据。例如,我有几个供应商收集的Invoice实体。所以我有两个不同的模式用于该实体。

所以我最终可能会在Couch中找到2个文档:

{
    "type": "Invoice",
    "subType": "supplier B",
    "total": 22.5,
    "date": "10 Jan 2017",
    "customerName": "me"
}

{
    "type": "Invoice",
    "subType": "supplier A",
    "InvoiceTotal": 10.2,
    "OrderDate": <some other date format>,
    "customerName": "me"
}

我也有这样的文档:

{
    "type": "Customer",
    "name": "me",
    "details": "etc..."
}

我的意图是“平坦化”&#39; Invoice实体,然后加入reduce函数。因此,map函数如下所示:

function(doc) {
    switch(doc.type) {
        case 'Customer':
            emit(doc.customerName, { doc information ..., type: "Customer" });
            break;
        case 'Invoice':
            switch (doc.subType) {
                case 'supplier B':
                    emit (doc.customerName, { total:  doc.total, date: doc.date, type: "Invoice"});
                    break;

                case 'supplier A':
                    emit (doc.customerName, { total:  doc.InvoiceTotal, date: doc.OrderDate, type: "Invoice"});
                    break;
            }
            break;
    }
}

然后我会使用reduce函数来比较具有相同customerName(即连接)的文档。

这是否可以使用CouchDB?如果没有,为什么?

2 个答案:

答案 0 :(得分:0)

首先为迟到的事道道歉,我以为我直接看了它,但是自从我们前几天交换以来,我还没有去过。

Reduce函数只应用于减少标量值,而不是用于聚合数据。因此,您不会使用它们来实现诸如连接或删除重复项之类的事情,但您可以使用它们来计算每个客户的发票数量 - 您会看到这个想法。原因是你只能对你的reduce函数调用(传递记录的顺序,rereduce参数等等)做出弱假设,这样你就很容易遇到严重的性能问题。

但这是设计的,因为reduce函数的预期用途是减少标量值。考虑它的一个简单方法是说在reduce函数中不应该发生过滤,过滤和检查键之类的事情应该在map中完成。

如果您只想比较具有相同客户名称的文档,则根本不需要reduce函数,您可以查询视图中的以下参数:

startkey=["customerName"]
endkey=["customerName", {}]

否则,您可能需要先创建一个单独的视图来过滤客户,然后返回其名称,然后使用这些名称使用keys view parameter批量查询您的视图。如果您只想一次过滤一个客户,和/或需要以部分方式匹配复杂密钥,则Startkey / endkey是好的。

如果您追求的是数字,您可能想要这样做:

if(doc.type == "Invoice") {
    emit([doc.customerName, doc.supplierName, doc.date], doc.amount)
}

然后使用the _stats built-in reduce function获取金额(总和,最小,最大值)的统计数据

为了获得供应商花费的金额,您只需要对视图进行简化查询,并使用参数group_level = 2按密钥的前2个元素进行聚合。您可以将其与startkey和endkey结合使用,以过滤此键的特定值:

startkey=["name1", "supplierA"]
endkey=["name1", "supplierA", {}]

然后,您可以根据此示例构建以执行以下操作:

if(doc.type == "Invoice") {
    emit(["BY_DATE", doc.customerName, doc.date], doc.amount);
    emit(["BY_SUPPLIER", doc.customerName, doc.supplierName], doc.amount);
    emit(["BY_SUPPLIER_AND_DATE", doc.customerName, doc.supplierName, doc.date], doc.amount);
}

希望这有帮助

答案 1 :(得分:0)

通过视图“规范化”不同的模式(或subTypes)是完全可以的。但是,您无法基于这些规范化模式创建视图,并且从长远来看,可能很难管理不同的模式。

更好的解决方案可能是在将文档写入CouchDB之前对文档进行规范化。如果您仍需要原始模式中的文档,则可以添加子属性original,以便将文档存储为原始格式。这样可以更轻松地处理数据:

{
  "type": "Invoice",
  "total": 22.5,
  "date": "2017-01-10T00:00:00.000Z",
  "customerName": "me",
  "original": {
    "supplier": "supplier B",
    "total": 22.5,
    "date": "10 Jan 2017",
    "customerName": "me"
  }
},

{
  "type": "Invoice",
  "total": 10.2,
  "date": "2017-01-12T00:00:00:00.000Z,
  "customerName": "me",
  "original": {
    "subType": "supplier A",
    "InvoiceTotal": 10.2,
    "OrderDate": <some other date format>,
    "customerName": "me"
  }
}

我还将日期转换为ISO格式,因为它与new Date()一起解析,正确排序并且是人类可读的。您可以轻松发出按年,月,日等分组的发票。

最好只使用内置函数使用reduce,因为必须在查询上重新执行reduce,并且在许多文档上执行JavaScript是一项复杂且耗时的操作,即使数据库根本没有更改。您可以在CouchDB process中找到有关减少流程的更多信息。在将文档存储到CouchDB中之前,尽可能多地预处理文档更有意义。