"合并"视图排序到CouchDB中的有用输出

Mat*_*att 6 reduce couchdb mapreduce

在CouchDB中执行"连接"时,可以使用视图排序将记录组合​​在一起.例如,有两种文档类型的客户订单.这样您就可以返回客户,然后是该客户的所有订单,然后是下一个客户和订单.

问题是,如何合并行,这样如果您有10个客户和40个订单,那么您的输出仍然是10行而不是50行.您实际上是在客户行中添加了更多信息.

我相信用一个_list或一个reduce会解决这个问题.问题是如何做到这一点?

Mar*_*cio 5

我是第二个jhs回答,但我认为他的"选项2"太危险了.我很难学到它.你可以使用reduce函数来获取许多好东西,例如获取博客的每个用户的最后一篇文章,但是你不能将它用于任何不减少返回数据量的东西.

为了支持事实,我制作了这个小脚本来生成200个客户,每个客户有20个订单.

#!/bin/bash
echo '{"docs":['
for i in $(seq 1 200); do
  id=customer/$i
  echo '{"_id":"'$id'","type":"customer","name":"Customer '$i'"},'
  for o in $(seq 1 20); do
    echo '{"type":"order","_id":"order/'$i'/'$o'", "for":"'$id'", "desc":"Desc '$i$o'"},'
  done
done
echo ']}'
Run Code Online (Sandbox Code Playgroud)

这是一个非常可能的情况,它足以抛出一个Error: reduce_overflow_error.

恕我直言,你有两个选择:

选项1:优化列表功能

通过一些工作,您可以手动构建JSON响应,这样您就不需要在数组中累积订单.

我已经编辑了jhs的list函数以避免使用任何数组,因此您可以让客户拥有任意数量的订单.

function(head, req) {
  start({'headers':{'Content-Type':'application/json'}});

  var first_customer = true
    , first_order = true
    , row
    ;

  send('{"rows":[');

  while(row = getRow()) {
    if(row.key[1] === 2) {
      // Order for customer
      if (first_order) {
        first_order = false;
      } else {
        send(',');
      }
      send(JSON.stringify(row.value));
    }
    else if (row.key[1] === 1) {
      // New customer
      if (first_customer) {
        first_customer = false;
      } else {
        send(']},');
      }
      send('{"customer":');
      send(JSON.stringify(row.key[0]));
      send(',"orders":[');
      first_order = true;
    }
  }
  if (!first_customer)
    send(']}');

  send('\n]}');
}
Run Code Online (Sandbox Code Playgroud)

选项2:针对您的用例优化文档

如果您确实需要在同一文档中拥有订单,那么问问自己是否可以这样存储它并避免在查询时进行任何处理.

换句话说:尝试充分利用文档数据库提供的可能性.设计最适合您的用例的文档,并减少使用它们所需的后处理.


Jas*_*ith 2

CouchDB 的主要“观点”之一是它只做在分布式、集群环境中也可能发生的事情。实际上,这在开始时意味着一些不便,但稍后会在不更改代码的情况下获得高可扩展性的回报。

换句话说,“加入”问题没有完美的答案。但我认为有两个非常好的选择。

我正在使用这个数据集:

$ curl localhost:5984/so/_bulk_docs -XPOST -Hcontent-type:application/json -d @-
{"docs":[
{"type":"customer","name":"Jason"},
{"type":"customer","name":"Hunter"},
{"type":"customer","name":"Smith"},
{"type":"order", "for":"Jason", "desc":"Hat"},
{"type":"order", "for":"Jason", "desc":"Shoes"},
{"type":"order", "for":"Smith", "desc":"Pan"}
]}
^D

[{"id":"4cb766ebafda06d8a3a7382f74000b46","rev":"1-8769ac2fffb869e795c347e7b8c653bf"},
{"id":"4cb766ebafda06d8a3a7382f74000b7d","rev":"1-094eff3e3a5967d974fcd7b3cfd7e454"},
{"id":"4cb766ebafda06d8a3a7382f740019cb","rev":"1-5cda0b61da4c045ff503b57f614454d5"},
{"id":"4cb766ebafda06d8a3a7382f7400239d","rev":"1-50642a9809f15283a9d938c8fe28ef27"},
{"id":"4cb766ebafda06d8a3a7382f74002778","rev":"1-d03d883fb14a424e3db022350b38c510"},
{"id":"4cb766ebafda06d8a3a7382f74002c5c","rev":"1-e9612f5d267a8442d3fc2ae09e8c800d"}]
Run Code Online (Sandbox Code Playgroud)

我的地图功能是

function(doc) {
  if(doc.type == 'customer')
    emit([doc.name, 1], "");
  if(doc.type == 'order')
    emit([doc.for, 2], doc.desc);
}
Run Code Online (Sandbox Code Playgroud)

查询全视图显示:

{"total_rows":6,"offset":0,"rows":[
{"id":"4cb766ebafda06d8a3a7382f74000b7d","key":["Hunter",1],"value":""},
{"id":"4cb766ebafda06d8a3a7382f74000b46","key":["Jason",1],"value":""},
{"id":"4cb766ebafda06d8a3a7382f7400239d","key":["Jason",2],"value":"Hat"},
{"id":"4cb766ebafda06d8a3a7382f74002778","key":["Jason",2],"value":"Shoes"},
{"id":"4cb766ebafda06d8a3a7382f740019cb","key":["Smith",1],"value":""},
{"id":"4cb766ebafda06d8a3a7382f74002c5c","key":["Smith",2],"value":"Pan"}
]}
Run Code Online (Sandbox Code Playgroud)

选项 1:用于收集结果的列表函数

好处是,如果你要求 10 行,你肯定会得到 10 行(当然,除非没有足够的数据)。

但代价是你必须对每个查询进行服务器端处理。您想要的数据只是位于磁盘上,准备好流式传输给您,但现在您已经让它通过了这个瓶颈。

然而,我个人认为除非你有明显的性能问题,否则这_list很好。

function(head, req) {
  start({'headers':{'Content-Type':'application/json'}});

  send('{"rows":');

  var customer = null, orders = [], count = 0;

  var prefix = '\n[ ';
  function show_orders() {
    if(customer && orders.length > 0) {
      count += 1;

      send(prefix);
      prefix = '\n, ';

      send(JSON.stringify({'customer':customer, 'orders':orders}));
    }
  }

  function done() {
    send('\n]}');
  }

  var row;
  while(row = getRow()) {
    if(row.key[1] == 2) {
      // Order for customer
      orders.push(row.value);
    }

    if(row.key[1] == 1) {
      // New customer
      show_orders();

      if(req.query.lim && count >= parseInt(req.query.lim)) {
        // Reached the limit
        done();
        return;
      } else {
        // Prepare for this customer.
        customer = row.key[0];
        orders = [];
      }
    }
  }

  // Show the last order set seen and finish.
  show_orders();
  done();
}
Run Code Online (Sandbox Code Playgroud)

该函数只是循环遍历行map,并且仅在收集所有信息后输出完整的客户+订单行。显然,您可以更改输出的 JSON 格式。另外,还有一个?lim=X参数,因为使用参数limit会干扰地图查询。

危险在于该函数会在内存中建立无限的响应。如果客户下了 10,000 个订单怎么办?还是十万?最终构建orders阵列将会失败。这就是 CouchDB 将它们保留在“高”列表中的原因。如果您的每位客户永远不会收到 10,000 个订单,那么这不是问题。

$ curl 'http://localhost:5984/so/_design/ex/_list/ex/so?reduce=false&lim=2'
{"rows":
[ {"customer":"Jason","orders":["Hat","Shoes"]}
, {"customer":"Smith","orders":["Pan"]}
]}
Run Code Online (Sandbox Code Playgroud)

选项2:偷偷减少

您可以使用函数执行类似的操作reduce。在这里,我会警告您,这在技术上是不可扩展的,因为您在磁盘上累积了响应,但我个人更喜欢它而不是 _list ,因为代码更简单,而且我知道我直接从磁盘读取数据,无需进行后期处理。

function(keys, vals, re) {
  // If all keys are the same, then these are all
  // orders for the same customer, so accumulate
  // them. Otherwise, return something meaningless.
  var a;

  var first_customer = keys[0][0][0];
  for(a = 0; a < keys.length; a++)
    if(keys[a][0][0] !== first_customer)
      return null;

  var result = [];
  for(a = 0; a < vals.length; a++)
    if(vals[a]) {
      // Accumulate an order.
      result.push(vals[a]);
    }
  return result;
}
Run Code Online (Sandbox Code Playgroud)

始终查询此视图,该视图?group_level=1将按客户对结果进行分段(因为客户名称是键中的第一项map)。

这是违法的,因为您不应该在归约阶段积累数据。这就是为什么他们称之为“reduce”

然而,CouchDB 很轻松,只要您不构建巨大的列表,它就应该可以工作,而且更加优雅。

$ curl 'localhost:5984/so/_design/ex/_view/so?group_level=1&limit=3'
{"rows":[
{"key":["Hunter"],"value":[]},
{"key":["Jason"],"value":["Shoes","Hat"]},
{"key":["Smith"],"value":["Pan"]}
]}
Run Code Online (Sandbox Code Playgroud)

祝你好运!

  • 共产主义值得理解,但不值得实行。也许偷偷减少也是一样的。 (7认同)