Mongodb update array of objects - mongodb

I have a document to Mongodb update array of objects like this:
{
"_id" : 5,
"quizzes" : [
{ "wk" : 1, "score" : 10 },
{ "wk" : 2, "score" : 8 },
{ "wk" : 5, "score" : 8 }
]
}
And I want to add new field in each object of quizzes array.
The expected result should be
{
"_id" : 5,
"quizzes" : [
{ "wk" : 1, "score" : 10, "day": 1 },
{ "wk" : 2, "score" : 8, "day": 2 },
{ "wk" : 5, "score" : 8, "day": 3 }
]
}
Any solution for this.

You can use Aggregation Framework:
db.col.aggregate([
{
$unwind: {
path: "$quizzes",
includeArrayIndex: "quizzes.day"
}
},
{
$group: {
_id: "$_id",
quizzes: {
$push: {
"score" : "$quizzes.score",
"wk" : "$quizzes.wk",
"day" : { $add: [ "$quizzes.day", 1 ] }
}
}
}
},
{
$out: "col"
}
])
To assign indexes to each element you can use $unwind with includeArrayIndex option. Those indexes start with 0 so we have to use $add to start with 1. Then you can group back by your _id property and use $out to save aggregation results to your collection.

Related

MongoDB aggregate array of objects together by object id and count occurences

I'm trying to figure out what I'm doing wrong, I have collected the following, "Subset of data", "Desired output"
This is how my data objects look
[{
"survey_answers": [
{
"id": "9ca01568e8dbb247", // As they are, this is the key to groupBy
"option_answer": 5, // Represent the index of the choosen option
"type": "OPINION_SCALE" // Opinion scales are 0-10 (meaning elleven options)
},
{
"id": "ba37125ec32b2a99",
"option_answer": 3,
"type": "LABELED_QUESTIONS" // Labeled questions are 0-x (they can change it from survey to survey)
}
],
"survey_id": "test"
},
{
"survey_answers": [
{
"id": "9ca01568e8dbb247",
"option_answer": 0,
"type": "OPINION_SCALE"
},
{
"id": "ba37125ec32b2a99",
"option_answer": 3,
"type": "LABELED_QUESTIONS"
}
],
"survey_id": "test"
}]
My desired output is:
[
{
id: '9ca01568e8dbb247'
results: [
{ _id: 5, count: 1 },
{ _id: 0, count: 1 }
]
},
{
id: 'ba37125ec32b2a99'
results: [
{ _id: 3, count: 2 }
]
}
]
Active query
Model.aggregate([
{
$match: {
'survey_id': survey_id
}
},
{
$unwind: "$survey_answers"
},
{
$group: {
_id: "$survey_answers.option_answer",
count: {
$sum: 1
}
}
}
])
Current output
[
{
"_id": 0,
"count": 1
},
{
"_id": 3,
"count": 2
},
{
"_id": 5,
"count": 1
}
]
I added your records to my db. Post that I tried your commands one by one.
$unwind results you similar to -
> db.survey.aggregate({$unwind: "$survey_answers"})
{ "_id" : ObjectId("5c3859e459875873b5e6ee3c"), "survey_answers" : { "id" : "9ca01568e8dbb247", "option_answer" : 5, "type" : "OPINION_SCALE" }, "survey_id" : "test" }
{ "_id" : ObjectId("5c3859e459875873b5e6ee3c"), "survey_answers" : { "id" : "ba37125ec32b2a99", "option_answer" : 3, "type" : "LABELED_QUESTIONS" }, "survey_id" : "test" }
{ "_id" : ObjectId("5c3859e459875873b5e6ee3d"), "survey_answers" : { "id" : "9ca01568e8dbb247", "option_answer" : 0, "type" : "OPINION_SCALE" }, "survey_id" : "test" }
{ "_id" : ObjectId("5c3859e459875873b5e6ee3d"), "survey_answers" : { "id" : "ba37125ec32b2a99", "option_answer" : 3, "type" : "LABELED_QUESTIONS" }, "survey_id" : "test" }
I am not adding code for match since that is okay in your query as well
The grouping would be -
> db.survey.aggregate({$unwind: "$survey_answers"},{$group: { _id: { 'optionAnswer': "$survey_answers.option_answer", 'id':"$survey_answers.id"}, count: { $sum: 1}}})
{ "_id" : { "optionAnswer" : 0, "id" : "9ca01568e8dbb247" }, "count" : 1 }
{ "_id" : { "optionAnswer" : 3, "id" : "ba37125ec32b2a99" }, "count" : 2 }
{ "_id" : { "optionAnswer" : 5, "id" : "9ca01568e8dbb247" }, "count" : 1 }
You can group on $survey_answers.id to bring it into projection.
The projection is what you're missing in your query -
> db.survey.aggregate({$unwind: "$survey_answers"},{$group: { _id: { 'optionAnswer': "$survey_answers.option_answer", 'id':'$survey_answers.id'}, count: { $sum: 1}}}, {$project : {answer: '$_id.optionAnswer', id: '$_id.id', count: '$count', _id:0}})
{ "answer" : 0, "id" : "9ca01568e8dbb247", "count" : 1 }
{ "answer" : 3, "id" : "ba37125ec32b2a99", "count" : 2 }
{ "answer" : 5, "id" : "9ca01568e8dbb247", "count" : 1 }
Further you can add a group on id and add results to a set. And your final query would be -
db.survey.aggregate(
{$unwind: "$survey_answers"},
{$group: {
_id: { 'optionAnswer': "$survey_answers.option_answer", 'id':'$survey_answers.id'},
count: { $sum: 1}
}},
{$project : {
answer: '$_id.optionAnswer',
id: '$_id.id',
count: '$count',
_id:0
}},
{$group: {
_id:{id:"$id"},
results: { $addToSet: {answer: "$answer", count: '$count'} }
}},
{$project : {
id: '$_id.id',
answer: '$results',
_id:0
}})
Hope this helps.

Mongo $filter (aggregation) get only one element of array

I do some filter with mongodb but it return all of the data in my array. But i only want to get the specific element from that array. I cant find it in the document.
db.sales.aggregate([
{
$project: {
items: {
$filter: {
input: "$items",
as: "item",
cond: { $gte: [ "$$item.price", 100 ] }
}
}
}
}
])
Run above command I will this is result
{
"_id" : 0,
"items" : [
{ "item_id" : 2, "quantity" : 1, "price" : 240 }
]
}
Question is I only want to get the price
{
"_id" : 0,
"items" : [
{ "price" : 240 }
]
}
or even
{
"price" : 240
}
How to do it?
You actually need $map to "alter" the array elements returned, as $filter only "selects" the array elements that "match". Try to run the below code.
ds.sales.aggregate([
{
$project: {
items: {
$map: {
input: {
$filter: {
input: "$items",
as: "item",
cond: { $gte: [ "$$item.price", 100 ] }
}
},
"as": "a",
"in": {
"price": "$$a.price"
}
}
}
}
}], function (err, list) {
...
I don't know your whole data looks like, if your data looks like this
{
"_id" : 0,
"items" : [
{
"item_id" : 1,
"quantity" : 5,
"price" : 80
},
{
"item_id" : 2,
"quantity" : 1,
"price" : 240
},
{
"item_id" : 3,
"quantity" : 4,
"price" : 320
}
]
}
Just try this
> db.sales.aggregate([
{'$unwind': '$items'},
{'$project': {'price': '$items.price'}},
{'$match' : {'price': {'$gte': 100 }}}
])
{ "_id" : 0, "price" : 240 }
{ "_id" : 0, "price" : 320 }
$unwind
{'items': [{'item_id': 1}, {'item_id': 2}]}
after $unwind
{'items': {'item_id': 1}}
{'items': {'item_id': 2}}
$project
This can choose which field you want ( or just remove which field you don't want) and rename a field to what you want.
{'items': {'item_id': 1}}
after $project
{'renamefor__item_id': 1}
$match
Just see the previous link for more detail. My English is not very good:(

Mongo db project a field not in the group

I'm wondering if I can project field is not exist in the group.
For example I have the below collections and I want the name of students who has highest soccer across all students
{ "_id" : 1, "Snmae" : Alex, "score" : 97}
{ "_id" : 2, "Snmae" : Sara, "socre" : 97 }
{ "_id" : 3, "Snmae" : Sam, "socre" : 93 }
{ "_id" : 4, "Snmae" : Dan, "socre" : 77 }
db.stuudent.aggregate(
{$project:{_id:0,sname:1,score:1}},
{ $group : { _id : "", High_Score: { $max: "$score" }}}
);
The desire output is
Sname: Alex , score: 97
Sname: Sara , score: 97
Data:
{ "_id" : 1.0, "Sname" : "Alex", "score" : 97.0 },
{ "_id" : 2.0, "Sname" : "Sara", "score" : 97.0 },
{ "_id" : 3.0, "Sname" : "Sam", "score" : 93.0 },
{ "_id" : 4.0, "Sname" : "Dan", "score" : 77.0 }
Query:
db.collection.aggregate([
{
$group: {
_id: "$score",
"names": { $push: "$Sname" }
}
},
{ $sort: { "_id": -1 } },
{ $limit: 1},
{ $unwind: "$names" },
{
$project: {
"Sname": "$names",
"score": "$_id",
"_id": 0
}
}
])
Explanation:
$group - groups the students by score.
$sort - sorts the documents by score in the descending direction.
$limit - takes only first document (document with highest score value).
$unwind - splits "names" array into separated documents.
$project - generates the final results (documents with defined shape).

calculate the difference betwen Two values on the same document

I am new to Mongo aggregation.I want to calculate the difference betwen Two values (The last collection for each day -The first collection for each day).the data base record data every 5 mn for many ressource name.The structucture of the document is :
{
_id : ObjectId("5820511a95d447ed648b45d6"),
DeviceName : "OLT01FTV",
ResourceName : "CM MAC:00-07-11-11-39-20",
CollectionTime : ISODate("2016-11-07T09:30:00.000+01:00"),
GranularityPeriod : 5,
A : 0,
B: 17,
C: 4,
D: 21,
E: 3,
F: 0
}
A,B...F are the differrent counters.
Below, the illustration of that I'm trying to have :
result
([
{ "$match": {
"CollectionTime": {
$gte: ISODate("2016-09-05T00:00:00.000Z"),
$lt: ISODate("2016-10-07T00:00:00.000Z")
}
}},
{ "$unwind": "$u2000" },
{ "$group": {
"_id": null,
"firstUC": { "$first": "$UC" },
"lastUC": { "$last": "$UC" },
"firstSM-MISS": { "$first": "$SM-MISS" },
"lastSM-MISS": { "$last": "$SM-MISS" }
}},
{ "$project": {
"diff": {
"$divide": [
{ "$subtract": [ "$firstUC", "$lastUC" ] },
{ "$subtract": [ "$firstSM-MISS", "$lastSM-MISS" ] }
]
}
}}
])
This will get you the difference between the 'A' values for your above scenario. You can add the other fields if you want to get the difference for them also.
db.collection.aggregate([
{ "$match": {
"CollectionTime": {
$gte: ISODate("2016-11-01T00:00:00.000Z"),
$lt: ISODate("2016-11-30T00:00:00.000Z")
}
}},
{ "$sort": { "CollectionTime": 1 } },
{ "$group": {
"_id": null,
"firstA": { "$first": "$A" },
"lastA": { "$last": "$A" }
}},
{ "$project": {
_id: 0,
diffA: {
$subtract: [ "$lastA", "$firstA"]
}
}}
])
* EDIT *
So I'm using the following sample documents I created with the following to match your schema:
// Create 3 Documents 1 second apart
for (var i = 1; i < 4; i++) {
db.foo.insert({
DeviceName : "OLT01FTV",
ResourceName : "CM MAC:00-07-11-11-39-20",
CollectionTime : new Date(),
GranularityPeriod : 5,
A : 1*i,
B: 2*i,
C: 3*i,
D: 4*i,
E: 5*i,
F: 6*i
})
sleep(1000); // To add a delay between insertions so we can visibly see the date difference
}
This results in the following 3 documents being created:
> db.foo.find().pretty()
{
"_id" : ObjectId("582b1a6ced19a7334a5dee31"),
"DeviceName" : "OLT01FTV",
"ResourceName" : "CM MAC:00-07-11-11-39-20",
"CollectionTime" : ISODate("2016-11-15T14:23:40.934Z"),
"GranularityPeriod" : 5,
"A" : 1,
"B" : 2,
"C" : 3,
"D" : 4,
"E" : 5,
"F" : 6
}
{
"_id" : ObjectId("582b1a6ded19a7334a5dee32"),
"DeviceName" : "OLT01FTV",
"ResourceName" : "CM MAC:00-07-11-11-39-20",
"CollectionTime" : ISODate("2016-11-15T14:23:41.936Z"),
"GranularityPeriod" : 5,
"A" : 2,
"B" : 4,
"C" : 6,
"D" : 8,
"E" : 10,
"F" : 12
}
{
"_id" : ObjectId("582b1a6eed19a7334a5dee33"),
"DeviceName" : "OLT01FTV",
"ResourceName" : "CM MAC:00-07-11-11-39-20",
"CollectionTime" : ISODate("2016-11-15T14:23:42.939Z"),
"GranularityPeriod" : 5,
"A" : 3,
"B" : 6,
"C" : 9,
"D" : 12,
"E" : 15,
"F" : 18
}
The first step of the aggregation pipeline will match on all documents between the date range - which I set to beginning of November... so no worry there, then the sorting will sort by collection time:
After the grouping we have one document with the firstA and lastA value:
{ "_id" : null, "firstA" : 1, "lastA" : 3 }
And finally - perform the subtract in the projection and hide the ID field:
{ "diffA" : 2 }

aggregation and display details in mongodb

I have been learning MongoDB, while doing so I tried to implement the aggregation property for my database collection. I grouped the details of the employee based on their age and by using match function, my question is it possible to display the other key-value once they pass the age criteria?
db.employee.aggregate([
{ $match: { age: { $gte: 23 } } },
{
$group: {
_id:'$age',
total: { $sum: 1 },
name: { $addToSet: '$name' }
}
}
])
and the output was like this
{ "_id" : 27, "total" : 2, "name" : [ "indhu", "logesh" ] }
{ "_id" : 26, "total" : 1, "name" : [ "keerthana" ] }
{ "_id" : 25, "total" : 1, "name" : [ "sneha" ] }
{ "_id" : 24, "total" : 1, "name" : [ "dhiva" ] }
{ "_id" : 23, "total" : 1, "name" : [ "elango" ] }
where _id denotes their age.