EricP
EricP

Reputation: 163

MongoDB query multiple subdocuments

My MongoDB data structure looks like this:

{
  users: [{
    userref: "User1",
    results: [{
      testref: "TEST1",
      history: [{
        score: 10
      }, {
        score: 15
      }]
    }, {
      testref: "TEST2",
      history: [{
        score: 2
      }, {
        score: 1
      }]
    }]
  }]
}

I have a collection of users, each with an array of results (one result object for each test), and within each result, an array of the history of the historical scores.

What I need to do is "return the userref for all users whose most recent score for TEST1 is X and whose most recent score for TEST2 is Y". Users who have not completed one or other of these tests should not be returned. By "most recent", I mean the last in the array.

I'm assuming I need to use aggregation, but I'm totally new to this, so could do with some help.

This is what I have tried so far, and it works, but it only matches the condition where TEST1=15, and I would also like it to match the condition TEST2=1.

[{$unwind: {
  path: '$results’,
  preserveNullAndEmptyArrays: false
}}, {$match: {
  'results.testref': 'TEST1'
}}, {$project: {
  results: 1,
  userref: 1,
}}, {$replaceRoot: {
  newRoot: {
    $mergeObjects: [
      {
        userref: '$userref'
      },
      '$results'
    ]
  }
}}, {$project: {
  userref: 1,
  history: {
    $slice: [
      '$history',
      -1
    ]
  }
}}, {$match: {
  'history.score': 15
}}, {$project: {
  userref: 1,
}}]

Thanks

Upvotes: 0

Views: 79

Answers (1)

Gibbs
Gibbs

Reputation: 22974

To match both the conditions,

db.getCollection('test2').aggregate([{"$unwind":"$users"}, {$unwind:"$users.results"}, 
{$project:
     {"lastScore": 
        {$slice:
            ["$users.results.history", -1]
        },
     "users.userref":1,
     "users.results.testref":1
    }
},
{
    $match:{"$or":[{
      $and:[
        {"users.results.testref":"TEST1"},
        {"lastScore.0.score":15}
      ]},
      {
      $and:[
        {"users.results.testref":"TEST2"},
        {"lastScore.0.score":1}
      ]}
      ]
    }
},
{
    $group:{
        "_id":{"_id":"$_id","userref":"$users.userref"},
        "data":{$push:{"tests":"$users.results.testref", "userref":"$users.userref"}}
    }
},
{
    $project:{
        "_id":1,
        "validUser":{
        "$filter":{
            "input":"$data",
            "as":"data",
            "cond":{$or:[{$eq:["$$data.tests","TEST1"]}, {$eq:["$$data.tests","TEST2"]}]}
        }}
    }
},
{
    $project:{
        "_id.userref":1
    }
}
])

What does the query do:

  1. Unwind users
  2. Unwind results
  3. Get last score part along with user ref, test ref
  4. Check for the necessary conditions
  5. Group back on user ref along with doc id
  6. Check whether two conditions met using $filte
  7. Project userref

Output:

/* 1 */
{
    "_id" : {
        "userref" : "User1"
    }
}

Upvotes: 1

Related Questions