Платформа агрегации сглаживает данные поддокумента с родительским документом

#mongodb #mongodb-query #aggregation-framework

#mongodb #mongodb-запрос #платформа агрегации

Вопрос:

Я создаю панель мониторинга, которая вращается между разными веб-страницами. Я хочу извлечь все слайды, которые являются частью «тестовой» колоды, и упорядочить их соответствующим образом. После запроса мой результат в идеале выглядел бы так.

 [
    { "url" : "http://10.0.1.187", "position": 1, "duartion": 10 },
    { "url" : "http://10.0.1.189", "position": 2, "duartion": 3 }
]
  

В настоящее время у меня есть набор данных, который выглядит следующим образом

 {
    "_id" : ObjectId("53a612043c24d08167b26f82"),
    "url" : "http://10.0.1.189",
    "decks" : [
        {
            "title" : "Test",
            "position" : 2,
            "duration" : 3
        }
    ]
}
{
    "_id" : ObjectId("53a6103e3c24d08167b26f81"),
    "decks" : [
        {
            "title" : "Test",
            "position" : 1,
            "duration" : 2
        },
        {
            "title" : "Other Deck",
            "position" : 1,
            "duration" : 10
        }
    ],
    "url" : "http://10.0.1.187"
}
  

Моя попытка запроса выглядит так:

 db.slides.aggregate([
    {
        "$match": {
            "decks.title": "Test"
        }
    },
    {
        "$sort": {
            "decks.position": 1
        }
    },
    {
        "$project": {
            "_id": 0,
            "position": "$decks.position",
            "duration": "$decks.duration",
            "url": 1
        }
    }
]);
  

Но это не дает желаемых результатов. Как я могу запросить свой набор данных и получить ожидаемые результаты оптимальным способом?

Ответ №1:

Ну, чтобы по-настоящему «сгладить» документ, как следует из вашего названия, тогда $unwind всегда будет использоваться, поскольку другого способа сделать это действительно нет. Однако есть несколько разных подходов, если вы можете жить с массивом, отфильтрованным до соответствующего элемента.

В принципе, если у вас действительно есть только одна вещь для сопоставления в массиве, то ваш самый быстрый подход — просто использовать .find() сопоставление требуемого элемента и проецирование:

  db.slides.find(
     { "decks.title": "Test" },
     { "decks.$": 1 }
 ).sort({ "decks.position": 1 }).pretty()
  

Это все еще массив, но пока у вас есть только один элемент, который соответствует, это работает. Кроме того, элементы сортируются, как и ожидалось, хотя, конечно, поле «заголовок» не удаляется из сопоставленных документов, поскольку это выходит за рамки возможностей простого проектирования.

 {
    "_id" : ObjectId("53a6103e3c24d08167b26f81"),
    "decks" : [
            {
                    "title" : "Test",
                    "position" : 1,
                    "duration" : 2
            }
    ]
}
{
    "_id" : ObjectId("53a612043c24d08167b26f82"),
    "decks" : [
            {
                    "title" : "Test",
                    "position" : 2,
                    "duration" : 3
            }
    ]
}
  

Другой подход, если у вас есть MongoDB 2.6 или выше, использует $map operator и некоторые другие, чтобы как «фильтровать», так и изменять форму массива «на месте» без фактического применения $unwind :

 db.slides.aggregate([
    { "$project": {
        "url": 1,
        "decks": {
            "$setDifference": [
                { 
                    "$map": {
                        "input": "$decks",
                        "as": "el",
                        "in": {
                            "$cond": [
                                { "$eq": [ "$$el.title", "Test" ] },
                                { 
                                    "position": "$$el.position",
                                    "duration": "$$el.duration"
                                },
                                false
                            ]
                        }
                    }
                },
                [false]
            ]
        }
    }},
    { "$sort": { "decks.position": 1 }}
])
  

Преимущество заключается в том, что вы можете вносить изменения без «разматывания», что может сократить время обработки больших массивов, поскольку вы по существу не создаете новые документы для каждого элемента массива, а затем запускаете отдельный $match этап для «фильтрации» или другой $project для изменения формы.

 {
    "_id" : ObjectId("53a6103e3c24d08167b26f81"),
    "decks" : [
            {
                    "position" : 1,
                    "duration" : 2
            }
    ],
    "url" : "http://10.0.1.187"
}
{
    "_id" : ObjectId("53a612043c24d08167b26f82"),
    "url" : "http://10.0.1.189",
    "decks" : [
            {
                    "position" : 2,
                    "duration" : 3
            }
    ]
}
  

Вы можете снова либо работать с «отфильтрованным» массивом, либо, если хотите, вы можете снова «сгладить» это по-настоящему, добавив дополнительный $unwind , в котором вам не нужно фильтровать, $match поскольку результат уже содержит только совпадающие элементы.

Но, вообще говоря, если вы можете с этим смириться, просто используйте .find() , поскольку это будет самый быстрый способ. В противном случае то, что вы делаете, подходит для небольших данных, или есть другой вариант для рассмотрения.

Ответ №2:

Ну, как только я опубликовал, я понял, что должен использовать an $unwind . Является ли этот запрос оптимальным способом сделать это, или это можно сделать по-другому?

 db.slides.aggregate([
    {
        "$unwind": "$decks"
    },
    {
        "$match": {
            "decks.title": "Test"
        }
    },
    {
        "$sort": {
            "decks.position": 1
        }
    },
    {
        "$project": {
            "_id": 0,
            "position": "$decks.position",
            "duration": "$decks.duration",
            "url": 1
        }
    }
]);