2013-04-28 14 views
5

Rozważmy następujące wzory dokumentów przechowywanych w CouchDBCouchDB - Mapa Zmniejsz podobna do grupy SQL przez

{ 
"_id":...., 
"rev":...., 
"type":"orders", 
"Period":"2013-01", 
"Region":"East", 
"Category":"Stationary", 
"Product":"Pen", 
"Rate":1, 
"Qty":10, 
"Amount":10 
} 

{ 
"_id":...., 
"rev":...., 
"type":"orders", 
"Period":"2013-02", 
"Region":"South", 
"Category":"Food", 
"Product":"Biscuit", 
"Rate":7, 
"Qty":5, 
"Amount":35 
} 

Rozważmy następującą kwerendę SQL

SELECT Period, Region,Category, Product, Min(Rate),Max(Rate),Count(Rate), Sum(Qty),Sum(Amount) 
FROM Sales 
GROUP BY Period,Region,Category, Product; 

Czy to możliwe, aby stworzyć mapę/zmniejszenia widoków w ekwiwalencie couchdb do powyższa kwerenda SQL i produkcja wyjściowa taka jak

[ 
    { 
     "Period":"2013-01", 
     "Region":"East", 
     "Category":"Stationary", 
     "Product":"Pen", 
     "MinRate":1, 
     "MaxRate":2, 
     "OrdersCount":20, 
     "TotQty":1000, 
     "Amount":1750 
    }, 
    { 
    ... 
    } 

] 
+0

Czy próbowałeś już czegoś? Prosty widok powinien działać dobrze. Niektóre przykładowe dokumenty pomogłyby komuś w udzieleniu bardziej szczegółowej/szczegółowej odpowiedzi. –

+0

@ DominicBarnes Dostarczono kilku przykładowych dokumentów. –

Odpowiedz

3

Z góry, uważam, że odpowiedź @ benedolph to najlepsza praktyka i najlepszy scenariusz. Każda redukcja powinna idealnie zwrócić 1 wartość skalarną, aby kod był jak najprostszy.

Prawdą jest jednak, że należy wydać wiele zapytań, aby pobrać pełny zestaw wyników opisany w pytaniu. Jeśli nie masz możliwości równoległego uruchamiania zapytań lub bardzo ważne jest utrzymanie liczby zapytań, można zrobić to wszystko naraz.

Twoja funkcja mapy pozostanie dość prosta:

function (doc) { 
    emit([ doc.Period, doc.Region, doc.Category, doc.Product ], doc); 
} 

zmniejszyć funkcja jest, gdy robi się długa:

function (key, values, rereduce) { 
    // helper function to sum all the values of a specified field in an array of objects 
    function sumField(arr, field) { 
     return arr.reduce(function (prev, cur) { 
      return prev + cur[field]; 
     }, 0); 
    } 

    // helper function to create an array of just a single property from an array of objects 
    // (this function came from underscore.js, at least it's name and concept) 
    function pluck(arr, field) { 
     return arr.map(function (item) { 
      return item[field]; 
     }); 
    } 

    // rereduce made this more challenging, and I could not thoroughly test this right now 
    // see the CouchDB wiki for more information 
    if (rereduce) { 
     // a rereduce handles transitionary values 
     // (so the "values" below are the results of previous reduce functions, not the map function) 
     return { 
      OrdersCount: sumField(values, "OrdersCount"), 
      MinRate: Math.min.apply(Math, pluck(values, "MinRate")), 
      MaxRate: Math.max.apply(Math, pluck(values, "MaxRate")), 
      TotQty: sumField(values, "TotQty"), 
      Amount: sumField(values, "Amount") 
     }; 
    } else { 
     var rates = pluck(values, "Rate"); 

     // This takes a group of documents and gives you the stats you were asking for 
     return { 
      OrdersCount: values.length, 
      MinRate: Math.min.apply(Math, rates), 
      MaxRate: Math.max.apply(Math, rates), 
      TotQty: sumField(values, "Qty"), 
      Amount: sumField(values, "Amount") 
     }; 
    } 
} 

nie byłem w stanie przetestować „rereduce” oddział tego kodu na wszystko, będziesz musiał to zrobić na końcu. (ale to powinno zadziałać) Zobacz the wiki, aby uzyskać informacje o zmniejszaniu lub zmniejszaniu.

Funkcje pomocnicze, które dodałem u góry, sprawiły, że kod jest ogólnie krótszy i łatwiejszy do odczytania, w dużej mierze zależą one od mojego doświadczenia z Underscore.js. Nie można jednak włączać modułów CommonJS do funkcji zmniejszania, więc trzeba pisać ręcznie.

Ponownie, najlepszym scenariuszem jest, aby każde zagregowane pole otrzymało własną mapę/zmniejszyć indeks, ale jeśli to nie jest opcja dla ciebie, powyższy kod powinien dać ci to, co opisałeś tutaj w pytaniu .

+0

Dziękuję za poświęcony czas i szczegółową odpowiedź. Pójdę za twoim rozwiązaniem. –

2

Zaproponuję bardzo proste le rozwiązanie, które wymaga jednego widoku na zmienną, którą chcesz zebrać w klauzuli "wybierz". Chociaż z pewnością można agregować wszystkie zmienne w widoku pojedynczego, funkcja zmniejszania byłaby znacznie bardziej złożona.

dokument Konstrukcja wygląda następująco:

{ 
    "_id": "_design/ddoc", 
    "_rev": "...", 
    "language": "javascript", 
    "views": { 
     "rates": { 
      "map": "function(doc) {\n emit([doc.Period, doc.Region, doc.Category, doc.Product], doc.Rate);\n}", 
      "reduce": "_stats" 
     }, 
     "qty": { 
      "map": "function(doc) {\n emit([doc.Period, doc.Region, doc.Category, doc.Product], doc.Qty);\n}", 
      "reduce": "_stats" 
     } 
    } 
} 

Teraz można wyszukać <couchdb>/<database>/_design/ddoc/_view/rates?group_level=4 aby uzyskać statystyki o zmiennej „rate”. Wynik powinien wyglądać następująco:

{"rows":[ 
{"key":["2013-01","East","Stationary","Pen"],"value":{"sum":4,"count":3,"min":1,"max":2,"sumsqr":6}}, 
{"key":["2013-01","North","Stationary","Pen"],"value":{"sum":1,"count":1,"min":1,"max":1,"sumsqr":1}}, 
{"key":["2013-01","South","Stationary","Pen"],"value":{"sum":0.5,"count":1,"min":0.5,"max":0.5,"sumsqr":0.25}}, 
{"key":["2013-02","South","Food","Biscuit"],"value":{"sum":7,"count":1,"min":7,"max":7,"sumsqr":49}} 
]} 

Dla zmiennej „Qty”, zapytanie będzie <couchdb>/<database>/_design/ddoc/_view/qty?group_level=4.

Właściwością group_level można kontrolować poziomy, które agregacja ma zostać wykonana. Na przykład zapytanie z group_level=2 będzie agregowało do "Okresów" i "Regionów".

+0

Dziękujemy za rozwiązanie. Będzie to przydatne do agregowania pojedynczej zmiennej. –