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