11

I am newbie. But I try to learn the most logical ways to write the queries.

Assume I have collection which is as;

{ 
    "id" : NumberInt(1), 
    "school" : [
        {
            "name" : "george", 
            "code" : "01"
        }, 
        {
            "name" : "michelangelo", 
            "code" : "01"
        }
    ], 
    "enrolledStudents" : [
        {
            "userName" : "elisabeth", 
            "code" : NumberInt(21)
        }
    ]
}
{ 
    "id" : NumberInt(2), 
    "school" : [
        {
            "name" : "leonarda da vinci", 
            "code" : "01"
        }
    ], 
    "enrolledStudents" : [
        {
            "userName" : "michelangelo", 
            "code" : NumberInt(25)
        }
    ]
}

I want to list occurence of a key with their corresponding code values.

As an example key : michelangelo

To find the occurence of the key, I wrote two differen aggregation queries as;

db.test.aggregate([
    {$unwind: "$school"},
    {$match : {"school.name" : "michelangelo"}},
    {$project: {_id: "$id", "key" : "$school.name", "code" : "$school.code"}}
])

and

db.test.aggregate([
    {$unwind: "$enrolledStudents"},
    {$match : {"enrolledStudents.userName" : "michelangelo"}},
    {$project: {_id: "$id", "key" : "$enrolledStudents.userName", "code" : "$enrolledStudents.code"}}
])

the result of these 2 queries return what I want as;

{ "_id" : 1, "key" : "michelangelo", "code" : "01" }
{ "_id" : 2, "key" : "michelangelo", "code" : 25 }

One of them to search in enrolledStudents, the other one is searching in school field.

Can these 2 queries reduced into more logical query? Or is this the only way to do it?

ps: I am aware that database structure is not logical, but I tried to simulate.

edit I try to write a query with find.

db.test.find({$or: [{"enrolledStudents.userName" : "michelangelo"} , {"school.name" : "michelangelo"}]}).pretty()

but this returns the whole documents as;

{
    "id" : 1,
    "school" : [
        {
            "name" : "george",
            "code" : "01"
        },
        {
            "name" : "michelangelo",
            "code" : "01"
        }
    ],
    "enrolledStudents" : [
        {
            "userName" : "elisabeth",
            "code" : 21
        }
    ]
}
{
    "id" : 2,
    "school" : [
        {
            "name" : "leonarda da vinci",
            "code" : "01"
        }
    ],
    "enrolledStudents" : [
        {
            "userName" : "michelangelo",
            "code" : 25
        }
    ]
}

1 Answer 1

2
+25

Mongo 3.4

$match - This stage will keep all the school array and enrolledStudents where there is atleast one embedded document matching both the query condition

$group - This stage will combine all the school and enrolledStudents array to 2d array for each _id in a group.

$project - This stage will $filter the merge array for matching query condition and $map the array to with new labels values array.

$unwind - This stage will flatten the array.

$addFields & $replaceRoot - This stages will add the id field and promote the values array to the top.

db.collection.aggregate([
    {$match : {$or: [{"enrolledStudents.userName" : "michelangelo"} , {"school.name" : "michelangelo"}]}},
    {$group: {_id: "$id", merge : {$push:{$setUnion:["$school", "$enrolledStudents"]}}}},
    {$project: {
        values: {
              $map:
                 {
                   input: {
                            $filter: {
                                input: {"$arrayElemAt":["$merge",0]},
                                as: "onef",
                                cond: {
                                    $or: [{
                                        $eq: ["$$onef.userName", "michelangelo"]
                                    }, {
                                        $eq: ["$$onef.name", "michelangelo"]
                                    }]
                                }
                            }
                        },
                   as: "onem",
                   in: { 
                         key : { $ifNull: [ "$$onem.userName", "$$onem.name" ] },
                         code : "$$onem.code"}
                 }
            }
        }
    },
    {$unwind: "$values"},
    {$addFields:{"values.id":"$_id"}},
    {$replaceRoot: { newRoot:"$values"}}
])

Sample Response

{ "_id" : 2, "key" : "michelangelo", "code" : 25 }
{ "_id" : 1, "key" : "michelangelo", "code" : "01" }

Mongo <= 3.2

Replace last two stages of above aggregation with $project to format the response.

{$project: {"_id": 0 , id:"$_id", key:"$values.key", code:"$values.code"}}

Sample Response

{ "_id" : 2, "key" : "michelangelo", "code" : 25 }
{ "_id" : 1, "key" : "michelangelo", "code" : "01" }

You can use $redact instead of $group & match and add $project with $map to format the response.

$redact to go through a document level at a time and perform $$DESCEND and $$PRUNE on the matching criteria.

The only thing to note is usage of $ifNull in the first document level for id so that you can $$DESCEND to embedded document level for further processing.

db.collection.aggregate([
    {
        $redact: {
            $cond: [{
                $or: [{
                    $eq: ["$userName", "michelangelo"]
                }, {
                    $eq: ["$name", "michelangelo"]
                }, {
                    $ifNull: ["$id", false]
                }]
            }, "$$DESCEND", "$$PRUNE"]
        }
    },
    {
        $project: {
            id:1,
            values: {
              $map:
                 {
                   input: {$setUnion:["$school", "$enrolledStudents"]},
                   as: "onem",
                   in: { 
                         key : { $ifNull: [ "$$onem.userName", "$$onem.name" ] },
                         code : "$$onem.code"}
                 }
            }
        }
    },
    {$unwind: "$values"},
    {$project: {_id:0,id:"$id", key:"$values.key", code:"$values.code"}}
])
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.