Steve
Steve

Reputation: 596

Merging arrays from multiple MongoDB documents

I'm attempting to write some query that will take documents about YouTubers and their videos, and then return a single list of all their videos in chronological order.

So far with my query (in Mongoose):

return mongoose.model('creators').find({
  id: {}
})

I get these results:

[
    {
        "_id": "5b0891a3dcec4912601e32ce",
        "name": "CaptainDisillusion",
        "id": "UCEOXxzW2vU0P-0THehuIIeg",
        "videos": [
            {
                "_id": "5b0891a3dcec4912601e32cf",
                "name": "Quick D: A Fighter Jet Says Hi",
                "id": "-NPmBkYb_mY",
                "date": "2018-05-10T16:30:30.000Z"
            }
        ],
        "__v": 0
    },
    {
        "_id": "5b08967b66d8321034176afb",
        "name": "Curious Droid",
        "id": "UC726J5A0LLFRxQ0SZqr2mYQ",
        "videos": [
            {
                "_id": "5b0891a3dcec4912601e32cf",
                "name": "Guest post bloggers wanted for the Curious Droid website",
                "id": "sNLvdoValBw",
                "date": "2018-05-23T16:30:30.000Z"
            }
        ],
        "__v": 0
    }
]

Ideally what I'd like to do is return something along the lines of:

"videos": [
            {
                "_id": "5b0891a3dcec4912601e32cf",
                "name": "Guest post bloggers wanted for the Curious Droid website",
                "id": "sNLvdoValBw",
                "date": "2018-05-23T16:30:30.000Z"
            },
            {
                "_id": "5b0891a3dcec4912601e32cf",
                "name": "Quick D: A Fighter Jet Says Hi",
                "id": "-NPmBkYb_mY",
                "date": "2018-05-10T16:30:30.000Z"
            }
        ]

Returning just the one array of all the documents combined and in newest-first order.

Is that possible to do and if so how do I accomplish it?

Thanks!

Upvotes: 0

Views: 259

Answers (2)

Mạnh Quyết Nguyễn
Mạnh Quyết Nguyễn

Reputation: 18235

You need to use $unwind to flat the array items, then promote inner $videos as new root item along with $sort to order your videos item.

db.tests.aggregate([
      {$match: ...},
      {$unwind: "$videos"}, 
      {$replaceRoot: {newRoot: "$videos"}}, 
      {$sort: {date: -1}} // Newest first
])

Upvotes: 1

Neil Lunn
Neil Lunn

Reputation: 151112

You need aggregate() instead. Either using $unwind and $replaceRoot where you have it:

return mongoose.model('creators').aggregate([
  // { "$match": { ... } },                      // A real condition to match the document
  { "$unwind": "$videos" },
  { "$replaceRoot": { "newRoot": "$videoes" } }
])

Or using $project with all fields explicitly:

return mongoose.model('creators').aggregate([
  // { "$match": { ... } },                      // A real condition to match the document
  { "$unwind": "$videos" },
  { "$project": {
     "_id": "$videos._id",
     "name": "$videos.name",
     "id": "$videos.id",
     "date": "$videos.date"
  }}
])

Those are the only things that promote the embedded array content to the "top level" of the document in output.

NOTE: Any query conditions on either the document or array content should be done "before" the $unwind. This means applying the $match valid for query conditions and possibly applying a $filter projection on the array content first. If you don't do those "first" you are creating unnecessary overhead for your application.

The whole process of using $unwind is really quite expensive anyway. It would be far wiser to actually take a good look at your query patterns and really make a decision if it is in fact worth keeping the documents embedded or if your queries are indeed more frequently suited to placing the documents in a separate collection.

Upvotes: 2

Related Questions