Mongo db skip-limit work before match after unwind - mongodb

This is my query:
db.getCollection('grades').
aggregate([{ "$match" : { "class_id" : 28, "student_id" : 0 } },
{ "$unwind" : "$scores" },
{ "$match" : { "scores.type" : "homework" } },
{ "$skip" : 3 }, { "$limit" : 3 },
{ "$group" : { "_id" : { "id" : "$_id" }, "scores" : { "$push" : "$scores" } } },
{ "$project" : { "_id" : "$_id.id", "scores" : 1 } }])
scores - is a nested array of objects. Score object - {type: "someType", score: someScore}. This query returns one document.
The problem: array of scores has 6 objects and 4 of them have type homework.
The result, what I've received: http://prntscr.com/bq217r
The original document: http://prntscr.com/bq23bv
Why skip-limit performed before match operator? How can I fix it?

As per attached screenshot everything looks OK.
we have 4 elements 1 2 3 4, then we are skipping 3, so we get 4 at the end... and 53 is the value :-)
btw your skip/limit is after $match

Related

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).

MongoDB filtering out subdocuments with lookup aggregation

Our project database has a capped collection called values which gets updated every few minutes with new data from sensors. These sensors all belong to a single sensor node, and I would like to query the last data from these nodes in a single aggregation. The problem I am having is filtering out just the last of ALL the types of sensors while still having only one (efficient) query. I looked around and found the $group argument, but I can't seem to figure out how to use it correctly in this case.
The database is structured as follows:
nodes:
{
"_id": 681
"sensors": [
{
"type": "foo"
},
{
"type": "bar"
}
]
}
values:
{
"_id" : ObjectId("570cc8b6ac55850d5740784e"),
"timestamp" : ISODate("2016-04-12T12:06:46.344Z"),
"type" : "foo",
"nodeid" : 681,
"value" : 10
}
{
"_id" : ObjectId("190ac8b6ac55850d5740776e"),
"timestamp" : ISODate("2016-04-12T12:06:46.344Z"),
"type" : "bar",
"nodeid" : 681,
"value" : 20
}
{
"_id" : ObjectId("167bc997bb66750d5740665e"),
"timestamp" : ISODate("2016-04-12T12:06:46.344Z"),
"type" : "bar",
"nodeid" : 200,
"value" : 20
}
{
"_id" : ObjectId("110cc9c6ac55850d5740784e"),
"timestamp" : ISODate("2016-04-09T12:06:46.344Z"),
"type" : "foo",
"nodeid" : 681,
"value" : 12
}
so let's imagine I want the data from node 681, I would want a structure like this:
nodes:
{
"_id": 681
"sensors": [
{
"_id" : ObjectId("570cc8b6ac55850d5740784e"),
"timestamp" : ISODate("2016-04-12T12:06:46.344Z"),
"type" : "foo",
"nodeid" : 681,
"value" : 10
},
{
"_id" : ObjectId("190ac8b6ac55850d5740776e"),
"timestamp" : ISODate("2016-04-12T12:06:46.344Z"),
"type" : "bar",
"nodeid" : 681,
"value" : 20
}
]
}
Notice how one value of foo is not queried, because I want to only get the latest value possible if there are more than one value (which is always going to be the case). The ordering of the collection is already according to the timestamp because the collection is capped.
I have this query, but it just gets all the values from the database (which is waaay too much to do in a lifetime, let alone one request of the web app), so I was wondering how I would filter it before it gets aggregated.
query:
db.nodes.aggregate(
[
{
$unwind: "$sensors"
},
{
$match:{
nodeid: 681
}
},
{
$lookup:{
from: "values", localField: "sensors.type", foreignField: "type", as: "sensors"
}
}
}
]
)
Try this
// Pipeline
[
// Stage 1 - sort the data collection if not already done (optional)
{
$sort: {
"timestamp":1
}
},
// Stage 2 - group by type & nodeid then get first item found in each group
{
$group: {
"_id":{type:"$type",nodeid:"$nodeid"},
"sensors": {"$first":"$$CURRENT"} //consider using $last if your collection is on reverse
}
},
// Stage 3 - project the fields in desired
{
$project: {
"_id":"$sensors._id",
"timestamp":"$sensors.timestamp",
"type":"$sensors.type",
"nodeid":"$sensors.nodeid",
"value":"$sensors.value"
}
},
// Stage 4 - group and push it to array sensors
{
$group: {
"_id":{nodeid:"$nodeid"},
"sensors": {"$addToSet":"$$CURRENT"}
}
}
]
as far as I got document structure, there is no need to use $lookup as all data is in readings(values) collection.
Please see proposed solution:
db.readings.aggregate([{
$match : {
nodeid : 681
}
},
{
$group : {
_id : {
type : "$type",
nodeid : "$nodeid"
},
readings : {
$push : {
timestamp : "$timestamp",
value : "$value",
id : "$_id"
}
}
}
}, {
$project : {
_id : "$_id",
readings : {
$slice : ["$readings", -1]
}
}
}, {
$unwind : "$readings"
}, {
$project : {
_id : "$readings.id",
type : "$_id.type",
nodeid : "$_id.nodeid",
timestamp : "$readings.timestamp",
value : "$readings.value",
}
}, {
$group : {
_id : "$nodeid",
sensors : {
$push : {
_id : "$_id",
timestamp : "$timestamp",
value : "$value",
type:"$type"
}
}
}
}
])
and output:
{
"_id" : 681,
"sensors" : [
{
"_id" : ObjectId("110cc9c6ac55850d5740784e"),
"timestamp" : ISODate("2016-04-09T12:06:46.344Z"),
"value" : 12,
"type" : "foo"
},
{
"_id" : ObjectId("190ac8b6ac55850d5740776e"),
"timestamp" : ISODate("2016-04-12T12:06:46.344Z"),
"value" : 20,
"type" : "bar"
}
]
}
Any comments welcome!

MongoDB $sum and $avg of sub documents

I need to get $sum and $avg of subdocuments, i would like to get $sum and $avg of Channels[0].. and other channels as well.
my data structure looks like this
{
_id : ... Location : 1,
Channels : [
{ _id: ...,
Value: 25
},
{
_id: ... ,
Value: 39
},
{
_id: ..,
Value: 12
}
]
}
In order to get the sum and average of the Channels.Value elements for each document in your collection you will need to use mongodb's Aggregation processing. Further, since Channels is an array you will need to use the $unwind operator to deconstruct the array.
Assuming that your collection is called example, here's how you could get both the document sum and average of the Channels.Values:
db.example.aggregate( [
{
"$unwind" : "$Channels"
},
{
"$group" : {
"_id" : "$_id",
"documentSum" : { "$sum" : "$Channels.Value" },
"documentAvg" : { "$avg" : "$Channels.Value" }
}
}
] )
The output from your post's data would be:
{
"_id" : SomeObjectIdValue,
"documentSum" : 76,
"documentAvg" : 25.333333333333332
}
If you have more than one document in your collection then you will see a result row for each document containing a Channels array.
Solution 1: Using two groups based this example:
previous question
db.records.aggregate(
[
{ $unwind: "$Channels" },
{ $group: {
_id: {
"loc" : "$Location",
"cId" : "$Channels.Id"
},
"value" : {$sum : "$Channels.Value" },
"average" : {$avg : "$Channels.Value"},
"maximun" : {$max : "$Channels.Value"},
"minimum" : {$min : "$Channels.Value"}
}},
{ $group: {
_id : "$_id.loc",
"ChannelsSumary" : { $push :
{ "channelId" : '$_id.cId',
"value" :'$value',
"average" : '$average',
"maximun" : '$maximun',
"minimum" : '$minimum'
}}
}
}
]
)
Solution 2:
there is property i didn't show on my original question that might of help "Channels.Id" independent from "Channels._Id"
db.records.aggregate( [
{
"$unwind" : "$Channels"
},
{
"$group" : {
"_id" : "$Channels.Id",
"documentSum" : { "$sum" : "$Channels.Value" },
"documentAvg" : { "$avg" : "$Channels.Value" }
}
}
] )

MongoDB $ne in sub documents

{
"_id" : ObjectId("53692eb238ed04c824679f18"),
"firstUserId" : 1,
"secondUserId" : 17,
"messages" : [
{
"_id" : ObjectId("5369338997b964b81d579fc6"),
"read" : true,
"dateTime" : 1399403401,
"message" : "d",
"userId" : 1
},
{
"_id" : ObjectId("536933c797b964b81d579fc7"),
"read" : false,
"dateTime" : 1399403463,
"message" : "asdf",
"userId" : 17
}
]
}
I'm trying to select all documents that have firstUserId = 1 and also have sub documents
that have userId differnet ($ne) to 1 and read = false.
I tried:
db.usermessages.find({firstUserId: 1, "messages.userId": {$ne: 1}, "messages.read": false})
But it returns empty cause messages have both 1 and 17.
And also how to count subdocuments that have given case?
Are you trying to get the count of all the documents which are returned after your match criteria? If Yes, then you might consider using aggregation framework. http://docs.mongodb.org/manual/aggregation/
Something like below could be done to get you the counts:
db.usermessages.aggregate(
{ "$unwind": "$messages" },
{ "$match":
{ "firstUserId": 1,
"messages.userId": { "$ne" : 1},
"messages.read": false
}
},
{ "$group": { "_id" :null, "count" : { "$sum": 1 } } }
)
Hope this helps.
PS: I have not tried this on my system.

MongoDB: How can I calculate total count without using $group operator

I have following result:
"result" : [
{
"_id" : "London",
"count" : 499
},
{
"_id" : "Paris",
"count" : 135
},
{
"_id" : "Lviv",
"count" : 95
}
]
And here is query:
{"$group":{
_id: "$city",
"count" : {"$sum":1}
}
}
So, I want some how to calculate all fields not only grouped. I think it would better to show expected result:
"result" : [
{
"_id" : "London",
"count" : 499,
"total" : 729
},
{
"_id" : "Paris",
"count" : 135,
"total" : 729
},
{
"_id" : "Lviv",
"count" : 95,
"total" : 729
}
]
Expected result has "total" field which calculated as amount of "count" field (499+135+95 = 729).
EDITED: I must use only aggregation framework!
Can someone help me with this?
You have to count the total number before:
db.coll.count( ..., function( err, total ) {
and then use that result in your aggregation command:
{
"$group": {
_id: "$city",
count: { "$sum": 1 },
total: total
}
EDIT:
If you only want to use aggregation framework, try this instead of db.coll.count():
{
"$group": {
_id: 1,
count: { "$sum": 1 }
}
}
Sounds like db.collection.count() would give you your result actually. This is because you are actually just summing up ALL documents in the collection there.