2015-07-05 14 views
6

mam schematu MongoDB, który wygląda mniej więcej tak:MongoDB listy - dostać każdy n-ty element

[ 
    { 
    "name" : "name1", 
    "instances" : [ 
     { 
     "value" : 1, 
     "date" : ISODate("2015-03-04T00:00:00.000Z")    
     }, 
     { 
     "value" : 2, 
     "date" : ISODate("2015-04-01T00:00:00.000Z") 
     }, 
     { 
     "value" : 2.5, 
     "date" : ISODate("2015-03-05T00:00:00.000Z") 
     }, 
     ... 
    ] 
    }, 
    { 
    "name" : "name2", 
    "instances" : [ 
     ... 
    ] 
    } 
] 

gdzie liczba przypadków dla każdego elementu może być dość duża.

Czasami chcę uzyskać tylko próbkę danych, czyli pobrać co trzecią instancję lub co 10 instancję ... otrzymasz obraz.

Osiągam ten cel, pobierając wszystkie wystąpienia i filtrując je w moim kodzie serwera, ale zastanawiałem się, czy istnieje sposób, aby to zrobić za pomocą kwerendy agregacji.

Wszelkie pomysły?


Updated

Przyjmując strukturę danych był płaski jak @SylvainLeroux sugerowane poniżej, to jest:

[ 
    {"name": "name1", "value": 1, "date": ISODate("2015-03-04T00:00:00.000Z")}, 
    {"name": "name2", "value": 5, "date": ISODate("2015-04-04T00:00:00.000Z")}, 
    {"name": "name1", "value": 2, "date": ISODate("2015-04-01T00:00:00.000Z")}, 
    {"name": "name1", "value": 2.5, "date": ISODate("2015-03-05T00:00:00.000Z")}, 
    ... 
] 

będzie zadaniem coraz każdy element jest n-tym konkretnym name (z) będzie łatwiej?

+1

FWIW: _ "liczba instancji dla każdego elementu może być dość duża." _ Może użycie wbudowanego dokumentu nie jest dobrym pomysłem, jeśli liczba dokumentów jest tak duża, że ​​w końcu osiągnie limit 32 MB. Ponadto, jeśli regularnie masz dostęp do podzbioru tej "wielkiej" tablicy, być może jest to inna wskazówka, że ​​schemat nie jest właściwy. Nie znając twoich dokładnych potrzeb, pomyślałbym o czymś bardziej podobnym do '{" name ":" name1 ", value: 1, date: ...}, {" name ":" name1 ", value: 2, date: ...}, ... 'Z tym schematem, możesz nawet (ab) użyć pola _id, aby wybrać próbkę * obciążonego * danych. –

+0

@SylvainLeroux, wystąpienia dzielą się czymś więcej niż tylko nazwą (uprościłem schemat dla tego celu pytania). Nie chcę, aby każda instancja zawierała zasadniczo te same dane, co inne, więc mam obiekt nadrzędny z wieloma instancjami. W ten sposób miałeś dla mnie więcej sensu. Ale jestem otwarty na sugestie ... – yarons

Odpowiedz

1

Niestety, z ramami agregacji nie jest to możliwe, gdyż wymagałoby to opcja z $unwind emitować tablicę index/stanowisko, które obecnie nie mogą obsługiwać agregację. Dostępny jest tutaj otwarty bilet JIRA SERVER-4588.

jednak obejście byłoby użyć MapReduce ale ten pojawia się w ogromnym kosztem wydajności, ponieważ faktyczne obliczenia coraz indeks tablicy są wykonywane przy użyciu silnika javascript osadzone (który jest wolny), a tam jeszcze jest pojedyncza globalna blokada JavaScript, która pozwala tylko na uruchamianie pojedynczego wątku JavaScript w tym samym czasie.

Z mapreduce, można spróbować czegoś takiego:

funkcja mapowania:

var map = function(){ 
    for(var i=0; i < this.instances.length; i++){ 
     emit(
      { "_id": this._id, "index": i }, 
      { "index": i, "value": this.instances[i] } 
     ); 
    } 
}; 

Zmniejszyć funkcja:

var reduce = function(){} 

Następnie można uruchomić następujące mapReduce Function kolekcja:

db.collection.mapReduce(map, reduce, { out : "resultCollection" }); 

A potem można wyszukać kolekcję wynik do Geta listy/tablicy każdy n-ty elementu tablicy instancji przy użyciu metody map() Kursor:

var thirdInstances = db.resultCollection.find({"_id.index": N}) 
             .map(function(doc){return doc.value.value}) 
+1

Jak już powiedziałeś, użycie MapReduce do zapytań ad-hoc ma pewne kary za wydajność. Jednak, w zależności od przypadku użycia, jeśli użycie przestarzałych danych jest akceptowalne, OP może zaplanować zadanie MapReduce na regularne uruchamianie w celu przetworzenia pewnej zbiorczej kolekcji, którą mógłby następnie przesłać później. –

+0

@SylvainLeroux Dobra rozmowa, absolutnie się zgadzam. – chridam

2

to, że Twoje pytanie wyraźnie wydaje zapytał "dostać każdą n-tą instancję", co wydaje się całkiem jasnym pytaniem.

zapytywania jak .find() może rzeczywiście tylko zwrócić dokumencie „jak jest” z wyjątkiem ogólnego pola „Wybór” i w rzucie operatorom takich jak operator positional $ dopasowania lub $elemMatch które pozwalają na pojedynczą dopasowanego elementu tablicy.

Oczywiście jest $slice, ale to po prostu pozwala na "wybór zakresu" w tablicy, więc znowu nie ma zastosowania.

"Jedynymi" rzeczami, które mogą modyfikować wynik na serwerze, są: .aggregate() i .mapReduce(). Ten pierwszy nie "gra bardzo dobrze" z "krojeniem" tablic w jakikolwiek sposób, przynajmniej nie przez elementy "n". Jednak ponieważ argumenty "function()" mapReduce są oparte na JavaScript, to masz trochę więcej miejsca do zabawy.

Dla procesów analitycznych oraz do celów analitycznych „tylko”, a następnie po prostu filtrować zawartość tablicy za pośrednictwem mapreduce użyciu .filter():

db.collection.mapReduce(
    function() { 
     var id = this._id; 
     delete this._id; 

     // filter the content of "instances" to every 3rd item only 
     this.instances = this.instances.filter(function(el,idx) { 
      return ((idx+1) % 3) == 0; 
     }); 
     emit(id,this); 
    }, 
    function() {}, 
    { "out": { "inline": 1 } } // or output to collection as required 
) 

To naprawdę tylko „JavaScript biegacz” w tym momencie, ale jeśli jest to tylko w przypadku anaylsis/testing nie ma na ogół nic złego w koncepcji. Oczywiście wynik nie jest "dokładnie" taki, jak struktura twojego dokumentu, ale jest tak blisko faksu jak może zdobyć mapReduce.

Inna propozycja, którą tu widzę, wymaga utworzenia nowej kolekcji ze wszystkimi pozycjami "zdenormalizowanymi" i wstawienia "indeksu" z tablicy jako części unqique _id klucza. Które mogą produkować coś można zapytań bezpośrednio BU dla „każdego elementu n” będzie trzeba jeszcze zrobić:

db.resultCollection.find({ 
    "_id.index": { "$in": [2,5,8,11,14] } // and so on .... 
}) 

więc wypracować i dostarczyć wartość indeksu „każdy n pozycji” w celu uzyskania " każdy n-ty element ". Tak naprawdę nie wydaje się, aby rozwiązać problem, który został zadany.

Jeśli formularz wyjście wydawało się bardziej pożądane dla swoich celów „testing”, to lepiej kolejne zapytanie o te wyniki byłyby z wykorzystaniem rurociągu agregacji, z $redact

db.newCollection([ 
    { "$redact": { 
     "$cond": { 
      "if": { 
       "$eq": [ 
        { "$mod": [ { "$add": [ "$_id.index", 1] }, 3 ] }, 
       0 ] 
      }, 
      "then": "$$KEEP", 
      "else": "$$PRUNE" 
     } 
    }} 
]) 

że przynajmniej używa „stan logiczny "bardzo podobne do tego, co zostało zastosowane przed .filter(), zanim wystarczy wybrać elementy" nth index "bez wymieniania wszystkich możliwych wartości indeksu jako argumentu zapytania.

0

Albo tylko z bloku znajdują się:

db.Collection.find({}).then(function(data) { 
    var ret = []; 
    for (var i = 0, len = data.length; i < len; i++) { 
    if (i % 3 === 0) { 
     ret.push(data[i]); 
    } 
    } 
    return ret; 
}); 

Zwraca obietnicę którego następnie() można powołać się pobrać n-ty modulo'ed danych.

+0

Jak widać na moim pytaniu, już powiedziałem, że mogę pobrać wszystkie elementy i przefiltrować je w moim kodzie. Moje pytanie brzmiało, czy istnieje sposób, aby to zrobić za pomocą zapytania mongo. – yarons

Powiązane problemy