Redact in mongodb seems obscure to me - mongodb

I'm fighting with redact right now and I'm not sure to understand it.
I just read the docs and tried to use redact on a collection grades (it comes from mongodb online training)
A document in the collection "grades" looks like this :
{
"_id" : ObjectId("50b59cd75bed76f46522c34e"),
"student_id" : 0,
"class_id" : 2,
"scores" : [
{
"type" : "exam",
"score" : 57.92947112575566
},
{
"type" : "quiz",
"score" : 21.24542588206755
},
{
"type" : "homework",
"score" : 68.19567810587429
},
{
"type" : "homework",
"score" : 67.95019716560351
},
{
"type" : "homework",
"score" : 18.81037253352722
}
]
}
I use the following query :
db.grades.aggregate([
{ $match: { student_id: 0 } },
{
$redact: {
$cond: {
if: { $eq: [ "$type" , "exam" ] },
then: "$$PRUNE",
else: "$$DESCEND"
}
}
}
]
);
With this query, each type an exam is found, this sub document should be excluded. And it works, the result is:
{
"_id" : ObjectId("50b59cd75bed76f46522c34e"),
"student_id" : 0,
"class_id" : 2,
"scores" : [
{
"type" : "quiz",
"score" : 21.24542588206755
},
{
"type" : "homework",
"score" : 68.19567810587429
},
{
"type" : "homework",
"score" : 67.95019716560351
},
{
"type" : "homework",
"score" : 18.81037253352722
}
]
}
but if I invert the condition, I expect that only exams are kept in the result :
if: { $eq: [ "$type" , "exam" ] },
then: "$$DESCEND",
else: "$$PRUNE"
however the result is empty.
I don't understand why subdocument of type "exam" are not included.

The $redact stage starts at the root document and its fields, and only when that document fulfills the condition to $$DESCEND, it examines the sub-documents included in that document. That means the first thing $redact does with your document is examine this:
{
"_id" : ObjectId("50b59cd75bed76f46522c34e"),
"student_id" : 0,
"class_id" : 2,
"scores" : [] // Some array. I will look at this later.
}
It doesn't even find a type field here, so $eq: [ "$type" , "exam" ] is false. What did you tell $redact to do when the condition is false? else: "$$PRUNE", so the whole document is pruned before the sub-documents are examined.
As a workaround, test if $type is either "exam" or doesn't exist. You didn't explicitly ask for a working solution, so I will leave it as an exercise to you to figure out how to do this.

Related

Aggreate in MongoDb for documents embedded in list of values

I have in MongDb data as below
{
"_id" : 7,
"name" : "Salena Olmos",
"scores" : [
{
"type" : "exam",
"score" : 90.37826509157176
},
{
"type" : "quiz",
"score" : 42.48780666956811
},
{
"type" : "homework",
"score" : 67.18328596625217
},
{
"type" : "homework",
"score" : 96.52986171633331
}
]
}
all I want to sum "scores" for "type" : "homework" as
{ "_id" : 7, "score" : 163.71314768258548}
I have written my query using aggregate and filter condition as below
db.students.aggregate([
{
"$group" :
{
"_id":"$_id",
"score":{$sum: {$filter: {
input: "$scores",
as: "x",
cond: {$eq : ["$$x.type", "homework"]}
}
}
}
}
}
])
but all it gives me as "0" as sum value as :
{ "_id" : 7, "score" : 0 }
Please help me , I am new to MongoDb and trying to learn by solving some exercise problems.
Thanks
You can use below aggregation. Use $let to extract the score from matching scores.
db.students.aggregate({
"$project":{
"score":{
"$sum":{
"$let":{
"vars":{
"mscores":{
"$filter":{
"input":"$scores",
"as":"x",
"cond":{"$eq":["$$x.type","homework"]}
}
}
},
"in":"$$mscores.score"
}
}
}
}
})
I have gone through the MongoDb docs and come up with below solution
db.students.aggregate([
{$unwind: "$scores"},
{$match:{"scores.type" : "homework"}},
{$group : {"_id":"$_id", "score":{$sum: "$scores.score"}}}
])

Find sum of fields inside array in MongoDB

I have a data as follows:
> db.PQRCorp.find().pretty()
{
"_id" : 0,
"name" : "Ancy",
"results" : [
{
"evaluation" : "term1",
"score" : 1.463179736705023
},
{
"evaluation" : "term2",
"score" : 11.78273309957772
},
{
"evaluation" : "term3",
"score" : 6.676176060654615
}
]
}
{
"_id" : 1,
"name" : "Mark",
"results" : [
{
"evaluation" : "term1",
"score" : 5.89772766299929
},
{
"evaluation" : "term2",
"score" : 12.7726680028769
},
{
"evaluation" : "term3",
"score" : 2.78092882672992
}
]
}
{
"_id" : 2,
"name" : "Jeff",
"results" : [
{
"evaluation" : "term1",
"score" : 36.78917882992872
},
{
"evaluation" : "term2",
"score" : 2.883687879200287
},
{
"evaluation" : "term3",
"score" : 9.882668212003763
}
]
}
What I want to achieve is ::Find employees who failed in aggregate (term1 + term2 + term3)
What I am doing and eventually getting is:
db.PQRCorp.aggregate([
{$unwind:"$results"},
{ $group: {_id: "$id",
'totalTermScore':{ $sum:"$results.score" }
}
}])
OUTPUT:{ "_id" : null, "totalTermScore" : 90.92894831067625 }
Simply I am getting a output of a flat sum of all scores. What I want is, to sum terms 1 , 2 and 3 separately for separate employees.
Please can someone help me. I am new to MongoDB (quite evident though).
You do not need to use $unwind and $group here... A simple $project query can $sum your entire score...
db.PQRCorp.aggregate([
{ "$project": {
"name": 1,
"totalTermScore": {
"$sum": "$results.score"
}
}}
])

How can I do match after second level unwind in mongodb?

I am working on a software that uses MongoDB as a database. I have a collection like this (this is just one document)
{
"_id" : ObjectId("5aef51e0af42ea1b70d0c4dc"),
"EndpointId" : "89799bcc-e86f-4c8a-b340-8b5ed53caf83",
"DateTime" : ISODate("2018-05-06T19:05:04.574Z"),
"Url" : "test",
"Tags" : [
{
"Uid" : "E2:02:00:18:DA:40",
"Type" : 1,
"DateTime" : ISODate("2018-05-06T19:05:04.574Z"),
"Sensors" : [
{
"Type" : 1,
"Value" : NumberDecimal("-98")
},
{
"Type" : 2,
"Value" : NumberDecimal("-65")
}
]
},
{
"Uid" : "12:3B:6A:1A:B7:F9",
"Type" : 1,
"DateTime" : ISODate("2018-05-06T19:05:04.574Z"),
"Sensors" : [
{
"Type" : 1,
"Value" : NumberDecimal("-95")
},
{
"Type" : 2,
"Value" : NumberDecimal("-59")
},
{
"Type" : 3,
"Value" : NumberDecimal("12.939770381907275")
}
]
}
]
}
and I want to run this query on it.
db.myCollection.aggregate([
{ $unwind: "$Tags" },
{
$match: {
$and: [
{
"Tags.DateTime": {
$gte: ISODate("2018-05-06T19:05:02Z"),
$lte: ISODate("2018-05-06T19:05:09Z"),
},
},
{ "Tags.Uid": { $in: ["C1:3D:CA:D4:45:11"] } },
],
},
},
{ $unwind: "$Tags.Sensors" },
{ $match: { "$Tags.Sensors.Type": { $in: [1, 2] } } },
{
$project: {
_id: 0,
EndpointId: "$EndpointId",
TagId: "$Tags.Uid",
Url: "$Url",
TagType: "$Tags.Type",
Date: "$Tags.DateTime",
SensorType: "$Tags.Sensors.Type",
Value: "$Tags.Sensors.Value",
},
},
])
the problem is, the second match (that checks $Tags.Sensors.Type) doesn't work and doesn't affect the result of the query.
How can I solve that?
If this is not the right way, what is the right way to run these conditions?
The $match stage accepts field names without a leading $ sign. You've done that correctly in your first $match stage but in the second one you write $Tags.Sensors.Type. Simply removing the leading $ sign should make your query work.
Mind you, the whole thing can be a bit simplified (and some beautification doesn't hurt, either):
You don't need to use $and in your example since it's assumed by default if you specify more than one criterion in a filter.
The $in that you use for the Tags.Sensors.Type filter can be a simple : kind of equality operator unless you have more than one element in the list of acceptable values.
In the $project stage, instead of (kind of) duplicating identical field names you can use the <field>: 1 syntax unless the order of the fields matters.
So the final query would be something like this.
db.myCollection.aggregate([
{
"$unwind" : "$Tags"
},
{
"$match" : {
"Tags.DateTime" : { "$gte" : ISODate("2018-05-06T19:05:02Z"), "$lte" : ISODate("2018-05-06T19:05:09Z") },
"Tags.Uid" : { "$in" : ["C1:3D:CA:D4:45:11"] }
}
}, {
"$unwind" : "$Tags.Sensors"
}, {
"$match" : {
"Tags.Sensors.Type" : { "$in" : [1,2] }
}
},
{
"$project" : {
"_id" : 0,
"EndpointId" : 1,
"TagId" : "$Tags.Uid",
"Url" : 1,
"TagType" : "$Tags.Type",
"Date" : "$Tags.DateTime",
"SensorType" : "$Tags.Sensors.Type",
"Value" : "$Tags.Sensors.Value"
}
}])

How to find minimum value in aggregate $group

I have below collection structure and I want to find minimum score for each student.
>db.students.findOne()
{
"_id" : 0,
"name" : "aimee Zank",
"scores" : [
{
"type" : "exam",
"score" : 1.463179736705023
},
{
"type" : "quiz",
"score" : 11.78273309957772
},
{
"type" : "homework",
"score" : 6.676176060654615
},
{
"type" : "homework",
"score" : 35.8740349954354
}
]
}
I use below aggregate command
db.students.aggregate([
{
$group: {_id: "$_id" , min: {$min: '$scores.score'}}
}
])
below is the output:
{ "_id" : 199, "min" : [ 82.11742562118049, 49.61295450928224, 28.86823689842918, 5.861613903793295 ] }
{ "_id" : 198, "min" : [ 11.9075674046519, 20.51879961777022, 55.85952928204192, 64.85650354990375 ] }
{ "_id" : 95, "min" : [ 8.58858127638702, 88.40377630359677, 25.71387474240768, 23.73786528217532 ] }
{ "_id" : 11, "min" : [ 78.42617835651868, 82.58372817930675, 87.49924733328717, 15.81264595052612 ] }
{ "_id" : 94, "min" : [ 6.867644836612586, 63.4908039680606, 85.41865347441522, 26.82623527074511 ] }
it returns all scores for each student instead of the minimum one. What wrong with my query command? I am using mongo 3.4.
After some searching, I found that the solution is to add $unwind on scores.score. The complete command is:
stus = db.students.aggregate([
{
"$unwind": "$scores"
},
{
$group: {_id: "$_id" , minScore: {$min: '$scores.score'}}
}
])

Mongo DB - Group By and Having Implementation

I'm trying to write a Mongo DB query where I do the following,
JSON list - Representing documents in my collection,
Industry[]:
{
"_id" : ObjectId("57aa6058be0c853c8cee34cd"),
"options": {
"paramList" : [
{
"name" : "industryCategory",
"value" : "Travel",
"mandatory" : true
},
{
"name" : "someOtherThing",
"value" : "dontMind",
"mandatory" : true
}
]
}
"mostRecent" : true
},
{
"_id" : ObjectId("57aa6058be0c853c8cee34cd"),
"options": {
"paramList" : [
{
"name" : "industryCategory",
"value" : "Dining",
"mandatory" : true
},
{
"name" : "someOtherThing",
"value" : "dontMind",
"mandatory" : true
}
]
}
"mostRecent" : true
},
{
"_id" : ObjectId("57aa6058be0c853c8cee34cd"),
"options": {
"paramList" : [
{
"name" : "industryCategory",
"value" : "Travel",
"mandatory" : true
},
{
"name" : "someOtherThing",
"value" : "dontMind",
"mandatory" : true
}
]
}
"mostRecent" : true
}
I'm trying to group and get a count of values for those paramList - values where name is industryCategory. Essentially, the output I am looking for is something like this,
Travel: 2
Dining: 1
I'm trying to do the following,
Industry is the name of the collection,
db.Industry.aggregate (
[
{$match: {mostRecent: true}},
{$group: {
_id: '$options.paramList.value',
count: {$sum: 1}
}},
{$match: {'options.paramList.name': 'industryCategory'}}
])
I'm getting an empty result. Please suggest what I can do
Well, I didn't have anyone answering. But, in the meanwhile I managed to figure it out myself. Posting the answer below,
aggregate (
[
{"$match": {mostRecent: true}},
{"$unwind" : "$options.paramList"},
{"$group" :
{
_id: "$options.paramList.value",
count: {$sum : 1}
}
}
]
Essentially, use unwind wherever you have to iterate over arrays (a list of sub-documents). This has been a productive learning, at least for me.