extract subarray value in mongodb - mongodb

MongoDB noob here...
I have a collection as follows...
> db.students.find({_id:22},{scores:[{type:'exam'}]}).pretty()
{
"_id" : 22,
"scores" : [
{
"type" : "exam",
"score" : 75.04996547553947
},
{
"type" : "quiz",
"score" : 10.23046475899236
},
{
"type" : "homework",
"score" : 96.72520512117761
},
{
"type" : "homework",
"score" : 6.488940333376703
}
]
}
how do I display only the quiz score via mongo shell?

You have some syntax in your original example which probably isn't doing what you expect .. that is, it looks like your intent was to only match scores for a specific type ('exam' in your example, 'quiz' by your description).
Below are some examples using the MongoDB 2.2 shell.
$elemMatch projection
You can use the $elemMatch projection to return the first matching element in an array:
db.students.find(
// Search criteria
{ '_id': 22 },
// Projection
{ _id: 0, scores: { $elemMatch: { type: 'exam' } }}
)
The result will be the matching element of the array for each document, eg:
{ "scores" : [ { "type" : "exam", "score" : 75.04996547553947 } ] }
Aggregation Framework
If you want to display more than one matching value or reshape the result document instead of returning the full matching array element, you can use the Aggregation Framework:
db.students.aggregate(
// Initial document match (uses index, if a suitable one is available)
{ $match: {
'_id': 22, 'scores.type' : 'exam'
}},
// Convert embedded array into stream of documents
{ $unwind: '$scores' },
// Only match scores of interest from the subarray
{ $match: {
'scores.type' : 'exam'
}},
// Note: Could add a `$group` by _id here if multiple matches are expected
// Final projection: exclude fields with 0, include fields with 1
{ $project: {
_id: 0,
score: "$scores.score"
}}
)
The result in this case includes would be:
{ "result" : [ { "score" : 75.04996547553947 } ], "ok" : 1 }

Related

I need limited nested array in mongodb document

I have a document like
{
"deviceId" : "1106",
"orgId" : "5ffe9fe1c9e77c0006f0aad3",
"values" : [
{
"paramVal" : 105.0,
"dateTime" : ISODate("2021-05-05T09:18:08.000Z")
},
{
"paramVal" : 110.0,
"dateTime" : ISODate("2021-05-05T09:18:08.000Z")
},
{
"paramVal" : 115.0,
"dateTime" : ISODate("2021-05-05T10:18:08.000Z")
},
{
"paramVal" : 125.0,
"dateTime" : ISODate("2021-05-05T11:18:08.000Z")
},
{
"paramVal" : 135.0,
"dateTime" : ISODate("2021-05-05T12:18:08.000Z")
}
]
}
Now I need to filter a document which I can do easily with match or find but in that document the subarray i.e. values should have latest 2 values because in future the count can be more than 100.
the output should be like
{
"deviceId" : "1106",
"orgId" : "5ffe9fe1c9e77c0006f0aad3",
"values" : [
{
"paramVal" : 125.0,
"dateTime" : ISODate("2021-05-05T11:18:08.000Z")
},
{
"paramVal" : 135.0,
"dateTime" : ISODate("2021-05-05T12:18:08.000Z")
}
]
}
Try $slice operator, to select number of elements, pass negative value to select documents from below/last elements,
db.collection.aggregate([
{ $set: { values: { $slice: ["$values", -2] } } }
])
Playground
I need for the array values in sorted order by date
There is no straight way to do this, check the below aggregation query, but it will cause the performance issues, i would suggest to change you schema structure to manage this data order by date,
$unwind deconstruct values array
$sort by dateTime in descending order
$group by _id and reconstruct values array and return other required fields
$slice to select number of elements, pass negative value to select documents from below/last elements
db.collection.aggregate([
{ $unwind: "$values" },
{ $sort: { "values.dateTime": -1 } },
{
$group: {
_id: "$_id",
deviceId: { $first: "$deviceId" },
orgId: { $first: "$orgId" },
values: { $push: "$values" }
}
},
{ $set: { values: { $slice: ["$values", 2] } } }
])
Playground

Calculate average of ratings in array, then add field to original document in MongoDB

I have a documents that have a field called ratings. This is an array of objects, each object containing userId and ratingValue
ratings: Array
0: Object
userId: "uidsample1"
ratingValue: 5
1: Object
userId:"uidsample2"
ratingValue:1.5
I want to do an aggregation pipeline to calculate the new average when one of the ratings in the array is updated or added. Then, I want to put that value in the document as a new field called averageRating.
I have tried unwinding, then $ add field of $avg : "ratings.ratingValue" but it adds to the unwinded documents and doesnt get the average. It looks something like this (not exactly since testing on compass)
db.test.aggregate{
[
{
$unwind: {
path: "$ratings"
}
},
{
$addFields {
averageRating: {
$avg: "$ratings.ratingValue"
}
}
}
]
}
What's a good query structure for this ?
you don't actually need to $unwind and $group to calculate the average, these operations are costly
you can simply $addFields with $avg
db.col.aggregate([
{$addFields : {averageRating : {$avg : "$ratings.ratingValue"}}}
])
sample collection and aggregation
> db.t62.drop()
true
> db.t62.insert({data : {ratings : [{val : 1}, {val : 2}]}})
WriteResult({ "nInserted" : 1 })
> db.t62.find()
{ "_id" : ObjectId("5c44d9719d56bf65be5ab2e6"), "data" : { "ratings" : [ { "val" : 1 }, { "val" : 2 } ] } }
> db.t62.aggregate([{$addFields : {avg : {$avg : "$data.ratings.val"}}}])
{ "_id" : ObjectId("5c44d9719d56bf65be5ab2e6"), "data" : { "ratings" : [ { "val" : 1 }, { "val" : 2 } ] }, "avg" : 1.5 }
Use $group after $unwind as below to calculate the averageRating. Aggregate is a read operation. You need to update the doc afterward.
[
{
'$unwind': {
'path': '$ratings'
}
}, {
'$group': {
'_id': '$_id',
'averageRating': {
'$avg': '$ratings.ratingValue'
}
}
}
]

Summing a value of a key over multiple documents in MongoDB

I have a collection named users with the following structure to its documents
{
"_id" : <user_id>,
"NAME" : "ABC",
"TIME" : 53.0,
"OBJECTS" : 1
},
{
"_id" : <user_id>,
"NAME" : "ABCD",
"TIME" : 353.0,
"OBJECTS" : 70
}
Now, I want to sum the value of OBJECTS over the entire collection and return the value along with the objects.
Something like this
{
{
"_id" : <user_id>,
"NAME" : "ABC",
"TIME" : 53.0,
"OBJECTS" : 1
},
{
"_id" : <user_id>,
"NAME" : "ABCD",
"TIME" : 353.0,
"OBJECTS" : 70
},
"TOTAL_OBJECTS": 71
}
Or any way wherein I don't have to compute on the received object and can directly access from it. Now, I've tried looking this up but I found none where the hierarchy of the existing documents isn't destroyed.
You can use $group specifying null as a grouping id. You'll gather all documents into one array (using $$ROOT variable) and another field can represent a sum of OBJECT like below:
db.users.aggregate([
{
$group: {
_id: null,
documents: { $push: "$$ROOT" },
TOTAL_OBJECTS: { $sum: "$OBJECTS" }
}
}
])
db.users.aggregate(
// Pipeline
[
// Stage 1
{
$group: {
_id: null,
TOTAL_OBJECTS: {
$sum: '$OBJECTS'
},
documents: {
$addToSet: '$$CURRENT'
}
}
},
]
);
Into above aggregate query I have pushed all documents into an array using $addToSet operator as a part of $group stage of aggregate operation

how to insert an array field (brand new) while updating a document (MongoDB)

I have a TEST collection like this:
{
"_id" : 1.0,
"tags" : [
"technology"
]
}
now I am trying to insert another array field during the update. e.g. query is:
db.TEST.update(
{ _id: 1.0 },
{ $set:
{
"musings.0.rating": 2
}
}
)
expecting the collection to be:
{
"_id" : 1.0,
"tags" : [
"technology"
],
"musings" : [
{
"ratings": 2
}
]
}
but it updates it like this where musings is added as a regular field and not as the array field:
{
"_id" : 1.0,
"tags" : [
"technology"
],
"musings" : {
"0" : {
"rating" : 2.0
}
}
}
You should use special update operator for array.
db.TEST.update({
id: "1.0",
$push: {
musings: {
ratings: 2
}
}
})
$push operator append element to array. If array field doesn't exist it is created.
Here are details for array update operators:
https://docs.mongodb.com/manual/reference/operator/update-array/

combining distinct on projection in mongodb

Is there a query i can use on the following collection to get the result at the bottom?
Example:
{
"_id" : ObectId(xyz),
"name" : "Carl",
"something":"else"
},
{
"_id" : ObectId(aaa),
"name" : "Lenny",
"something":"else"
},
{
"_id" : ObectId(bbb),
"name" : "Carl",
"something":"other"
}
I need a query to get this result:
{
"_id" : ObectId(xyz),
"name" : "Carl"
},
{
"_id" : ObectId(aaa),
"name" : "Lenny"
},
A set of documents with no identical names. Its not important which _ids are kept.
You can use aggregation framework to get this shape, the query could look like this:
db.collection.aggregate(
[
{
$group:
{
_id: "$name",
id: { $first: "$_id" }
}
},
{
$project:{
_id:"$id",
name:"$_id"
}
}
]
)
As long as you don't need other fields this will be sufficient.
If you need to add other fields - please update document structure and expected result.
as you don't care about ids it can be simplified
db.collection.aggregate([{$group:{_id: "$name"}}])