Related
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"
}
I have in mongodb differents records. I write down a little example:
{_id:"sad547er4w2v5x85b8", name:"Jhon", jobTime:600, floor:2, dept:5, age:25},
{_id:"xcz547wer4xcvcx1g2", name:"Alex", jobTime:841, floor:4, dept:1, age:55},
{_id:"xcnwep2321954ldfsl", name:"Alice", jobTime:100, floor:3, dept:3, age:55},
{_id:"23s3ih94h548jhfk2u", name:"Anne", jobTime:280, floor:2, dept:8, age:22},
{_id:"03dfsk9342hjwq1503", name:"Alexa", jobTime:355, floor:2, dept:6, age:25}
I tried to obtain this output, but I don't know how to group by twice to get that structure.
{[
{age:22, floors:[{floor:2,persons:[{name:"Anne",jobTime:280,dept:8}]}]},
{age:25, floors:[{floor:2,persons:[{name:"Jhon",jobTime:600,dept:5},{name:"Alexa",jobTime:355,dept:6}]}]},
{age:55, floors:[{floor:3,persons:[{name:"Alex",jobTime:841,dept:1}]},{floor:4,persons:[{name:"Alice",jobTime:100,dept:3}]}]}
]}
Exactly. Use "two" $group stages
collection.aggregate([
{ "$group": {
"_id": {
"age": "$age",
"floor": "$floor",
},
"persons": { "$push": {
"name": "$name",
"jobTime": "$jobTime",
"dept": "$dept"
}}
}},
{ "$group": {
"_id": "$_id.age",
"floors": { "$push": {
"floor": "$_id.floor",
"persons": "$persons"
}}
}}
],function(err,results) {
// deal with results here
})
Which produces:
{
"_id" : 25,
"floors" : [
{ "floor" : 2,
"persons" : [
{ "name" : "Jhon", "jobTime" : 600, "dept" : 5 },
{ "name" : "Alexa", "jobTime" : 355, "dept" : 6 }
]
}
]
},
{
"_id" : 55,
"floors" : [
{ "floor" : 3,
"persons" : [
{ "name" : "Alice", "jobTime" : 100, "dept" : 3 }
]
},
{ "floor" : 4,
"persons" : [
{ "name" : "Alex", "jobTime" : 841, "dept" : 1 }
]
}
]
},
{
"_id" : 22,
"floors" : [
{ "floor" : 2,
"persons" : [
{ "name" : "Anne", "jobTime" : 280, "dept" : 8 }
]
}
]
}
So the initial $group is on a compound key including the detail down to the items you want to add to the initial "array", for "persons". Then the second $group takes only part of the initial _id for it's key and again "pushes" the content into a new array.
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
}
My mongo database contains a collection 'Shops' and the data is like below:
{
"_id" : ObjectId("XXXX1b83d2b227XXXX"),
"ShopId" : 435,
"products" : [
{
"productId" : "1234",
"productName" : "non veg",
"productCategory" : "meals",
"mrp" : "38",
},
{
"productId" : "5234",
"productName" : "non veg",
"productCategory" : "meals",
"mrp" : "38",
},
{
"productId" : "6234",
"productName" : "apple",
"productCategory" : "juice",
"mrp" : "38",
},
{
"productId" : "7234",
"productName" : "non veg",
"productCategory" : "biriyani",
"mrp" : "38",
},
{
"productId" : "8234",
"productName" : "non veg",
"productCategory" : "biriyani",
"mrp" : "38",
}
]
}
There will be several shops in the collection having a list of products.
Expected Output
{ "productList": [
{
"categoryname": "meals",
"productcount": "2",
"products": [
{
"productname": "Non-Veg"
},
{
"productname": "Veg"
}
]
},
{
"categoryname": "juice",
"productcount": "1",
"products": [
{
"productname": "apple"
}
]
},{......}
]
}
I tried it using 'async' method with 2 queries, but I didn't get the output correctly. I think it can be done in one query without using 'async'.
My code follows, I think it's the wrong approach:
model.Shops.aggregate([
{$match:{ShopId:435}},
{$unwind:"$products"},
{$limit:2},{$skip:0},
{$group:{_id:{"productCategory":"$products.productCategory"}}}
],function (err, doc) {
if (doc!=null){
var arr = [];
async.each(doc, function(item,callback){
model.Shops.aggregate([
{"$unwind":"$products"},
{$match:{"ShopId":435,"products.productCategory":item._id.productCategory}},
{$limit:2},
{
$group: {
_id:null,
"products": {
$push:{"productName":"$products.productName"}
}
}
}
], function (err,doc) {
arr.push({"categoryname":item._id.productCategory,"products":doc.products});
callback(null);
});
},function (err) {
res.json(arr);
});
}
});
You certainly do not need two queries for this, a single pipeline will suffice. Run the following aggregate operation to get the desired results:
model.Shops.aggregate([
{ "$match": { "ShopId": 435 } },
{ "$unwind": "$products" },
{
"$group": {
"_id": "$products.productCategory",
"count": { "$sum": 1 },
"products": {
"$push": {
"productName": "$products.productName"
}
}
}
},
{
"$group": {
"_id": null,
"productList": {
"$push": {
"categoryname": "$_id",
"productcount": "$count",
"products": "$products"
}
}
}
}
], function (err, results) {
res.json(results);
});
Explanations
The above pipeline uses the following pipeline steps (in the order given) and explained as:
Step 1) $match operator is there to filter documents that get into the pipeline. If you are coming from a SQL background, this pipeline is similar to the SQL's WHERE clause where e.g.
SELECT *
FROM Shops
WHERE ShopId = 435
If you run the pipeline at this stage only, it will return all the documents that match on the ShopId of 435
Step 2) $unwind - The products field is an array so you'll need to add an $unwind stage to your pipeline so that you can flatten the array as it needs to be processed further down as a denormalised field. For each input document, this outputs n documents where n is the number of array elements and can be zero for an empty array.
Running the aggregate pipeline up to this stage for the above sample will produce 5 documents i.e. in mongo shell
db.getCollection('shops').aggregate([
{ "$match": { "ShopId": 435 } }, // Step 1
{ "$unwind": "$products" } // Step 2
])
will yield
[
{
"_id" : ObjectId("58aadec0671a3794272f342f"),
"ShopId" : 435,
"products" : {
"productId" : "1234",
"productName" : "non veg",
"productCategory" : "meals",
"mrp" : "38"
}
},
{
"_id" : ObjectId("58aadec0671a3794272f342f"),
"ShopId" : 435,
"products" : {
"productId" : "5234",
"productName" : "non veg",
"productCategory" : "meals",
"mrp" : "38"
}
},
{
"_id" : ObjectId("58aadec0671a3794272f342f"),
"ShopId" : 435,
"products" : {
"productId" : "6234",
"productName" : "apple",
"productCategory" : "juice",
"mrp" : "38"
}
},
{
"_id" : ObjectId("58aadec0671a3794272f342f"),
"ShopId" : 435,
"products" : {
"productId" : "7234",
"productName" : "non veg",
"productCategory" : "biriyani",
"mrp" : "38"
}
},
{
"_id" : ObjectId("58aadec0671a3794272f342f"),
"ShopId" : 435,
"products" : {
"productId" : "8234",
"productName" : "non veg",
"productCategory" : "biriyani",
"mrp" : "38"
}
}
]
Step 3) $group pipeline step to group the documents in the pipeline by the productCategory field from the denormalised documents and creates an array products that has fields from the previous pipeline. The $group pipeline operator is similar to the SQL's GROUP BY clause.
In SQL, you can't use GROUP BY unless you use any of the aggregation functions. The same way, you have to use an aggregation function called accumulator in MongoDB as well. You can read more about the aggregation functions here.
The accumulator operator you would need to create the array is $push.
In the same $group operation, the logic to calculate the count aggregate i.e. the number of documents in each category group is done using the $sum accumulator operator. The expression { $sum : 1 } returns the sum of values of the number of documents in each group.
To understand the pipeline, run the operation at this stage and analyse the results. So, executing the equivalent mongo operation
db.getCollection('shops').aggregate([
{ "$match": { "ShopId": 435 } }, // Step 1
{ "$unwind": "$products" }, // Step 2
{ // Step 3
"$group": {
"_id": "$products.productCategory",
"count": { "$sum": 1 },
"products": {
"$push": {
"productName": "$products.productName"
}
}
}
}
])
yields the following documents
[
{
"_id" : "meals",
"count" : 2,
"products" : [
{
"productName" : "non veg"
},
{
"productName" : "non veg"
}
]
},
{
"_id" : "juice",
"count" : 1,
"products" : [
{
"productName" : "apple"
}
]
},
{
"_id" : "biriyani",
"count" : 2,
"products" : [
{
"productName" : "non veg"
},
{
"productName" : "non veg"
}
]
}
]
Step 4) The last $group pipeline will then produce the desired result when you specify an _id value of null to calculate accumulated values for all the input documents above as a whole. The desired structure has a productsList array that can be created using the $push operator.
Again, running the final aggregate pipeline at this stage will give you the desired result, i.e. executing this in mongo shell
db.getCollection('shops').aggregate([
{ "$match": { "ShopId": 435 } }, // Step 1
{ "$unwind": "$products" }, // Step 2
{ // Step 3
"$group": {
"_id": "$products.productCategory",
"count": { "$sum": 1 },
"products": {
"$push": {
"productName": "$products.productName"
}
}
}
},
{ // Step 4
"$group": {
"_id": null,
"productList": {
"$push": {
"categoryname": "$_id",
"productcount": "$count",
"products": "$products"
}
}
}
}
])
will yield
{
"_id" : null,
"productList" : [
{
"categoryname" : "meals",
"productcount" : 2,
"products" : [
{
"productName" : "non veg"
},
{
"productName" : "non veg"
}
]
},
{
"categoryname" : "juice",
"productcount" : 1,
"products" : [
{
"productName" : "apple"
}
]
},
{
"categoryname" : "biriyani",
"productcount" : 2,
"products" : [
{
"productName" : "non veg"
},
{
"productName" : "non veg"
}
]
}
]
}
One thing to note here is when executing a pipeline, MongoDB pipes operators into each other. "Pipe" here takes the Linux meaning: the output of an operator becomes the input of the following operator. The result of each operator is a new collection of documents. So Mongo executes the above pipeline as follows:
collection | $match | $unwind | $group | $group => result
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