MongoDB aggregation framework $subtract - mongodb

I'm want use mongodb to achieve simple query like mysql "select a-b from table", but aggregation framework query result is not right.
data:
{ "_id" : ObjectId("511223348a88785127a0d13f"), "a" : 1, "b" : 1, "name" : "xxxxx0" }
{ "_id" : ObjectId("511223348a88785127a0d13f"), "a" : 2, "b" : 2, "name" : "xxxxx1" }
mongodb cmd:
db.site.aggregate([
{ $match: {
"a" : {$exists:true},
"b" : {$exists:true},
}
},
{ $project: { _id : 0,name : 1,
r1: {$subtract:["$a", "$b"]} }
},
{ $limit: 100 },
]);
"result" : [
{
"name" : "xxxx1",
"r1" : -1
},
{
"name" : "xxxx0",
"r1" : -2
},
]

I cannot replicate your behaviour:
> db.tg.find()
{ "_id" : ObjectId("511223348a88785127a0d13f"), "a" : 1, "b" : 1, "name" : "xxxxx0" }
> db.tg.aggregate([{ $match: { "a" : {$exists:true}, "b" : {$exists:true} } }, { $project: { _id : 0,name : 1, r1: {$subtract:["$a", "$b"]} }}, { $limit: 100 }])
{ "result" : [ { "name" : "xxxxx0", "r1" : 0 } ], "ok" : 1 }
Can you give us a little more info like your MongoDB version?

Related

What is $$ROOT in MongoDB aggregate and how it works?

I am watching a tutorial I can understand how this aggregate works, What is the use of pings, $$ROOT in it.
client = pymongo.MongoClient(MY_URL)
pings = client['mflix']['watching_pings']
cursor = pings.aggregate([
{
"$sample": { "size": 50000 }
},
{
"$addFields": {
"dayOfWeek": { "$dayOfWeek": "$ts" },
"hourOfDay": { "$hour": "$ts" }
}
},
{
"$group": { "_id": "$dayOfWeek", "pings": { "$push": "$$ROOT" } }
},
{
"$sort": { "_id": 1 }
}
]);
Let's assume that our collection looks like below:
{
"_id" : ObjectId("b9"),
"key" : 1,
"value" : 20,
"history" : ISODate("2020-05-16T00:00:00Z")
},
{
"_id" : ObjectId("ba"),
"key" : 1,
"value" : 10,
"history" : ISODate("2020-05-13T00:00:00Z")
},
{
"_id" : ObjectId("bb"),
"key" : 3,
"value" : 50,
"history" : ISODate("2020-05-12T00:00:00Z")
},
{
"_id" : ObjectId("bc"),
"key" : 2,
"value" : 0,
"history" : ISODate("2020-05-13T00:00:00Z")
},
{
"_id" : ObjectId("bd"),
"key" : 2,
"value" : 10,
"history" : ISODate("2020-05-16T00:00:00Z")
}
Now based on the history field you want to group and insert the whole documents in to an array field 'items'. Here $$ROOT variable will be helpful.
So, the aggregation query to achieve the above will be:
db.collection.aggregate([{
$group: {
_id: '$history',
items: {$push: '$$ROOT'}
}
}])
It will result in following output:
{
"_id" : ISODate("2020-05-12T00:00:00Z"),
"items" : [
{
"_id" : ObjectId("bb"),
"key" : 3,
"value" : 50,
"history" : ISODate("2020-05-12T00:00:00Z")
}
]
},
{
"_id" : ISODate("2020-05-13T00:00:00Z"),
"items" : [
{
"_id" : ObjectId("ba"),
"key" : 1,
"value" : 10,
"history" : ISODate("2020-05-13T00:00:00Z")
},
{
"_id" : ObjectId("bc"),
"key" : 2,
"value" : 0,
"history" : ISODate("2020-05-13T00:00:00Z")
}
]
},
{
"_id" : ISODate("2020-05-16T00:00:00Z"),
"items" : [
{
"_id" : ObjectId("b9"),
"key" : 1,
"value" : 20,
"history" : ISODate("2020-05-16T00:00:00Z")
},
{
"_id" : ObjectId("bd"),
"key" : 2,
"value" : 10,
"history" : ISODate("2020-05-16T00:00:00Z")
}
]
}
I hope it helps.

MongoDB aggregation $elemMatch inside $lookup stage

I have a MongoDB collection that is looking like this:
{
players: [
{uuid: "A"},
{uuid: "B"}
]
},
{
players: [
{uuid: "A"},
{uuid: "C"}
]
},
{
players: [
{uuid: "D"},
{uuid: "E"}
]
}
I want to use results of a previous aggregation stage and now find all documents where a player shows up, using a $lookup stage:
from: "collection",
pipeline: [
{
$match: {
players: {
$elemMatch: {
uuid: "$playerId"
}
}
//using "players.uuid": "$playerId" doesn't work either
}
}
],
as: "field"
The input to my $lookup stage looks like this:
{
"playerId" : "A"
}
{
"playerId" : "B"
}
{
"playerId" : "C"
}
This query returns an empty array in field. It seems like $uuid is not getting evaluated correctly, because if I exchange $uuid with a hardcoded value (e.g. A), this query returns results.
I have also tried using the let property, this gave me the same result.
What am I doing wrong?
Using the documents you've provided. I believe this might work for you:
I've used $lookup to do a join onto the collection which holds the playerId, which creates an array called field. I then use $unwind to extract all the array elements from both field and player. Finally I use $cond to crosscheck if both values match.
db.getCollection('foo').aggregate([
{ $lookup : {
from: "bar",
localField: "players.uuid",
foreignField: "playerId",
as: "field"
} },
{ $unwind : "$players" },
{ $unwind : "$field" },
{ $project : {
"players": 1,
"field" : 1,
"isMatch": {
"$cond": [ { "$eq": ["$players.uuid", "$field.playerId"] }, 1, 0 ]
} } }
])
I've purposely left the output verbose..
/* 1 */
{
"_id" : ObjectId("5a7f534b337e8d2b97ff2ffb"),
"players" : {
"uuid" : "A"
},
"field" : {
"_id" : ObjectId("5a7f5374337e8d2b97ff2ffe"),
"playerId" : "A"
},
"isMatch" : 1.0
}
/* 2 */
{
"_id" : ObjectId("5a7f534b337e8d2b97ff2ffb"),
"players" : {
"uuid" : "A"
},
"field" : {
"_id" : ObjectId("5a7f539b337e8d2b97ff2fff"),
"playerId" : "B"
},
"isMatch" : 0.0
}
/* 3 */
{
"_id" : ObjectId("5a7f534b337e8d2b97ff2ffb"),
"players" : {
"uuid" : "B"
},
"field" : {
"_id" : ObjectId("5a7f5374337e8d2b97ff2ffe"),
"playerId" : "A"
},
"isMatch" : 0.0
}
/* 4 */
{
"_id" : ObjectId("5a7f534b337e8d2b97ff2ffb"),
"players" : {
"uuid" : "B"
},
"field" : {
"_id" : ObjectId("5a7f539b337e8d2b97ff2fff"),
"playerId" : "B"
},
"isMatch" : 1.0
}
/* 5 */
{
"_id" : ObjectId("5a7f5356337e8d2b97ff2ffc"),
"players" : {
"uuid" : "A"
},
"field" : {
"_id" : ObjectId("5a7f5374337e8d2b97ff2ffe"),
"playerId" : "A"
},
"isMatch" : 1.0
}
/* 6 */
{
"_id" : ObjectId("5a7f5356337e8d2b97ff2ffc"),
"players" : {
"uuid" : "A"
},
"field" : {
"_id" : ObjectId("5a7f53a8337e8d2b97ff3000"),
"playerId" : "C"
},
"isMatch" : 0.0
}
/* 7 */
{
"_id" : ObjectId("5a7f5356337e8d2b97ff2ffc"),
"players" : {
"uuid" : "C"
},
"field" : {
"_id" : ObjectId("5a7f5374337e8d2b97ff2ffe"),
"playerId" : "A"
},
"isMatch" : 0.0
}
/* 8 */
{
"_id" : ObjectId("5a7f5356337e8d2b97ff2ffc"),
"players" : {
"uuid" : "C"
},
"field" : {
"_id" : ObjectId("5a7f53a8337e8d2b97ff3000"),
"playerId" : "C"
},
"isMatch" : 1.0
}

mongodb $group for nested array element

I have following mongodb collection (questionanswers) for a given question
{
"_id" : { "$oid" : "59cda8b730a0131cfafc9101" },
"questionId" : { "$oid" : "59cb7b85c854036fec173267" },
"userQuestionAnswerDetails" : [ { "srno" : 1, "correct_yn" : false}, {
"srno" : 2, "correct_yn" : false} ]
}
{
"_id" : { "$oid" : "59cdb1b930a0131cfafc9102" },
"questionId" : { "$oid" : "59cb7b85c854036fec173267" },
"userQuestionAnswerDetails" : [ { "srno" : 2, "correct_yn" : false} ]
}
{
"_id" : { "$oid" : "59cdbb1b30a0131cfafc9105" },
"questionId" : { "$oid" : "59cb7b85c854036fec173267" },
"userQuestionAnswerDetails" : [ { "srno" : 2, "correct_yn" : false} ]
}
{
"_id" : { "$oid" : "59cdd26a30a0131cfafc9107" },
"questionId" : { "$oid" : "59cb7b85c854036fec173267" },
"userQuestionAnswerDetails" : [ { "srno" : 1, "correct_yn" : false} ]
}
I need cnt per sr no which is inside nested array element.
Expected is:
Srno ===> cnt
1 ===> 2
2 ===> 3
I am trying with below query
db.questionanswers.aggregate([
{
$match:{
$and: [
{questionId: ObjectId("59cb7b85c854036fec173267")}
]
}
},
{
$group : {_id : {questionId:"$questionId", userQuestionAnswerDetails: "$userQuestionAnswerDetails.srno"}, num_tutorial : {$sum : 1}}
}
])
Above query is giving me something similar to below:
{"_id": {"questionId":"ObjectId(59cb7b85c854036fec173267)", "userQuestionAnswerDetails":[1,2]},"num_tutorial":1}
{"_id":{"questionId":"ObjectId(59cb7b85c854036fec173267)", "userQuestionAnswerDetails":[1]},"num_tutorial":1}
{"_id":{"questionId":"ObjectId(59cb7b85c854036fec173267)", "userQuestionAnswerDetails":[2]},"num_tutorial":2}
which is equivalent to
Srno ===> Cnt
1,2 ===> 1
1 ===> 1
2 ===> 2
How can I modify $group so as to achieve the expected result? Thanks in advance.
You can make it by adding $unwind stage.
db.questionanswers.aggregate([
{
$match:{
$and: [
{questionId: ObjectId("59cb7b85c854036fec173267")}
]
}
}
,{
$unwind: "$userQuestionAnswerDetails"
}
,{
$group : {_id : {questionId:"$questionId", userQuestionAnswerDetails: "$userQuestionAnswerDetails.srno"}, num_tutorial : {$sum : 1}}
}
])
Result:
{ "_id" : { "questionId" : ObjectId("59cb7b85c854036fec173267"), "userQuestionAnswerDetails" : 2 }, "num_tutorial" : 3 }
{ "_id" : { "questionId" : ObjectId("59cb7b85c854036fec173267"), "userQuestionAnswerDetails" : 1 }, "num_tutorial" : 2 }

$avg in mongodb aggregation

Document looks like this:
{
"_id" : ObjectId("361de42f1938e89b179dda42"),
"user_id" : "u1",
"evaluator_id" : "e1",
"candidate_id" : ObjectId("54f65356294160421ead3ca1"),
"OVERALL_SCORE" : 150,
"SCORES" : [
{ "NAME" : "asd", "OBTAINED_SCORE" : 30}, { "NAME" : "acd", "OBTAINED_SCORE" : 36}
]
}
Aggregation function:
db.coll.aggregate([ {$unwind:"$SCORES"}, {$group : { _id : { user_id : "$user_id", evaluator_id : "$evaluator_id"}, AVG_SCORE : { $avg : "$SCORES.OBTAINED_SCORE" }}} ])
Suppose if there are two documents with same "user_id" (say u1) and different "evaluator_id" (say e1 and e2).
For example:
1) Average will work like this ((30 + 20) / 2 = 25). This is working for me.
2) But for { evaluator_id : "e1" } document, score is 30 for { "NAME" : "asd" } and { evaluator_id : "e2" } document, score is 0 for { "NAME" : "asd" }. In this case, I want the AVG_SCORE to be 30 only (not (30 + 0) / 2 = 15).
Is it possible through aggregation??
Could any one help me out.
It's possible by placing a $match between the $unwind and $group aggregation pipelines to first filter the arrays which match the specified condition to include in the average computation and that is, score array where the obtained score is not equal to 0 "SCORES.OBTAINED_SCORE" : { $ne : 0 }
db.coll.aggregate([
{
$unwind: "$SCORES"
},
{
$match : {
"SCORES.OBTAINED_SCORE" : { $ne : 0 }
}
},
{
$group : {
_id : {
user_id : "$user_id",
evaluator_id : "$evaluator_id"
},
AVG_SCORE : {
$avg : "$SCORES.OBTAINED_SCORE"
}
}
}
])
For example, the aggregation result for this document:
{
"_id" : ObjectId("5500aaeaa7ef65c7460fa3d9"),
"user_id" : "u1",
"evaluator_id" : "e1",
"candidate_id" : ObjectId("54f65356294160421ead3ca1"),
"OVERALL_SCORE" : 150,
"SCORES" : [
{
"NAME" : "asd",
"OBTAINED_SCORE" : 0
},
{
"NAME" : "acd",
"OBTAINED_SCORE" : 36
}
]
}
will yield:
{
"result" : [
{
"_id" : {
"user_id" : "u1",
"evaluator_id" : "e1"
},
"AVG_SCORE" : 36
}
],
"ok" : 1
}

mongo shell: return only 1 element of nested array

I have the following data on a mongodb:
{
"name" : "bla",
"log" : [
{
"A" : 1,
"B" : 10
},
{
"A" : 2,
"B" : 20
}
]
}
I understand how to return all values of A from the mongoshell:
db.test.find({},{'name':1,'log.A':1})
{ "_id" : ObjectId("52712539c99a2fc6f6088cd4"), "name" : "bla", "log" : [ { "A" : 1 }, { "A" : 2 } ] }
but how can I limit the output of A to only the first element? This is the output that I extect to have:
{ "_id" : ObjectId("52712539c99a2fc6f6088cd4"), "name" : "bla", "log.A" : 1, "log.B":10}
I don't mind in having log.A or just A, or even having some [ ] in the output, as soon as it is always only one entry for A and for B
how can I do it?
You can use the $slice array projection operator to do that:
db.test.find({}, {name: 1, 'log.A':1, log: {$slice: 1}})
Outputs:
{ "_id" : ObjectId("..."), "name" : "bla", "log" : [ { "A" : 1 } ] }