【问题标题】:CouchDB: Querying Many-To-Many "Relationship"CouchDB:查询多对多“关系”
【发布时间】:2012-03-29 15:25:54
【问题描述】:

鉴于我有以下产品:

{
   "_id": "2666df80782596200fca49557d757870",
   "_rev": "3-99382057f6c484526835f1042753ccf2",
   "type": "product",
   "name": "Shirt",
   "hersteller": "oska",
   "price": 11.15
}

{
   "_id": "2666df80782596200fca49557d758e8c",
   "_rev": "1-01cc88e69e5ff30f0d011fdf61fbedbc",
   "type": "product",
   "name": "Pullover",
   "hersteller": "acme",
   "price": 7.58
}

还有订单

{
   "_id": "2666df80782596200fca49557d758228",
   "_rev": "2-0b4b3a8605893b60c962b8ae78f0b775",
   "type": "order",
   "orderDate": "01.01.2012",
   "positions": [
       "2666df80782596200fca49557d757870",
       "2666df80782596200fca49557d758e8c"
   ]
}

我想查询所有带有_id、orderDate 和所有仓位价格总和的订单。 我该如何处理?

例子:

[
 { "_id": "2666df80782596200fca49557d758228", "orderDate": "01.01.2012", "total": 18.73 }
]

编辑:
这个例子是虚构的,我知道订单的总额不应该改变历史订单的产品价格变化。对于需要真正“加入”的事情来说,这是一个不好的例子。想象一下,当产品的价格发生变化时,我必须更改总数。因此,我不想简单地对总计字段进行非规范化。 当产品发生变化时,“修复”订单中的总数是可行的。但是,当我有很多不同的文档类型引用产品时,我需要使用一些功能逐个类型地更改它们。 肖恩的回答让我想到了我可以使用更改侦听器修复非规范化字段,但每次某些文档类型与另一个文档类型相关时,我都需要一个侦听器 - 听起来需要做很多工作。

【问题讨论】:

  • 嗨瑞恩,感谢您的评论。我以前知道这一点,我想我已经阅读了关于加入的任何 SO 主题:-) 你链接的那个是关于“一对多连接”的,我要求“多对多”连接。我知道我可以 ?include_docs=true 在当前的 couchdb 版本中,这将有效地获取订单的相关产品,但我不能减少结果来计算总数。我看不出 reduce 在这里不起作用的原因。
  • 好的,再次阅读stackoverflow.com/questions/3033443/… 意味着它不是正在讨论的“一对多连接”。我想我缺少的是这里的显示功能。我不明白为什么我仍然不能使用 reduce :-)
  • 最后一条评论的意思是“列表函数”

标签: couchdb


【解决方案1】:

我相信最好的方法是拥有一个 _changes 监听器,当记录变为历史记录(即只读)时,它会使用每个产品的价格值更新订单文档。过去订单的价格可能会发生变化似乎是错误的,如果不通过兔子洞价格日期范围,您就无法打印出正确的报告/发票......

要解决您所说的问题,如果您有一些可以使用 JSON 的客户端代码,Linked Documents 下指出的技巧可能会有所帮助。给定一个地图函数,如:

function(doc) {
 if(doc.type == 'order') {
  for(var position in doc.positions) emit(doc._id,{_id: doc.positions[position]});
 }
}

您可以使用 ?include_docs=true&key="2666df80782596200fca49557d758228" 来返回包含价格在内的订单示例。

不幸的是,您不能将 include_docs 与 reduce 一起使用,因此这可能是我提到的 _changes 守护进程的一个很好的起点,可以创建一个看起来像您想要的最终答案的订单摘要文档...


[编辑添加解决方案] 嗯,我找到了一种方法,但它并不漂亮。

给定具有此视图地图功能的设计文档:

function(doc) {
 if(doc.type == 'order') {
  for(var p in doc.positions) emit(doc.orderDate, {_id: doc.positions[p]});
 }
}

还有这个列表函数:

function(head, req) {
 var count = 0;
 var dates = {};
 var totals = {};

 while(r = getRow()) {
  if(!(r.id in dates)) {
   count += 1;
   dates[r.id] = r.key;
   totals[r.id] = 0; 
  }
  totals[r.id] += r.doc.price;
 }
 start({'headers': {'Content-Type': 'application/json'}});
 send('[\n');
 for(var order in dates) {
  count -= 1;
  send(JSON.stringify({'_id': order, 'orderDate': dates[order], 'total': totals[order]}));
  send((count > 0)?',\n':'\n');
 }
 send(']\n');
}

你想要的结果是在

/db/_design/[Design Doc Name]/_list/[List Function Name]/[Map Function Name]?include_docs=true

顺便说一句,在这种情况下,关键值是 orderDate...

【讨论】:

  • 你确定你的 emit 会得到这些产品 ID 吗?因为您有一个 if 语句将您的范围缩小到订单,然后您尝试将 id 发出这个圈子。
  • 感谢您的详细解答!我知道有人会说订单的价格将变为历史价格和只读价格:-) 这个例子是虚构的,对于需要真正“加入”的东西来说是一个不好的例子。但是,_changes 侦听器可能是非规范化的一个很好的解决方案 - 当原始文档更改时,它可以遍历我对值进行非规范化的所有文档并更改它们。链接文档的解决方案看起来不错 - 我昨晚在阅读 goo.gl/838Zj 后玩了一下,得到了类似的东西,但没能在这里发帖。稍后再试试你的代码
  • 好的,我可以确认这是一个解决方案。每个请求都会调用 list 函数,这并不好。如果我们找到适用于 reduce 的解决方案,结果(“totals”)将存储在 b-tree 中。
  • 如果订单文档将价格复制到了仓位数组中,reduce 将完美运行,并且链接文档的性能也不会受到影响。
  • 并且列表函数被每个键调用,而不是每个订单(即, ?startkey="01.01.2012"&endkey="02.01.2012" 一次就可以工作)。 getRow() 迭代器是循环发生的地方,而不是列表函数。
猜你喜欢
  • 2021-10-03
  • 2018-11-02
  • 2015-10-26
  • 2011-08-08
  • 2015-05-29
  • 2019-10-28
  • 2019-01-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多