«объединить» представление сортировки в полезный вывод в CouchDB - PullRequest
6 голосов
/ 22 мая 2011

При выполнении «соединения» в CouchDB вы можете использовать сортировку представлений для группировки записей вместе.Например, имея два типа документов клиенты и заказы .Так что вы можете вернуть клиента , затем все заказов для этого клиента, затем следующего клиента и заказы.

Вопрос в том, как вы делаетеобъединение строк, так что если у вас есть 10 клиентов и 40 заказов, вы получите 10 строк вместо 50. По сути, вы добавляете больше информации в строку customer .

Я считаю, что использование _list или reduce решит эту проблему.Вопрос в том, как именно это сделать?

Ответы [ 2 ]

5 голосов
/ 23 мая 2011

Я второй jhs отвечаю , но я думаю, что его "Вариант 2" слишком опасен. Я научился этому нелегко. Вы можете использовать функцию Reduction для многих полезных вещей, таких как получение последнего сообщения каждого пользователя блога , но вы не можете использовать его для чего-либо, что не уменьшает количество возвращаемых данных.

Чтобы подтвердить это фактами, я создал этот небольшой сценарий для генерации 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 ']}'

Это очень вероятный сценарий, и достаточно бросить Error: reduce_overflow_error.

ИМХО, у вас есть два варианта:

Вариант 1: Оптимизированная функция списка

Немного потрудившись, вы можете создать ответ JSON вручную, чтобы вам не нужно было накапливать заказы в массиве.

Я отредактировал функцию списка jhs, чтобы избежать использования массивов, чтобы вы могли иметь клиентов с любым количеством заказов.

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]}');
}

Вариант 2. Оптимизация документов для вашего варианта использования

Если вам действительно нужно, чтобы заказы были в одном и том же документе, спросите себя, можете ли вы сохранить его таким образом и избежать обработки при запросах.

Другими словами: попытайтесь в полной мере использовать возможности, предоставляемые базой данных документов. Разработайте документы так, чтобы они наилучшим образом подходили к вашему варианту использования, и сократите объем последующей обработки, необходимой для их использования.

2 голосов
/ 22 мая 2011

Одним из основных «мнений» 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"}]

И моя функция карты

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

Запрос полного просмотра показывает:

{"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"}
]}

Опция 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();
}

Эта функция просто циклически перебирает строки map и выводит полную строку клиентских + заказов только после сбора всей информации. Очевидно, что вы можете изменить формат JSON, который вы выводите. Кроме того, есть параметр ?lim=X, поскольку использование параметра limit будет мешать запросу карты.

Опасность заключается в том, что эта функция создает неограниченный отклик в памяти . Что делать, если клиент сделал 10000 заказов? Или 100 000? В конечном итоге сборка массива orders завершится неудачно Вот почему CouchDB держит их в «высоком» списке. Если вы никогда не получите 10000 заказов на одного клиента, тогда это не проблема.

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

Вариант 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;
}

Всегда запрашивать это представление с помощью ?group_level=1, который будет сегментировать результаты по клиенту (поскольку имя клиента было первым элементом в клавише map).

Это против закона , поскольку вы не должны накапливать данных во время сокращения фазы. Вот почему они называют это уменьшить .

Тем не менее, 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"]}
]}

Удачи!

...