Подтвердить что ты не робот

Вычислить медиану в структуре агрегации MongoDB

Я новичок в агрегации MongoDB и задался вопросом, есть ли способ вычислить медиану с использованием структуры агрегации MongoDB?

Приветствия,

Льюис

4b9b3361

Ответ 1

Медиана несколько сложна для вычисления в общем случае, поскольку она включает в себя сортировку всего набора данных или использование рекурсии с глубиной, которая также пропорциональна размеру набора данных. Возможно, причина в том, что многие базы данных не имеют медианного оператора из коробки (у MySQL тоже нет).

Самый простой способ вычислить медиану будет с этими двумя утверждениями (предполагая, что атрибут, по которому мы хотим вычислить медиану, называется a, и мы хотим его по всем документам в коллекции, coll):

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

Это эквивалентно тому, что люди предлагают для MySQL.

Ответ 2

Структура агрегации не поддерживает медианную готовую версию. Поэтому вам придется писать что-то по своему усмотрению.

Я бы рекомендовал вам сделать это на уровне приложения. Извлеките все ваши документы с помощью обычной find(), сортируйте результирующие наборы (либо в базе данных с помощью функции .sort() курсора, либо отсортируйте их в приложении - ваше решение), а затем получите элемент size / 2.

Если вы действительно хотите сделать это на уровне базы данных, вы можете сделать это с помощью map-reduce. Функция-карта выдавала бы ключ и массив с единственным значением - значением, которое вы хотите получить медиану. Функция сокращения просто объединяет массивы полученных результатов, поэтому каждый ключ заканчивается массивом со всеми значениями. Затем функция finalize вычислит медиану этого массива, снова путем сортировки массива, а затем получит номер элемента size / 2.

Ответ 3

Это можно сделать одним выстрелом с помощью общей структуры.

Сортировка = > поместить в отсортированные значения массива = > получить размер массива = > разделить размер на два = > получить значение Int деления (левая часть медианного) = > добавить 1 к левой стороне (правая сторона) = > получить элемент массива в левой и правой частях = > средний из двух элементов

Это образец с Spring java mongoTemplate:

Модель представляет собой список книг с логином автора ( "владелец" ), цель состоит в том, чтобы получить медианную книгу от пользователей:

        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);

И вот итоговая агрегация:

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

}