Le garcon
Le garcon

Reputation: 8407

Customising groups in mongo during aggregation

I'm using an example from mongo docs which I've changed a bit:

db.books.aggregate(
   [
     { $group : { _id : "$genre", books: { $push: "$$ROOT" } } }
   ]
)

This query will return an array of books by genre.

I want to customise it a bit, so that I would not get extra data. The following example would be a dummy one, but I'm curious if it could be implemented in mongo. I want my aggregation to return an array of groups where if genre is 'Tragedy' only 1 book would be fetched and there would be a booksCount field and in all the other cases books will be an array and there won't be a booksCount.

So the aggregation result would look something like this:

[
  { _id: '_id of tragedy genre', book: {some book}, booksCount: some int },
  { _id: '_id of some other genre', books: [books] },
  ...
]

So I want groups to have different keys depending on some condition

Upvotes: 0

Views: 53

Answers (1)

Jonathan
Jonathan

Reputation: 1402

One way to do this is with the $facet aggregation pipeline stage. This stage allows us to create multiple pipelines with the same input documents. In this case we have one pipeline for the tragedy genre and another for all the other genres. In order to get your desired output we need to merge the two pipeline stages. From the docs:

Each sub-pipeline has its own field in the output document where its results are stored as an array of documents.

Because the facet stage returns an array of documents for each pipeline, we need to: concatenate these arrays together, unwind the resulting array so that each element is its own document, and then replace the root of each document to get rid of the unwanted key.

Example

Say you have the following documents:

db.books.insertMany([{
    genre: "Tragedy",
    title: "Romeo and Juliet"
}, {
    genre: "Tragedy",
    title: "Titanic"
}, {
    genre: "Comedy",
    title: "Hitchhikers Guide to the Galaxy"
}, {
    genre: "Comedy",
    title: "Blazing Saddles"
}, {
    genre: "Thriller",
    title: "Shutter Island"
}, {
    genre: "Thriller",
    title: "Hannibal"
}])

Then you can use the following query:

db.books.aggregate([{
    $facet: {
        tragedy: [{
            $match: {genre: "Tragedy"}
        }, {
            $group: {
                _id: "$genre",
                books: {$push: "$$ROOT"}
            }
        }, {
            $project: {
                book: {$arrayElemAt: ["$books", 1]},
                booksCount: {$size: "$books"}
            }
        }],
        other: [{
            $match: {
                genre: {$ne: "Tragedy"}
            } 
        }, {
            $group: {
                _id: "$genre",
                books: {$push: "$$ROOT"}
            }
        }]
    }
}, {
    $project: {
        documents: {$concatArrays: ["$tragedy", "$other"]}  
    }
}, {
    $unwind: "$documents"
}, {
    $replaceRoot: {newRoot: "$documents"}
}])

To produce:

{
    "_id" : "Tragedy",
    "book" : {
        "_id" : ObjectId("5c59f15bc59454560b36a5c7"),
        "genre" : "Tragedy",
        "title" : "Titanic"
    },
    "booksCount" : 2
}
{
    "_id" : "Thriller",
    "books" : [
        {
            "_id" : ObjectId("5c59f15bc59454560b36a5ca"),
            "genre" : "Thriller",
            "title" : "Shutter Island"
        },
        {
            "_id" : ObjectId("5c59f15bc59454560b36a5cb"),
            "genre" : "Thriller",
            "title" : "Hannibal"
        }
    ]
}
{
    "_id" : "Comedy",
    "books" : [
        {
            "_id" : ObjectId("5c59f15bc59454560b36a5c8"),
            "genre" : "Comedy",
            "title" : "Hitchhikers Guide to the Galaxy"
        },
        {
            "_id" : ObjectId("5c59f15bc59454560b36a5c9"),
            "genre" : "Comedy",
            "title" : "Blazing Saddles"
        }
    ]
}

Upvotes: 1

Related Questions