2013-12-08 16 views
13

Jestem nowy w agregacji MongoDB i zastanawiałem się, czy istnieje sposób na obliczenie mediany za pomocą struktury agregacji MongoDB?Oblicz medianę w strukturze agregacji MongoDB

Cheers,

Lewis

+0

AFAIK nie ma czegoś takiego jak '$ median' więc prawdopodobnie będziesz musiał użyć map-zmniejszenie do tego. – hgoebl

+0

Istnieje otwarte żądanie funkcji, aby dodać obsługę akumulatora mediana '$ mediana'. Pobudźuj/oglądaj [SERVER-4929] (https://jira.mongodb.org/browse/SERVER-4929) w module do śledzenia problemów MongoDB. – Stennie

Odpowiedz

2

Ramy agregacja nie obsługuje medianę out-of-the-box. Więc będziesz musiał napisać coś na własną rękę.

Polecam Ci to zrobić na poziomie aplikacji. Odzyskaj wszystkie swoje dokumenty za pomocą zwykłego find(), sortuj zestawy wyników (albo w bazie danych za pomocą funkcji .sort() kursora lub sortując je w aplikacji - twoja decyzja), a następnie pobierając element size/2.

Gdy naprawdę chcesz zrobić to na poziomie bazy danych, możesz to zrobić za pomocą map-reduce. Funkcja mapy wyemituje klucz i tablicę o pojedynczej wartości - wartość, którą chcesz uzyskać medianę. Funkcja redukująca po prostu połączyłaby tablice wyników, które otrzymuje, więc każdy klucz kończy się tablicą zawierającą wszystkie wartości. Funkcja finalizacji oblicza następnie medianę tej macierzy, ponownie sortując tablicę, a następnie pobierając numer elementu size/2.

+0

Okej, to genialne, oddam to. – user3080286

20

Mediana jest nieco trudna do obliczenia w ogólnym przypadku, ponieważ obejmuje sortowanie całego zestawu danych lub rekursję z głębokością, która jest również proporcjonalna do rozmiaru zestawu danych. To może powód, dla którego wiele baz danych nie ma operatora medianowego po wyjęciu z pudełka (MySQL również go nie ma).

Najprostszym sposobem, aby obliczyć medianę będzie z tych dwóch sprawozdań (zakładając, że atrybut, na której chcemy obliczyć medianę nazywa a i chcemy go wszystkie dokumenty w kolekcji, coll):

count = db.coll.count(); 
db.coll.find().sort({"a":1}).skip(count/2 - 1).limit(1); 

Jest to odpowiednik tego, co ludzie suggest for MySQL.

+5

Wiem, że nie wolno komentować tylko po to, by podziękować ... ale to jest piękne :) – fguillen

1

Jest to możliwe w jednym ujęciu ze strukturą zbiorczą.

Sortuj => wstaw w tabelę posortuj wartości => pobierz Rozmiar tablicy => podziel wielkość przez dwa => pobierz wartość Int podziału (lewa strona mediany) => dodaj 1 do lewej strony (prawa strona) = > dostać element tablicy po lewej stronie i po prawej stronie => średnia z dwóch elementów

to jest przykład z wiosennej java mongoTemplate:

Model jest lista książki z logowania autora ("właściciel "), celem jest uzyskanie mediana książki przez użytkowników:

 GroupOperation countByBookOwner = group("owner").count().as("nbBooks"); 

    SortOperation sortByCount = sort(Direction.ASC, "nbBooks"); 

    GroupOperation putInArray = group().push("nbBooks").as("nbBooksArray"); 

    ProjectionOperation getSizeOfArray = project("nbBooksArray").and("nbBooksArray").size().as("size"); 

    ProjectionOperation divideSizeByTwo = project("nbBooksArray").and("size").divide(2).as("middleFloat"); 

    ProjectionOperation getIntValueOfDivisionForBornLeft = project("middleFloat", "nbBooksArray").and("middleFloat") 
      .project("trunc").as("beginMiddle"); 

    ProjectionOperation add1ToBornLeftToGetBornRight = project("beginMiddle", "middleFloat", "nbBooksArray") 
      .and("beginMiddle").project("add", 1).as("endMiddle"); 

    ProjectionOperation arrayElementAt = project("beginMiddle", "endMiddle", "middleFloat", "nbBooksArray") 
      .and("nbBooksArray").project("arrayElemAt", "$beginMiddle").as("beginValue").and("nbBooksArray") 
      .project("arrayElemAt", "$endMiddle").as("endValue"); 

    ProjectionOperation averageForMedian = project("beginMiddle", "endMiddle", "middleFloat", "nbBooksArray", 
      "beginValue", "endValue").and("beginValue").project("avg", "$endValue").as("median"); 

    Aggregation aggregation = newAggregation(countByBookOwner, sortByCount, putInArray, getSizeOfArray, 
      divideSizeByTwo, getIntValueOfDivisionForBornLeft, add1ToBornLeftToGetBornRight, arrayElementAt, 
      averageForMedian); 

    long time = System.currentTimeMillis(); 
    AggregationResults<MedianContainer> groupResults = mongoTemplate.aggregate(aggregation, "book", 
      MedianContainer.class); 

I tu powstały agregacji:

{ 
"aggregate": "book" , 
"pipeline": [ 
    { 
     "$group": { 
      "_id": "$owner" , 
      "nbBooks": { 
       "$sum": 1 
      } 
     } 
    } , { 
     "$sort": { 
      "nbBooks": 1 
     } 
    } , { 
     "$group": { 
      "_id": null , 
      "nbBooksArray": { 
       "$push": "$nbBooks" 
      } 
     } 
    } , { 
     "$project": { 
      "nbBooksArray": 1 , 
      "size": { 
       "$size": ["$nbBooksArray"] 
      } 
     } 
    } , { 
     "$project": { 
      "nbBooksArray": 1 , 
      "middleFloat": { 
       "$divide": ["$size" , 2] 
      } 
     } 
    } , { 
     "$project": { 
      "middleFloat": 1 , 
      "nbBooksArray": 1 , 
      "beginMiddle": { 
       "$trunc": ["$middleFloat"] 
      } 
     } 
    } , { 
     "$project": { 
      "beginMiddle": 1 , 
      "middleFloat": 1 , 
      "nbBooksArray": 1 , 
      "endMiddle": { 
       "$add": ["$beginMiddle" , 1] 
      } 
     } 
    } , { 
     "$project": { 
      "beginMiddle": 1 , 
      "endMiddle": 1 , 
      "middleFloat": 1 , 
      "nbBooksArray": 1 , 
      "beginValue": { 
       "$arrayElemAt": ["$nbBooksArray" , "$beginMiddle"] 
      } , 
      "endValue": { 
       "$arrayElemAt": ["$nbBooksArray" , "$endMiddle"] 
      } 
     } 
    } , { 
     "$project": { 
      "beginMiddle": 1 , 
      "endMiddle": 1 , 
      "middleFloat": 1 , 
      "nbBooksArray": 1 , 
      "beginValue": 1 , 
      "endValue": 1 , 
      "median": { 
       "$avg": ["$beginValue" , "$endValue"] 
      } 
     } 
    } 
] 

}