You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 14:00:34