Mongodb sort by sum of keys - mongodb

I have a json document
{
{
"_id" : ObjectId("5715c4bbac530eb3018b456a"),
"content_id" : "5715c4bbac530eb3018b4569",
"views" : NumberLong(200),
"likes" : NumberLong(100),
"comments" : NumberLong(0)
},
{
"_id" : ObjectId("5715c4bbac530eb3018b4568"),
"content_id" : "5715c4bbac530eb3018b4567",
"views" : NumberLong(300),
"likes" : NumberLong(200),
"comments" : NumberLong(0)
},
{
"_id" : ObjectId("5715c502ac530ee5018b4956"),
"content_id" : "5715c502ac530ee5018b4955",
"views" : NumberLong(500),
"likes" : NumberLong(0),
"comments" : NumberLong(200)
}
}
How can we sort the document order by SUM("views", "likes", "comments")
something like in mysql
SELECT SUM(key1, key2, key3) AS key
FROM document
ORDER BY key
Thanks in advance.

First do a projection to obtain the sum of all the likes, views and comments, then sort based on that sum. I am considering group by content_id if is needed in the second snippet
db.test.aggregate([
{ $project : { "_id" : "$content_id", "total" : { $add : [ "$likes", "$views", "$comments"]}}},
{ $sort : { "total" : 1 }}
])
If you need a group operation if content_id can be duplicated
db.test.aggregate([
{ $project : { "_id" : "$content_id", "total" : { $add : [ "$likes", "$views", "$comments"]}}},
{ $group : { "_id" : "$_id" , totalPerId : { $sum : "$total" }}},
{ $sort : { "total" : 1 }}
])
Based on your test data, you will get:
{ "_id" : "5715c502ac530ee5018b4955", "totalPerId" : NumberLong(700) }
{ "_id" : "5715c4bbac530eb3018b4567", "totalPerId" : NumberLong(500) }
{ "_id" : "5715c4bbac530eb3018b4569", "totalPerId" : NumberLong(300) }

Related

Mongodb Query to get the nth document

I need to create a query in mongodb that needs to return the SECOND TO THE LAST document. I am planning to use $group for this query but i dont know what aggregation function to use. I only know $first and $last.
I have an example collection below and also include the expected output. Thank you!
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b4"),
"auditlogs" : [
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84be"),
"userid" : ObjectId("5ffe702d59a9205db81fcb69"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bd"),
"userid" : ObjectId("5ffe644f9493e05db9245192"),
"action" : "EDITPROFILE"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bc"),
"userid" : ObjectId("5ffe64949493e05db9245197"),
"action" : "DELETETRANSACTION"
} ]
"_id" : ObjectId("60dc27ac54b7c46bfa1b75ge2"),
"auditlogs" : [
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84bb"),
"userid" : ObjectId("5ffe64b69493e05db924519b"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84ba"),
"userid" : ObjectId("5ffe65419493e05db92451d4"),
"action" : "ADDTRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b9"),
"userid" : ObjectId("5ffe65689493e05db92451d9"),
"action" : "CHANGEACCESS"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b8"),
"userid" : ObjectId("5ffe65819493e05db92451dd"),
"action" : "DELETETRANSACTION"
},
{
"_id" : ObjectId("60dc27ac54b7c46bfa1b84b7"),
"userid" : ObjectId("5ffe65df9493e05db92451f3"),
"action" : "EDITPROFILE",
]
OUTPUT:
{"_id" : ObjectId("60dc27ac54b7c46bfa1b84b4"),"_id" : ObjectId("60dc27ac54b7c46bfa1b84bd"),"userid" : ObjectId("5ffe644f9493e05db9245192"),"action" : "EDITPROFILE"},
{"_id" : ObjectId("60dc27ac54b7c46bfa1b75ge2"),"_id" : ObjectId("60dc27ac54b7c46bfa1b84b8"),"userid" : ObjectId("5ffe65819493e05db92451dd"),"action" : "DELETETRANSACTION"}
You can't have two _id keys in one single object.
I've made the parent object's id to _parentId you can give it's a name anything you want except _id
Aggregation:
db.collection.aggregate([
{
$unwind: "$auditlogs"
},
{
"$project": {
"_parentId": "$_id",
"_id": "$auditlogs._id",
"action": "$auditlogs.action",
"userid": "$auditlogs.userid",
}
}
])
Playground
You can slice the array by -2 to get the last two item, then by 1 to get first one. Therefore, the array will be left the second to the last. Finally, unwind auditlogs so it can be changed from array to object which is structure that you want.
db.collection.aggregate([
{
$project: { auditlogs : { $slice: [ "$auditlogs", -2 ] } }
},
{
$project: { auditlogs : { $slice: [ "$auditlogs", 1 ] } }
},
{
$unwind: "$auditlogs"
}
])

can monodb do this with $in,multiple input and fixed conditions,output like grouped result

there is a colleciont named group_members:
{
"_id" : ObjectId("5f817d142689512ef0bd3bd7"),
"uid" : NumberInt(1),
"group_id" : "g1"
}
{
"_id" : ObjectId("5f817d142689512ef0bd3bd8"),
"uid" : NumberInt(1),
"group_id" : "g2"
}
{
"_id" : ObjectId("5f817d142689512ef0bd3bd9"),
"uid" : NumberInt(2),
"group_id" : "g2"
}
{
"_id" : ObjectId("5f817d142689512ef0bd3bd1"),
"uid" : NumberInt(2),
"group_id" : "g3"
}
I want the result like this in a single query,the key of map is input uid:
{1:["g1","g2"], 2:["g2","g3"]}
The input is uid array,like this: [1,2],the condition is fixed of group_id array, like this:["g1","g2","g3"]
=================================
Can single query do this?
db.test.aggregate(
[
{ $match: {"uid":{$in:[1,2]}, "group_id":{$in: ["g1","g2","g3"]}}},
{ $group : { _id : "$uid", group_id : {$push: '$group_id' }}},
]
)

MongoDB: How do I sum up a unique field in $group aggregation query?

I have the following dataset after completing some aggregation magic:
{ "_id" : "5700edfe03fcdb000347bebb", "comment" : { "commentor" : "56f3f70d4de8c74a69d1d5e1", "id" : ObjectId("570175e6c002e46edb922aa1")}, "max" : ObjectId("570175e6c002e46edb922aa3")}
{ "_id" : "5700edfe03fcdb000347bebb", "comment" : { "commentor" : "56f3f70d4de8c74a69d1d5e6", "id" : ObjectId("570175e6c002e46edb922aa2")}, "max" : ObjectId("570175e6c002e46edb922aa3")}
{ "_id" : "5700edfe03fcdb000347bebb", "comment" : { "commentor" : "56f3f70d4de8c74a69d1d5e1", "id" : ObjectId("570175e6c002e46edb922aa3")}, "max" : ObjectId("570175e6c002e46edb922aa3")}
The _id represents a post and in the post, there are comments. In this case, there are 3 comments; 2 by the same commentor ("56f3f70d4de8c74a69d1d5e1") and one by another commentor ("56f3f70d4de8c74a69d1d5e6").
I want to write an aggregation query that would count up all the unique comments by commentor ("56f3f70d4de8c74a69d1d5e1") only and return that the commentor commented twice on post "5700edfe03fcdb000347bebb".
I tried the following:
{ "$group" : { "_id" : "$_id", "count" : { "$sum" : "$comment.commentor" } } }
The results were:
{ "_id" : "5700edfe03fcdb000347bebb", "count" : 0 }
Please note that I'm not trying to count up all the comments by all the commentors in that post so I'm not trying to do this:
{ "$group" : { "_id" : "$_id", "count" : { "$sum" : 1 } } }
Would result in:
{ "_id" : "5700edfe03fcdb000347bebb", "count" : 3 }
I just want the count of post by user ("56f3f70d4de8c74a69d1d5e1")
EDIT:
After some research, I see that $sum only works on numeric fields and not non-numeric fields: https://docs.mongodb.com/manual/reference/operator/aggregation/sum/#grp._S_sum
Is there any way I can get the number of comments posted by user ("56f3f70d4de8c74a69d1d5e1") per post "5700edfe03fcdb000347bebb"?
So after a bit of trial and error, I managed to figure it out.
group2 = {
"$group" : {
"_id" : "$_id",
"count" : {
"$sum" : {"$cond" : [ {"$eq" : ["$comms.c", "56f3f70d4de8c74a69d1d5e1"] }, 1 ,0 ] }
}
}
}
We are summing up the 1's on condition that comms.c equals to user "56f3f70d4de8c74a69d1d5e1".
Result:
{ "_id" : "5700edfe03fcdb000347bebb", "count" : 2 }

$elemMatch dosen't work after $unwind in MongoDB Aggregation Framework

I have a collection of the following data:
{
"_id" : ObjectId("51f1fcc08188d3117c6da351"),
"cust_id" : "abc123",
"ord_date" : ISODate("2012-10-03T18:30:00Z"),
"status" : "A",
"price" : 25,
"items" : [{
"sku" : "ggg",
"qty" : 7,
"price" : 2.5
}, {
"sku" : "ppp",
"qty" : 5,
"price" : 2.5
}]
}
I am using the query:
cmd { "aggregate" : "orders" , "pipeline" : [
{ "$unwind" : "$items"} ,
{ "$match" : { "items" : { "$elemMatch" : { "qty" : { "$in" : [ 7]}}}}} ,
{ "$group" : { "price" : { "$first" : "$price"} , "items" : { "$push" : { "sku" : "$items.sku"}} , "_id" : { "items" : "$items"}}} ,
{ "$sort" : { "price" : -1}} ,
{ "$project" : { "_id" : 0 , "price" : 1 , "items" : 1}}
]}
Not able to understand what is going wrong
It's because you're doing $match after $unwind. $unwind generates a new stream of documents where items is no longer an array (see docs).
It emits each document as many times as there are items in it.
If you want to select documents with desired element in it and then process all of its documents, you should call $match first:
db.orders.aggregate(
{ "$match" : { "items" : { "$elemMatch" : { "qty" : { "$in" : [ 7]}}}}},
{ "$unwind" : "$items"},
...
);
If you want to select items to be processed after $unwind, you shoul remove $elemMatch:
db.orders.aggregate(
{ "$unwind" : "$items"},
{ "$match" : { "items.qty" : { "$in" : [7]}}},
...
);
In first case you'll get two documents:
{
"price" : 25,
"items" : [
{"sku" : "ppp"}
]
},
{
"price" : 25,
"items" : [
{"sku" : "ggg"}
]
}
and in second case you'll get one:
{
"price" : 25,
"items" : [
{"sku" : "ggg"}
]
}
Update. After $unwind your documents will look like:
{
"_id" : ObjectId("51f1fcc08188d3117c6da351"),
"cust_id" : "abc123",
"ord_date" : ISODate("2012-10-03T18:30:00Z"),
"status" : "A",
"price" : 25,
"items" : {
"sku" : "ggg",
"qty" : 7,
"price" : 2.5
}
}
For small number of documents, unwind and match is fine. But large number of documents, it better to do - match ($elemMatch), unwind, and match again.
db.orders.aggregate(
{ "$match" : { "items" : { "$elemMatch" : { "qty" : { "$in" : [ 7]}}}}},
{ "$unwind" : "$items"},
{ "$match" : { "items.qty" : { "$in" : [7]}}}
...
...
);
The first match will filter only documents that match qty criteria. Among the selected documents, the second match will remove the subdocuments not matching the qty criteria.

Do $sort works for sub array document

I have a collection which has a field of array kind. I want to sort on the basis of a field of sub-array but Mongo is not sorting the data.
My collection is:
{
"_id" : ObjectId("51f1fcc08188d3117c6da351"),
"cust_id" : "abc123",
"ord_date" : ISODate("2012-10-03T18:30:00Z"),
"status" : "A",
"price" : 25,
"items" : [{
"sku" : "ggg",
"qty" : 7,
"price" : 2.5
}, {
"sku" : "ppp",
"qty" : 5,
"price" : 2.5
}]
}
My Query is:
db.orders.aggregate([
{ "$unwind" : "$items"} ,
{ "$match" : { }} ,
{ "$group" : { "items" : { "$addToSet" : { "sku" : "$items.sku"}} , "_id" : { }}} ,
{ "$sort" : { "items.sku" : 1}} ,
{ "$project" : { "_id" : 0 , "items" : 1}}
])
Result is:
"result" : [
{
"items" : [
{
"sku" : "ppp"
},
{
"sku" : "ggg"
}
]
}
],
"ok" : 1
}
Whereas "sku":"ggg" should come first when it is ascending.
You weant to do the sort BEFORE you regroup:
db.orders.aggregate([
{ "$unwind" : "$items"} ,
{ "$sort" : { "items.sku" : 1}},
{ "$match" : { }} ,
{ "$group" : { "items" : { "$push" : { "sku" : "$items.sku"}} , "_id" : null}} ,
{ "$project" : { "_id" : 0 , "items" : 1}}
])