如何在CouchDB中实现类似SQL窗口函数的分组累计求和
问题
假设CouchDB中有以下文档:
[ { "_id": "1", "date": 1672119898, "amount": 120, "owner": "user_1" }, { "_id": "2", "date": 1672119897, "amount": 25, "owner": "user_1" }, { "_id": "3", "date": 1672119895, "amount": 40, "owner": "user_2" }, { "_id": "4", "date": 1672119899, "amount": 70, "owner": "user_1" }, { "_id": "5", "date": 1672119891, "amount": 33, "owner": "user_1" }, { "_id": "6", "date": 1672119893, "amount": 11, "owner": "user_2" } ]
需要创建一个视图,实现按owner分组、日期升序排序的累计总和计算,最终返回日期降序排列、包含分组累计字段totalAmount的结果,期望输出如下:
[ { "_id": "4", "date": 1672119899, "amount": 70, "owner": "user_1", "totalAmount": 248 // 178 + 70 }, { "_id": "1", "date": 1672119898, "amount": 120, "owner": "user_1", "totalAmount": 178 // 120 + 58 }, { "_id": "2", "date": 1672119897, "amount": 25, "owner": "user_1", "totalAmount": 58 // 25 + 33 }, { "_id": "5", "date": 1672119891, "amount": 33, "owner": "user_1", "totalAmount": 33 }, { "_id": "3", "date": 1672119895, "amount": 40, "owner": "user_2", "totalAmount": 51 // 40 + 11 }, { "_id": "6", "date": 1672119893, "amount": 11, "owner": "user_2", "totalAmount": 11 } ]
解决方案
CouchDB原生reduce函数仅支持全局或分组的聚合计算,无法直接生成每行的累计值,因此需要结合Map函数做排序分组,再通过List函数计算累计并调整顺序,或者在客户端处理结果。
方法一:使用Map+List函数(服务端处理)
1. Map函数
通过Map函数按owner分组,再按date升序排序输出文档:
function(doc) { // 键格式为[owner, date],确保先按owner分组,再按date升序排列 emit([doc.owner, doc.date], { _id: doc._id, date: doc.date, amount: doc.amount, owner: doc.owner }); }
2. List函数
遍历Map的输出结果,计算每个owner分组的累计总和,再反转分组内的文档顺序(转为日期降序):
function(head, req) { const groupedData = {}; let row; // 遍历所有Map输出的行 while (row = getRow()) { const owner = row.key[0]; // 初始化分组数据 if (!groupedData[owner]) { groupedData[owner] = { cumulativeTotal: 0, documents: [] }; } // 累计当前金额 groupedData[owner].cumulativeTotal += row.value.amount; // 给文档添加累计字段 const docWithTotal = Object.assign({}, row.value, { totalAmount: groupedData[owner].cumulativeTotal }); groupedData[owner].documents.push(docWithTotal); } // 整理最终结果:反转每个分组的顺序,合并所有分组 const finalResult = []; Object.values(groupedData).forEach(group => { finalResult.push(...group.documents.reverse()); }); // 返回JSON格式的结果 send(JSON.stringify(finalResult)); }
使用方式
- 创建一个设计文档,将上述Map函数添加到视图中(无需配置Reduce函数)
- 在同一个设计文档中创建List函数,关联到该视图
- 通过类似
/your-db/_design/your-design-doc/_list/your-list-name/your-view-name的路径访问,即可获取期望的输出
方法二:客户端处理(更简单)
如果允许在客户端处理结果,可以只使用Map函数获取排序后的原始数据,再在客户端计算累计并调整顺序:
// 假设已从CouchDB的Map视图获取到rows数组 const viewRows = [...]; // 示例:rows = [{key: ["user_1", 1672119891], value: {...}}, ...] const grouped = {}; viewRows.forEach(row => { const owner = row.key[0]; if (!grouped[owner]) { grouped[owner] = { total: 0, docs: [] }; } grouped[owner].total += row.value.amount; grouped[owner].docs.push({ ...row.value, totalAmount: grouped[owner].total }); }); // 反转每个分组的顺序,合并为最终结果 const finalOutput = Object.values(grouped).flatMap(group => group.docs.reverse());
内容的提问来源于stack exchange,提问作者oyalhi
相关产品推荐
相关产品推荐

