MongoDB: Aggregate with nested $group - node.js

I have the following query:
db.test.aggregate([
{
$match: {
'type': 'energy'
}
},
{
$limit: 10000
},
{
$addFields: {
day: {
$dateToString: {
date: "$when.date",
format: "%d/%m/%Y"
}
},
sensor: "$id"
},
},
{
$project: {
_id: 1,
sensor: 1,
when: 1,
value: 1,
day: 1
}
},
{
$group: {
_id: "$day",
data: {
$push: "$$ROOT"
},
}
},
{
$sort: {
'data': 1
}
}
])
Which returns this data format:
{
"_id" : "05/04/2018",
"data" : [
{
"_id" : ObjectId("5ac66be9b02d5c18fd4106c7"),
"value" : 0,
"when" : {
"date" : ISODate("2018-04-05T13:53:22.703-03:00"),
"unix" : 1522947202,
"milli" : 1522947202703
},
"day" : "05/04/2018",
"sensor" : "sen3"
},
{
"_id" : ObjectId("5ac66be9b02d5c18fd4106c8"),
"value" : 0,
"when" : {
"date" : ISODate("2018-04-05T13:53:22.705-03:00"),
"unix" : 1522947202,
"milli" : 1522947202705
},
"day" : "05/04/2018",
"sensor" : "sen4"
}
]
},
{
"_id" : "06/04/2018",
"data" : [
{
"_id" : ObjectId("5ac7e5d2efe88a4e76c008d2"),
"value" : 0,
"when" : {
"date" : ISODate("2018-04-06T18:25:38.885-03:00"),
"unix" : 1523049938,
"milli" : 1523049938885
},
"day" : "06/04/2018",
"sensor" : "sen3"
},
{
"_id" : ObjectId("5ac7e5e4efe88a4e76c008d5"),
"value" : 0,
"when" : {
"date" : ISODate("2018-04-06T18:25:56.105-03:00"),
"unix" : 1523049956,
"milli" : 1523049956105
},
"day" : "06/04/2018",
"sensor" : "sen3"
}
]
},
...
Note that we have in each "data" document, different types of sensors (sen3, sen4, ... ,senN).
I'm trying to aggregate this result once more, agrouping the data by sensor, to get the output something like this:
{
"_id" : "05/04/2018",
"sen3" : [
{
"_id" : ObjectId("5ac66be9b02d5c18fd4106c7"),
"value" : 0,
"when" : {
"date" : ISODate("2018-04-05T13:53:22.703-03:00"),
"unix" : 1522947202,
"milli" : 1522947202703
},
"day" : "05/04/2018",
"sensor" : "sen3"
},
{
"_id" : ObjectId("5ac66be9b02d5c18fd4106c7"),
"value" : 0,
"when" : {
"date" : ISODate("2018-04-05T13:53:22.703-03:00"),
"unix" : 1522947202,
"milli" : 1522947202703
},
"day" : "05/04/2018",
"sensor" : "sen3"
}
],
"sen4" : [
{
"_id" : ObjectId("5ac66be9b02d5c18fd4106c8"),
"value" : 0,
"when" : {
"date" : ISODate("2018-04-05T13:53:22.705-03:00"),
"unix" : 1522947202,
"milli" : 1522947202705
},
"day" : "05/04/2018",
"sensor" : "sen4"
},
{
"_id" : ObjectId("5ac66be9b02d5c18fd4106c8"),
"value" : 0,
"when" : {
"date" : ISODate("2018-04-05T13:53:22.705-03:00"),
"unix" : 1522947202,
"milli" : 1522947202705
},
"day" : "05/04/2018",
"sensor" : "sen4"
}
]
},
{
"_id" : "06/04/2018",
"sen3" : [
{
"_id" : ObjectId("5ac7e5d2efe88a4e76c008d2"),
"value" : 0,
"when" : {
"date" : ISODate("2018-04-06T18:25:38.885-03:00"),
"unix" : 1523049938,
"milli" : 1523049938885
},
"day" : "06/04/2018",
"sensor" : "sen3"
},
{
"_id" : ObjectId("5ac7e5e4efe88a4e76c008d5"),
"value" : 0,
"when" : {
"date" : ISODate("2018-04-06T18:25:56.105-03:00"),
"unix" : 1523049956,
"milli" : 1523049956105
},
"day" : "06/04/2018",
"sensor" : "sen3"
}
],
"sen4": [
{
"_id" : ObjectId("5ac7e7a7efe88a4e76c008de"),
"value" : 0,
"when" : {
"date" : ISODate("2018-04-06T18:33:27.365-03:00"),
"unix" : 1523050407,
"milli" : 1523050407365
},
"day" : "06/04/2018",
"sensor" : "sen4"
},
{
"_id" : ObjectId("5ac7e7a7efe88a4e76c008de"),
"value" : 0,
"when" : {
"date" : ISODate("2018-04-06T18:33:27.365-03:00"),
"unix" : 1523050407,
"milli" : 1523050407365
},
"day" : "06/04/2018",
"sensor" : "sen4"
}
]
}
In short: I want to group the data in days, sensors and inside each sensor the results that belongs with that day and sensor.
I'm trying to create a nested $group but in all tentatives it goes wrong.
Is this possible to do and case yes, how?

You can try below aggregation in mongodb 3.6 and above
db.collection.aggregate([
{ "$match": { "type": "energy" }},
{ "$limit": 10000 },
{ "$addFields": {
"day": { "$dateToString": { "date": "$when.date", "format": "%d/%m/%Y" }},
"sensor": "$id"
}},
{ "$project": { "_id": 1, "sensor": 1, "when": 1, "value": 1, "day": 1 }},
{ "$group": {
"_id": { "day": "$day", "sensor": "$sensor" },
"data": { "$push": "$$ROOT" }
}},
{ "$group": {
"_id": { "day": "$_id.day" },
"data": { "$push": { "k": "$_id.sensor", "v": "$data" }}
}},
{ "$addFields": { "data": { "$arrayToObject": "$data" }}},
{ "$replaceRoot": { "newRoot": { "$mergeObjects": [ "$_id", "$data" ] }}}
])

Related

How to use mongodb aggregate with the following data

I have a database with collection name "sensors" and it looks like this:
{ "_id" : ObjectId("5d4d27a7e1f2cf1d7cba1fe3"), "type" : "thermometer", "value" : 23, "createdAt" : ISODate("2019-08-09T07:58:31.698Z"), "updatedAt" : ISODate("2019-08-09T07:58:31.698Z"), "__v" : 0 }
{ "_id" : ObjectId("5d4d27a7e1f2cf1d7cba1fe4"), "type" : "hygrometer", "value" : 74, "createdAt" : ISODate("2019-08-09T07:58:31.739Z"), "updatedAt" : ISODate("2019-08-09T07:58:31.739Z"), "__v" : 0 }
{ "_id" : ObjectId("5d4d27a8e1f2cf1d7cba1fe5"), "type" : "thermometer", "value" : 25, "createdAt" : ISODate("2019-08-09T07:58:32.551Z"), "updatedAt" : ISODate("2019-08-09T07:58:32.551Z"), "__v" : 0 }
{ "_id" : ObjectId("5d4d27a8e1f2cf1d7cba1fe6"), "type" : "hygrometer", "value" : 86, "createdAt" : ISODate("2019-08-09T07:58:32.584Z"), "updatedAt" : ISODate("2019-08-09T07:58:32.584Z"), "__v" : 0 }
{ "_id" : ObjectId("5d4d27a9e1f2cf1d7cba1fe7"), "type" : "thermometer", "value" : 20, "createdAt" : ISODate("2019-08-09T07:58:33.554Z"), "updatedAt" : ISODate("2019-08-09T07:58:33.554Z"), "__v" : 0 }
{ "_id" : ObjectId("5d4d27a9e1f2cf1d7cba1fe8"), "type" : "hygrometer", "value" : 84, "createdAt" : ISODate("2019-08-09T07:58:33.587Z"), "updatedAt" : ISODate("2019-08-09T07:58:33.587Z"), "__v" : 0 }
What I want to do is to query on the very last two documents, thermometer and hygrometer then check the value for each of the documents. Then based on the conditions I set using $match tag, I perform any operations I want.
MongoClient.connect('mongodb://localhost', { useNewUrlParser: true }, function (err, client) {
if (err) throw err;
var db = client.db('mongodb');
db.collection('sensors').aggregate(
[
{ $sort: { _id: -1 } },
{ $limit: 1 },
{
$match: {"type": "hygrometer", "value": { "$gt": 60 } }
}
]
).toArray(function(err, item) {
if(err) console.log('error');
if(item.length > 0) console.log('Dehumidifier ON');
else console.log('Dehumidifier OFF');
});
This is the implementation for the very last data only (hygrometer) but how should I make it to check the last two documents then perform a separate operation for thermometer for example "AC ON/OFF"? I was thinking somehow make it check when item.type == "thermometer" perform something but that item.type syntax seems to not work.
Please try this :
Query 1:
db.getCollection('sensors').aggregate(
[
{ $sort: { _id: -1 } }, { $group: { _id: '$type', data: { $push: '$$ROOT' } } },
{ $project: { type: '$_id', _id: 0, lastTwo: { $slice: ["$data", 2] } } },
{ $unwind: '$lastTwo' },
{
$match: { $or: [{ 'type': 'hygrometer' }, { "type": "thermometer", "lastTwo.value": { "$gt": 60 } }] }
}
])
Query 2:
//Check the response below & If you want to group again on type, adding `$group` stage to the above query.
db.getCollection('sensors').aggregate(
[
{ $sort: { _id: -1 } }, { $group: { _id: '$type', data: { $push: '$$ROOT' } } },
{ $project: { type: '$_id', _id: 0, lastTwo: { $slice: ["$data", 2] } } },
{ $unwind: '$lastTwo' },
{
$match: { $or: [{ 'type': 'hygrometer' }, { "type": "thermometer", "lastTwo.value": { "$gt": 60 } }] }
}, { $group: { _id: '$type', data: { $push: '$$ROOT' } } }, { $project: { type: '$_id', _id: 0, data: 1 } }
])
In Output 1 you can simply check whether an object with specific type exists or not and do something in code (Vs) in Output 2 you've to check for type and data array is not empty.
Sample Data :
/* 1 */
{
"_id" : ObjectId("5d4d27a7e1f2cf1d7cba1fe3"),
"type" : "thermometer",
"value" : 23,
"createdAt" : ISODate("2019-08-09T07:58:31.698Z"),
"updatedAt" : ISODate("2019-08-09T07:58:31.698Z"),
"__v" : 0
}
/* 2 */
{
"_id" : ObjectId("5d4d27a7e1f2cf1d7cba1fe4"),
"type" : "hygrometer",
"value" : 74,
"createdAt" : ISODate("2019-08-09T07:58:31.739Z"),
"updatedAt" : ISODate("2019-08-09T07:58:31.739Z"),
"__v" : 0
}
/* 3 */
{
"_id" : ObjectId("5d4d27a8e1f2cf1d7cba1fe5"),
"type" : "thermometer",
"value" : 251,
"createdAt" : ISODate("2019-08-09T07:58:32.551Z"),
"updatedAt" : ISODate("2019-08-09T07:58:32.551Z"),
"__v" : 0
}
/* 4 */
{
"_id" : ObjectId("5d4d27a8e1f2cf1d7cba1fe6"),
"type" : "hygrometer",
"value" : 86,
"createdAt" : ISODate("2019-08-09T07:58:32.584Z"),
"updatedAt" : ISODate("2019-08-09T07:58:32.584Z"),
"__v" : 0
}
/* 5 */
{
"_id" : ObjectId("5d4d27a9e1f2cf1d7cba1fe7"),
"type" : "thermometer",
"value" : 20,
"createdAt" : ISODate("2019-08-09T07:58:33.554Z"),
"updatedAt" : ISODate("2019-08-09T07:58:33.554Z"),
"__v" : 0
}
/* 6 */
{
"_id" : ObjectId("5d4d27a9e1f2cf1d7cba1fe8"),
"type" : "hygrometer",
"value" : 84,
"createdAt" : ISODate("2019-08-09T07:58:33.587Z"),
"updatedAt" : ISODate("2019-08-09T07:58:33.587Z"),
"__v" : 0
}
Output 1:
/* 1 */
{
"type" : "thermometer",
"lastTwo" : {
"_id" : ObjectId("5d4d27a8e1f2cf1d7cba1fe5"),
"type" : "thermometer",
"value" : 251,
"createdAt" : ISODate("2019-08-09T07:58:32.551Z"),
"updatedAt" : ISODate("2019-08-09T07:58:32.551Z"),
"__v" : 0
}
}
/* 2 */
{
"type" : "hygrometer",
"lastTwo" : {
"_id" : ObjectId("5d4d27a9e1f2cf1d7cba1fe8"),
"type" : "hygrometer",
"value" : 84,
"createdAt" : ISODate("2019-08-09T07:58:33.587Z"),
"updatedAt" : ISODate("2019-08-09T07:58:33.587Z"),
"__v" : 0
}
}
/* 3 */
{
"type" : "hygrometer",
"lastTwo" : {
"_id" : ObjectId("5d4d27a8e1f2cf1d7cba1fe6"),
"type" : "hygrometer",
"value" : 86,
"createdAt" : ISODate("2019-08-09T07:58:32.584Z"),
"updatedAt" : ISODate("2019-08-09T07:58:32.584Z"),
"__v" : 0
}
}
Output 2:
/* 1 */
{
"type" : "hygrometer",
"data" : [
{
"type" : "hygrometer",
"lastTwo" : {
"_id" : ObjectId("5d4d27a9e1f2cf1d7cba1fe8"),
"type" : "hygrometer",
"value" : 84,
"createdAt" : ISODate("2019-08-09T07:58:33.587Z"),
"updatedAt" : ISODate("2019-08-09T07:58:33.587Z"),
"__v" : 0
}
},
{
"type" : "hygrometer",
"lastTwo" : {
"_id" : ObjectId("5d4d27a8e1f2cf1d7cba1fe6"),
"type" : "hygrometer",
"value" : 86,
"createdAt" : ISODate("2019-08-09T07:58:32.584Z"),
"updatedAt" : ISODate("2019-08-09T07:58:32.584Z"),
"__v" : 0
}
}
]
}
/* 2 */
{
"type" : "thermometer",
"data" : [
{
"type" : "thermometer",
"lastTwo" : {
"_id" : ObjectId("5d4d27a8e1f2cf1d7cba1fe5"),
"type" : "thermometer",
"value" : 251,
"createdAt" : ISODate("2019-08-09T07:58:32.551Z"),
"updatedAt" : ISODate("2019-08-09T07:58:32.551Z"),
"__v" : 0
}
}
]
}
Updates as per comments (In case if you need documents irrespective of type, as documents is always ordered), Please try this:
db.sensors.aggregate( [ { $sort: { _id: -1 } }, { $limit: 2 }, { $match: { $or: [{ 'type': 'hygrometer' , "value": { "$gt": 60 }}, { "type": "thermometer", "value": { "$gt": 20 } }] } } ] )
or make $slice: ["$data", 2] to $slice: ["$data", 1] from above query.
After the sort, limit it to 2 and then use $or:
db.aggtest.aggregate([
{ $sort: { _id: -1 } },
{ $limit: 2 },
{$match: { $or: [ {"type": "hygrometer", "value": { "$gt": 60 }}, {"type": "thermometer"} ]}}
])

How to use $filter(aggregation) to select some fields of array only if condition true?

Here I'll show you what exactly I want. Suppose I have the below two document for XYZ model.
[
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"email" : "one#one.com",
"mobileNumber" : "910123456989",
"verificationStatus" : true,
"activities" : [
{
"name" : "a1",
"_id" : ObjectId("59ef8786e8c7d60552139bae"),
"type" : 0,
"level" : null,
"verificationStatus" : true
},
{
"name" : "a2",
"_id" : ObjectId("59ef8786e8c7d60552139bad"),
"type" : 0,
"level" : null,
"verificationStatus" : false
}
],
"address" : {
"line1" : "asd",
"line2" : "asd",
"city" : "sd",
"state" : "sd",
"country" : "asd",
"landmark" : "sdsa",
"pincode" : "560090"
},
"__v" : 0
},
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"email" : "one#one.com",
"mobileNumber" : "919876543210",
"verificationStatus" : true,
"activities" : [
{
"name" : "b1",
"_id" : ObjectId("59ef8786e8c7d60552139bae"),
"level" : null,
"type" : 0,
"verificationStatus" : true
},
{
"name" : "b2",
"_id" : ObjectId("59ef8786e8c7d60552139bad"),
"level" : null,
"type" : 0,
"verificationStatus" : false
}
],
"address" : {
"line1" : "asd",
"line2" : "asd",
"city" : "sd",
"state" : "sd",
"country" : "asd",
"landmark" : "sdsa",
"pincode" : "560090"
},
"__v" : 0
}
]
Now I want only the name, mobileNumber and activities.name from the document where verificationStatus is true and I don't want all the activities I want activities.name only if activities.varificationStatus is true.
I can get the list of all document where varificationStatus is true and activities.varificationStatus is true but I'm not able to select only required fields (activities.name) from activities.
My current code is:
XYZ.aggregate(
[
{ $match: { verificationStatus: true } },
{
$project: {
name: 1,
coverImage: 1,
location: 1,
address: 1,
dist: 1,
activities: {
$filter: {
input: "$activities",
as: "activity",
cond: {
$eq: ["$$activity.verificationStatus", true]
}
}
}
}
}], function (err, list) {
if (err) {
reject(err);
}
else {
resolve(list);
}
});
You actually need $map to "alter" the array elements returned, as $filter only "selects" the array elements that "match":
XYZ.aggregate(
[
{ $match: { verificationStatus: true } },
{
$project: {
name: 1,
mobileNumber: 1,
activities: {
$map: {
input: {
$filter: {
input: "$activities",
as: "activity",
cond: "$$activity.verificationStatus"
}
},
"as": "a",
"in": "$$a.name"
}
}
}
}], function (err, list) {
...
Would return:
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"mobileNumber" : "910123456989",
"activities" : ["a1"]
}
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"mobileNumber" : "919876543210",
"activities" : ["b1"]
}
Note also that the "cond" in $filter can be shortened since it's already a boolean value.
If you wanted the "object" with the property of "name" only, then return just that assigned key:
XYZ.aggregate(
[
{ $match: { verificationStatus: true } },
{
$project: {
name: 1,
mobileNumber: 1,
activities: {
$map: {
input: {
$filter: {
input: "$activities",
as: "activity",
cond: "$$activity.verificationStatus"
}
},
"as": "a",
"in": {
"name": "$$a.name"
}
}
}
}
}], function (err, list) {
...
Returns as:
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"mobileNumber" : "910123456989",
"activities" : [{ "name": "a1" }]
}
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"mobileNumber" : "919876543210",
"activities" : [{ "name": "b1" }]
}
If you knew for certain that you were matching "one" element in the array, then $indexOfArray with $arrayElemAt could be used instead if you have MongoDB 3.4
{ "$project": {
"name": 1,
"mobileNumber": 1,
"activities": {
"$arrayElemAt": [
"$activities.name",
{ "$indexOfArray": [ "$activities.verificationStatus", true ] }
]
}
}}
Which would come out a little differently since it's a singular value and not an array:
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"mobileNumber" : "910123456989",
"activities" : "a1"
}
{
"_id" : ObjectId("59ef8786e8c7d60552139ba9"),
"name" : "s1",
"mobileNumber" : "919876543210",
"activities" : "b1"
}

Group data by category and date with total by date

I need to do the following:
Group by date, then total number of upvotes and total number of downvotes
Group by Category then aggregation has to be done by total number of upvote of all category and downvote of all category by each day upvotes and down votes of individual dates.
Here is my code:
db.collection.aggregate([{
$unwind: '$votes'
}, {
$match: {
'category_id': array[i]
}
}, {
$group: {
_id:'$votes.date',
"category_id": {
$first: "$category_id"
},
up_vote: {
$sum: {
$cond: [{
'$gt': ['$votes.score', 0]
}, "$votes.score", 0]
}
},
down_vote: {
$sum: {
$cond: [{
'$lt': ['$votes.score', 0]
}, "$votes.score", 0]
}
}
}
}, {
"$group": {
"_id": "$_id",
"categories": {
"$push": {
"category_id": "$category_id",
"up_vote ": "$up_vote",
"down_vote": "$down_vote"
}
},
"total_up_vote": {
$sum: {
$cond: [{
'$lt': ['$votes.score', 0]
}, "$votes.score", 0]
}
},
"total_down_vote": {
"$sum": "$down_vote"
}
}
}{
"$unwind": "$categories"
},
{
"$project": {
"category_id": "$categories.category_id",
"down_vote": "$categories.down_vote",
"down_vote_Percentage": {
"$multiply": [{ "$divide": [ "$categories.down_vote", "$total_down_vote" ] },
100
]
},
"up_vote": "$categories.up_vote",
"up_vote_Percentage": {
"$multiply": [{ "$divide": [ "$categories.down_vote", "$total_total_up_vote" ] },
100
]
}
}
}
], function(err, results) {
res.send(result)
})
This is my database structure:
"_id" : ObjectId("590f1ab8a45e6eb418be32cd"),
"category_id" : "singer",
"celebrity_id" : ObjectId("591e71884e743d8015fd1ae0"),
"user_id" : "591e81277bd0b65c141e64be",
"votes" : [
{
"date" : "2017/4/7",
"score" : -1
},
{
"date" : "2017/4/19",
"score" : -1
}
]
}
{
"_id" : ObjectId("59204135dab356f410d1b8a6"),
"category_id" : "actor",
"celebrity_id" : ObjectId("591e80e47bd0b65c141e64bc"),
"user_id" : "591974b64abd73701dc7c4aa",
"votes" : [
{
"date" : "2017/4/20",
"score" : 1
}
]
}
{
"_id" : ObjectId("5920415cdab356f410d1b8a7"),
"category_id" : "actor",
"celebrity_id" : ObjectId("591e81177bd0b65c141e64bd"),
"user_id" : "591974b64abd73701dc7c4aa",
"votes" : [
{
"date" : "2017/4/20",
"score" : 1
}
]
}
You want something like this:
db.collection.aggregate([
{ "$unwind": "$votes" },
{ "$group": {
"_id": {
"date": "$votes.date",
"category_id": "$category_id",
},
"upvote": {
"$sum": {
"$cond": [ { "$gt": [ "$votes.score", 0 ] }, 1, 0 ]
}
},
"downvote": {
"$sum": {
"$cond": [ { "$lt": [ "$votes.score", 0 ] }, 1, 0 ]
}
}
}},
{ "$group": {
"_id": "$_id.date",
"categories": {
"$push": {
"category": "$_id.category_id",
"upvote": "$upvote",
"downvote": "$downvote"
}
},
"total_upvote": { "$sum": "$upvote" },
"total_downvote": { "$sum": "$downvote" }
}},
{ "$unwind": "$categories" },
{ "$project": {
"category": "$categories.category",
"upvote": "$categories.upvote",
"upvote_percent": {
"$multiply": [
{ "$divide": [
"$categories.upvote",
{ "$cond": [{ "$eq": [ "$total_upvote", 0 ]}, 1, "$total_upvote" ] }
]},
100
]
},
"downvote": "$categories.downvote",
"downvote_percent": {
"$multiply": [
{ "$divide": [
"$categories.downvote",
{ "$cond": [{ "$eq": [ "$total_downvote", 0 ]}, 1, "$total_downvote" ] }
]},
100
]
}
}}
])
Remembering that as a "pipeline", the view of the document of each stage is equal to how the document was output from the previous stage.
Source data
{
"_id" : ObjectId("590f1ab8a45e6eb418be32cd"),
"category_id" : "singer",
"celebrity_id" : ObjectId("591e71884e743d8015fd1ae0"),
"user_id" : "591e81277bd0b65c141e64be",
"votes" : [
{
"date" : "2017/4/7",
"score" : -1
},
{
"date" : "2017/4/19",
"score" : -1
}
]
}
{
"_id" : ObjectId("59204135dab356f410d1b8a6"),
"category_id" : "actor",
"celebrity_id" : ObjectId("591e80e47bd0b65c141e64bc"),
"user_id" : "591974b64abd73701dc7c4aa",
"votes" : [
{
"date" : "2017/4/20",
"score" : 1
}
]
}
{
"_id" : ObjectId("5920415cdab356f410d1b8a7"),
"category_id" : "actor",
"celebrity_id" : ObjectId("591e81177bd0b65c141e64bd"),
"user_id" : "591974b64abd73701dc7c4aa",
"votes" : [
{
"date" : "2017/4/20",
"score" : 1
}
]
}
{
"_id" : ObjectId("5923c7fdbcc8728a67bcc653"),
"category_id" : "actor",
"celebrity_id" : ObjectId("591e81177bd0b65c141e64bd"),
"user_id" : "591974b64abd73701dc7c4aa",
"votes" : [
{
"date" : "2017/4/20",
"score" : -11
}
]
}
{
"_id" : ObjectId("5923d1b9bcc8728a67bcc655"),
"category_id" : "blip",
"celebrity_id" : ObjectId("591e81177bd0b65c141e64bd"),
"user_id" : "591974b64abd73701dc7c4aa",
"votes" : [
{
"date" : "2017/4/20",
"score" : -11
}
]
}
Output
{
"_id" : "2017/4/19",
"category" : "singer",
"upvote" : 0,
"upvote_percent" : 0,
"downvote" : 1,
"downvote_percent" : 100
}
{
"_id" : "2017/4/7",
"category" : "singer",
"upvote" : 0,
"upvote_percent" : 0,
"downvote" : 1,
"downvote_percent" : 100
}
{
"_id" : "2017/4/20",
"category" : "blip",
"upvote" : 0,
"upvote_percent" : 0,
"downvote" : 1,
"downvote_percent" : 50
}
{
"_id" : "2017/4/20",
"category" : "actor",
"upvote" : 2,
"upvote_percent" : 100,
"downvote" : 1,
"downvote_percent" : 50
}

Mongo DB + group by with last field data

I have sample json data in collections.
Sample data:
[{
"_id" : 1,
"username" : "abcd",
"createdDate" : ISODate("2016-06-03T08:52:32.434Z")
},
{
"_id" : 2,
"username" : "abcd",
"createdDate" : ISODate("2016-05-03T09:52:32.434Z")
},
{
"_id" : 3,
"username" : "abcd",
"createdDate" : ISODate("2016-04-03T10:52:32.434Z")
},
{
"_id" : 4,
"username" : "xyz",
"createdDate" : ISODate("2016-03-03T10:52:32.434Z")
},{
"_id" : 5,
"username" : "xyz",
"createdDate" : ISODate("2016-02-03T10:52:32.434Z")
},{
"_id" : 6,
"username" : "zzz",
"createdDate" : ISODate("2016-01-03T10:52:32.434Z")
}]
This data I need to retrieve data for following condtions.
Group by username.
username not equal "zzz"
Order by date desc order.
need date field also (which have lastest/last record).
get total count.
Expecting output:
[{
"username" : "abcd",
"createdDate" : "2016-06-03T08:52:32.434Z",
"total" : 3
},
{
"username" : "xyz",
"createdDate" : "2016-03-03T10:52:32.434Z",
"total" : 2
}]
Query:
db.logs.aggregate([
{ "$match": { "username": { "$ne": "zzz" } }},
{ "$group": {
"_id": {
"username": "$username",
"createdDate": "$createdDate"
},
"count": { "$sum": 1 }
}}])
try this :
db.logs.aggregate([
{
"$match":{
"username":{
"$ne":"zzz"
}
}
},
{
"$group":{
_id:"$username",
"count":{
"$sum":1
},
date:{
$max:"$createdDate"
}
}
},
{
$project:{
username:"$_id",
total:"$count",
createdDate:"$date"
}
}
])
output
{
"_id":"xyz",
"username":"xyz",
"total":2,
"createdDate": ISODate("2016-03-03T10:52:32.434 Z")
}{
"_id":"abcd",
"username":"abcd",
"total":3,
"createdDate": ISODate("2016-06-03T08:52:32.434 Z")
}
try it online: mongoplayground.net/p/3_-s2tUjPFi

MongoDB match documents that contain an array field with ALL elements that match the query

From the MongoDB doc for $elementMatch:
The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria.
But how can I match documents that contain an array field with ALL elements that match the query?
For example I have documents like this:
{
"_id": ObjectId("55c99649b8b5fc5b0a2f1c83"),
"sku": "ed-39211",
"created_at": ISODate("2015-08-11T06:29:29.139+0000"),
"formats": [{
"name": "thefile",
"_id": ObjectId("55c99649f2e2d6353348ec9c"),
"prices": [{
"price": 4.49,
"currency": "GBP",
"territory": "GB",
"_id": ObjectId("55c99649f2e2d6353348ec9f")
}, {
"price": 6.99,
"currency": "USD",
"territory": "US",
"_id": ObjectId("55c99649f2e2d6353348ec9e")
}, {
"price": 6.99,
"currency": "CHF",
"territory": "CH",
"_id": ObjectId("55c99649f2e2d6353348ec9d")
}]
}]
}
And I need to match all documents that have all the formats.prices.price > 5
If I use the following query:
{ 'formats.prices': { $elemMatch: { price: { $gte: 5 } } } }
That document will be matched because there is at least one price > 5
I also tried this but it doesn't seem to work:
{ 'formats.prices': { $all: { $elemMatch: {price: { $gte: 0.98 } } } } }
Is there a way to exclude that document looking at all prices an not at least one?
Found it! It was easy, just use $notoperator and check the opposite (< 5):
{ 'formats.prices': { $not: { $elemMatch: {price: { $lt: 5 } } } } }
You Can use Aggegation OR MAP REDUCE to achieve it :
First solution is using Map-Reduce :
I created a collection called "format" and inserted below data :
{
"_id" : ObjectId("55c99649b8b5fc5b0a2f1c83"),
"sku" : "ed-39211",
"created_at" : ISODate("2015-08-11T06:29:29.139Z"),
"formats" : [
{
"name" : "thefile",
"_id" : ObjectId("55c99649f2e2d6353348ec9c"),
"prices" : [
{
"price" : 4.49,
"currency" : "GBP",
"territory" : "GB",
"_id" : ObjectId("55c99649f2e2d6353348ec9f")
},
{
"price" : 6.99,
"currency" : "USD",
"territory" : "US",
"_id" : ObjectId("55c99649f2e2d6353348ec9e")
},
{
"price" : 6.99,
"currency" : "CHF",
"territory" : "CH",
"_id" : ObjectId("55c99649f2e2d6353348ec9d")
}
]
}
]
}
{
"_id" : ObjectId("55c99649b8b5fc5b0a2f1c84"),
"sku" : "ed-39211",
"created_at" : ISODate("2015-08-11T06:29:29.139Z"),
"formats" : [
{
"name" : "thefile",
"_id" : ObjectId("55c99649f2e2d6353348ec9a"),
"prices" : [
{
"price" : 5.49,
"currency" : "GBP",
"territory" : "GB",
"_id" : ObjectId("55c99649f2e2d6353348ec9f")
},
{
"price" : 6.99,
"currency" : "USD",
"territory" : "US",
"_id" : ObjectId("55c99649f2e2d6353348ec9e")
},
{
"price" : 6.99,
"currency" : "CHF",
"territory" : "CH",
"_id" : ObjectId("55c99649f2e2d6353348ec9d")
}
]
}
]
}
Map_reduce :
db.format.mapReduce(
function()
{
var doc = {"_id" : this._id, "sku" : this.sku, "created_at" : this.created_at, "formats" : this.formats};
var prices;
var flag = 0;
for ( var i = 0 ; i < doc.formats.length; i++)
{
prices = doc.formats[i].prices
for ( var j =0 ; j < prices.length; j++)
{
if( prices[j].price < 5)
{
flag = 1;
break;
}
}
if( flag == 1)
doc.formats.splice(i,1);
}
if( doc.formats.length > 0 )
emit( this._id, doc);
},
function(){},
{ "out": { "inline": 1 } }
)
Output :
{
"results" : [
{
"_id" : ObjectId("55c99649b8b5fc5b0a2f1c84"),
"value" : {
"_id" : ObjectId("55c99649b8b5fc5b0a2f1c84"),
"sku" : "ed-39211",
"created_at" : ISODate("2015-08-11T06:29:29.139Z"),
"formats" : [
{
"name" : "thefile",
"_id" : ObjectId("55c99649f2e2d6353348ec9a"),
"prices" : [
{
"price" : 5.49,
"currency" : "GBP",
"territory" : "GB",
"_id" : ObjectId("55c99649f2e2d6353348ec9f")
},
{
"price" : 6.99,
"currency" : "USD",
"territory" : "US",
"_id" : ObjectId("55c99649f2e2d6353348ec9e")
},
{
"price" : 6.99,
"currency" : "CHF",
"territory" : "CH",
"_id" : ObjectId("55c99649f2e2d6353348ec9d")
}
]
}
]
}
Second Solution using Aggregation :
Using aggregate operators $unwind and $size we can get the required result using below query :
After $unwind of "Formats" and "Formats.prices", size of the "Formats.prices" is taken and then a $match on the "prices" is done and again the new size is calculated for "Formats.prices".
If the size are same then all the "prices" in the "format" field are greater than 5 and the document will be projected.
db.format.aggregate([
{ $unwind: "$formats" },
{ $project : { _id : 1, sku : 1, created_at : 1, formats : 1, "size" : { $size : "$formats.prices" } } },
{ $unwind: "$formats.prices" },
{ $match: { "formats.prices.price" : { $gt:5 } } },
{ $group: { _id: { "name" : "$formats.name" , "_id" : "$formats._id", "id" : "$_id" }, prices : { $push: "$formats.prices" } , sku: { $first: "$sku" }, created_at : { $first: "$created_at" }, oldsize : { $first: "$size" } } },
{ $project: { _id : 1, prices : 1, sku : 1, created_at : 1, oldsize : 1, newsize : {$size: "$prices" } } },
{ $project: { _id : 1, prices : 1, sku : 1, created_at : 1, cmp_value: { $cmp: ["$oldsize", "$newsize"] } } },
{ $match: { cmp_value:{ $eq:0 } } },
{ $group : { _id : "$_id.id" , sku: { $first: "$sku" }, created_at : { $first: "$created_at" }, formats : { $push: { name : "$_id.name", "_id" : "$_id._id", prices: "$prices" } } } }
]).pretty()
Output :
{
"_id" : ObjectId("55c99649b8b5fc5b0a2f1c84"),
"sku" : "ed-39211",
"created_at" : ISODate("2015-08-11T06:29:29.139Z"),
"formats" : [
{
"name" : "thefile",
"_id" : ObjectId("55c99649f2e2d6353348ec9a"),
"prices" : [
{
"price" : 5.49,
"currency" : "GBP",
"territory" : "GB",
"_id" : ObjectId("55c99649f2e2d6353348ec9f")
},
{
"price" : 6.99,
"currency" : "USD",
"territory" : "US",
"_id" : ObjectId("55c99649f2e2d6353348ec9e")
},
{
"price" : 6.99,
"currency" : "CHF",
"territory" : "CH",
"_id" : ObjectId("55c99649f2e2d6353348ec9d")
}
]
}
]
}

Resources