MongoDb aggregation query with $group and $push into subdocument - mongodb

I have a question regarding the $group argument of MongoDb aggregations. My data structure looks as follows:
My "Event" collection contains this single document:
{
"_id": ObjectId("mongodbobjectid..."),
"name": "Some Event",
"attendeeContainer": {
"min": 0,
"max": 10,
"attendees": [
{
"type": 1,
"status": 2,
"contact": ObjectId("mongodbobjectidHEX1")
},
{
"type": 7,
"status": 4,
"contact": ObjectId("mongodbobjectidHEX2")
}
]
}
}
My "Contact" collection contains these documents:
{
"_id": ObjectId("mongodbobjectidHEX1"),
"name": "John Doe",
"age": 35
},
{
"_id": ObjectId("mongodbobjectidHEX2"),
"name": "Peter Pan",
"age": 60
}
What I want to do is perform an aggregate query on the "Event" collection and get the following result with full "contact" data:
{
"_id": ObjectId("mongodbobjectid..."),
"name": "Some Event",
"attendeeContainer": {
"min": 0,
"max": 10,
"attendees": [
{
"type": 1,
"status": 2,
"contact": {
"_id": ObjectId("mongodbobjectidHEX1"),
"name": "John Doe",
"age": 35
}
},
{
"type": 7,
"status": 4,
"contact": {
"_id": ObjectId("mongodbobjectidHEX2"),
"name": "Peter Pan",
"age": 60
}
}
]
}
}
The arguments I am using right now look as follows (shortened version):
"$unwind" : "$attendeeContainer.attendees",
"$lookup" : { "from" : "contactinfo", "localField" : "attendeeContainer.attendees.contact","foreignField" : "_id", "as" : "contactInfo" },
"$unwind" : "$contactInfo",
"$group" : { "_id": "$_id",
"name": { "$first" : "$name" },
...
"contact": { "$push": { "contact": "$contactInfo"} }
}
However, this leads to the "contact" array being on "Event" level (because of the grouping) instead of one document of the array being at each "attendeeContainer.attendees". How can I push the "contact" array to be at "attendeeContainer.attendees"? (as shown in the desired output above)
I tried things like:
"attendeeContainer.attendees.contact": { "$push": { "contact": "$contactInfo"} }
But mongodb apparently does not allow "." at $group stage.

Try running the following aggregation pipeline, the key is using a final $project pipeline to create the attendeeContainer subdocument:
db.event.aggregate([
{ "$unwind": "$attendeeContainer.attendees" },
{
"$lookup" : {
"from" : "contactinfo",
"localField" : "attendeeContainer.attendees.contact",
"foreignField" : "_id",
"as" : "attendeeContainer.attendees.contactInfo"
}
},
{ "$unwind": "$attendeeContainer.attendees.contactInfo" },
{
"$group": {
"_id" : "$_id",
"name": { "$first": "$name" },
"min" : { "$first": "$attendeeContainer.min" },
"max" : { "$first": "$attendeeContainer.max" },
"attendees": { "$push": "$attendeeContainer.attendees" }
}
},
{
"$project": {
"name": 1,
"attendeeContainer.min": "$min",
"attendeeContainer.max": "$min",
"attendeeContainer.attendees": "$attendees"
}
}
])
Debugging Tips
Debugging the pipeline at the 4th stage, you would get the result
db.event.aggregate([
{ "$unwind": "$attendeeContainer.attendees" },
{
"$lookup" : {
"from" : "contactinfo",
"localField" : "attendeeContainer.attendees.contact",
"foreignField" : "_id",
"as" : "attendeeContainer.attendees.contactInfo"
}
},
{ "$unwind": "$attendeeContainer.attendees.contactInfo" },
{
"$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"min" : { "$first": "$attendeeContainer.min" },
"max" : { "$first": "$attendeeContainer.max" },
"attendees": { "$push": "$attendeeContainer.attendees" }
}
}/*,
{
"$project": {
"name": 1,
"attendeeContainer.min": "$min",
"attendeeContainer.max": "$min",
"attendeeContainer.attendees": "$attendees"
}
}*/
])
Pipeline result
{
"_id" : ObjectId("582c789282a9183adc0b53f5"),
"name" : "Some Event",
"min" : 0,
"max" : 10,
"attendees" : [
{
"type" : 1,
"status" : 2,
"contact" : ObjectId("582c787682a9183adc0b53f3"),
"contactInfo" : {
"_id" : ObjectId("582c787682a9183adc0b53f3"),
"name" : "John Doe",
"age" : 35
}
},
{
"type" : 7,
"status" : 4,
"contact" : ObjectId("582c787682a9183adc0b53f4"),
"contactInfo" : {
"_id" : ObjectId("582c787682a9183adc0b53f4"),
"name" : "Peter Pan",
"age" : 60
}
}
]
}
and the final $project pipeline will give you the desired result:
db.event.aggregate([
{ "$unwind": "$attendeeContainer.attendees" },
{
"$lookup" : {
"from" : "contactinfo",
"localField" : "attendeeContainer.attendees.contact",
"foreignField" : "_id",
"as" : "attendeeContainer.attendees.contactInfo"
}
},
{ "$unwind": "$attendeeContainer.attendees.contactInfo" },
{
"$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"min" : { "$first": "$attendeeContainer.min" },
"max" : { "$first": "$attendeeContainer.max" },
"attendees": { "$push": "$attendeeContainer.attendees" }
}
},
{
"$project": {
"name": 1,
"attendeeContainer.min": "$min",
"attendeeContainer.max": "$min",
"attendeeContainer.attendees": "$attendees"
}
}/**/
])
Desired/Actual Output
{
"_id" : ObjectId("582c789282a9183adc0b53f5"),
"name" : "Some Event",
"attendeeContainer" : {
"min" : 0,
"max" : 10,
"attendees" : [
{
"type" : 1,
"status" : 2,
"contact" : ObjectId("582c787682a9183adc0b53f3"),
"contactInfo" : {
"_id" : ObjectId("582c787682a9183adc0b53f3"),
"name" : "John Doe",
"age" : 35
}
},
{
"type" : 7,
"status" : 4,
"contact" : ObjectId("582c787682a9183adc0b53f4"),
"contactInfo" : {
"_id" : ObjectId("582c787682a9183adc0b53f4"),
"name" : "Peter Pan",
"age" : 60
}
}
]
}
}

Related

Mongodb how to reduce the array within the matching key and calculate avg

{
"_id" : {
"state" : "NY",
"st" : "value"
},
"List" : [
{
"id" : "21",
"score" : 18.75,
"name" : "PU"
},
{
"id" : "21",
"score" : 25.0,
"name" : "PU"
},
{
"id" : "23",
"score" : 25.0,
"name" : "CL"
},
{
"id" : "23",
"score" : 56.25,
"name" : "CL"
}
]
}
Desired result:
Match the key with id within the array and calculate avg of score.
{
"_id" : {
"state" : "New York",
"st" : "value"
},
"List" : [
{
"id" : "21",
"score" : 21.875,
"name" : "PU"
},
{
"id" : "23",
"score" : 40.625,
"name" : "CL"
}
]
}
Thank you in advance.
Query
(returns the expected result)
unwind List
group with including the id, and find avg
fix the structure to be similar with the document you want
group back to restore the document structure (reverse the unwind)
if 2 sames ids have different name(if possible to happen)
query will make them seperated members in the array.
(alternativly it could make them same member and pack the names in an array, but that would produce different schema from the one you expect to see)
Test code here
db.collection.aggregate([
{
"$unwind": {
"path": "$List"
}
},
{
"$group": {
"_id": {
"state": "$_id.state",
"st": "$_id.st",
"id": "$List.id",
"name": "$List.name"
},
"avg": {
"$avg": "$List.score"
}
}
},
{
"$project": {
"_id": {
"state": "$_id.state",
"st": "$_id.st"
},
"List": {
"name": "$_id.name",
"id": "$_id.id",
"avg": "$avg"
}
}
},
{
"$group": {
"_id": "$_id",
"List": {
"$push": "$List"
}
}
}
])

how to use $subtract in $group in mongodb

I have below mongo record in purchase table with individual quantity
{
"_id" : ObjectId("60f0a8fa7f73e1b4883e2b26"),
"status" : "Sold",
"isSold" : true,
"unitcost" : 4.5,
"mrp" : 220,
"unitprice" : 6,
"product" : ObjectId("60f0a7c8355781b277607f19"),
"manufacturingdate" : ISODate("2021-06-30T18:30:00.000Z"),
"expirydate" : ISODate("2021-07-29T18:30:00.000Z"),
"customer" : ObjectId("60f0975a93ba0ba2152a1ec0")
},
{
"_id" : ObjectId("60f0a8fa7f73e1b4883e2b87"),
"status" : "Sold",
"isSold" : true,
"unitcost" : 4.5,
"mrp" : 220,
"unitprice" : 6,
"product" : ObjectId("60f0a7c8355781b277607f19"),
"manufacturingdate" : ISODate("2021-06-30T18:30:00.000Z"),
"expirydate" : ISODate("2021-07-29T18:30:00.000Z"),
"customer" : ObjectId("60f0975a93ba0ba2152a1ec0")
}
Want data like below Product and customer from there collection and group by product
{
"productDetails":{
"_id":ObjectId("60f0a7c8355781b277607f19"),
"title":"Prodcut 1"
},
"profit":3.0,
"unitprice":6,
"unitcost":4.5,
"customerDetails":{
"_id": ObjectId("60f0975a93ba0ba2152a1ec0"),
"title":"User 1"
}
}
pls refer to Sample code below and also find it here https://mongoplayground.net/p/S9xbt9Lnk5d :
db.purchase.aggregate([
{
"$addFields": {
"profit": {
"$subtract": [
"$unitprice",
"$unitcost"
]
}
}
},
{
"$group": {
"_id": "$product",
"profit": {
"$sum": "$profit"
},
"unitprice": {
"$first": "$unitprice"
},
"unitcost": {
"$first": "$unitcost"
},
"customerDetails": {
"$push": {
_id: "$_id"
}
},
}
}
])

Merge mongodb aggregation results of unread messages count per conversation member into document

I want to aggregate unread messages count per member in a conversation group and seems my aggregation pipeline is working correctly, but, I don't know how to achieve the following results.
Please check the example.
Thread documents
/* 1 */
{
"_id" : ObjectId("60d4efa6a95f446051f31492"),
"latestMessage" : "Voluptatem eos officiis optio dolor est et.",
"type" : "FEED",
"users" : [
{
"_id" : ObjectId("60d4efa6a95f446051f31491"),
"displayName" : "Monique Connelly II"
},
{
"_id" : ObjectId("60d4efa6a95f446051f31490"),
"displayName" : "Ivory Jacobson DDS"
},
{
"_id" : ObjectId("60d4efa6a95f446051f3148f"),
"displayName" : "Ron Weimann"
}
],
"createdBy" : "60d4efa6a95f446051f3148f",
"createdAt" : ISODate("2021-06-24T20:48:38.537Z"),
"modifiedAt" : ISODate("2021-06-24T20:48:38.620Z"),
}
/* 2 */
{
"_id" : ObjectId("60d4efa6a95f446051f31493"),
"type" : "CONVERSATION",
"users" : [
{
"_id" : ObjectId("60d4efa6a95f446051f31491"),
"displayName" : "Monique Connelly II"
},
{
"_id" : ObjectId("60d4efa6a95f446051f31490"),
"displayName" : "Ivory Jacobson DDS"
},
{
"_id" : ObjectId("60d4efa6a95f446051f3148f"),
"displayName" : "Ron Weimann"
}
],
"createdBy" : "60d4efa6a95f446051f3148f",
"createdAt" : ISODate("2021-06-24T20:48:38.569Z"),
"modifiedAt" : ISODate("2021-06-24T20:48:38.569Z"),
}
Message documents
/* 1 */
{
"_id" : ObjectId("60d4efa6a95f446051f31494"),
"content" : "Itaque rerum facere neque fuga aspernatur dolorum.",
"deleted" : false,
"threadId" : ObjectId("60d4efa6a95f446051f31492"),
"type" : "TEXT",
"thread" : {
"$ref" : "threads",
"$id" : ObjectId("60d4efa6a95f446051f31492")
},
"readBy" : [
ObjectId("60d4efa6a95f446051f3148f")
],
"attributes" : [],
"createdBy" : "60d4efa6a95f446051f3148f",
"createdAt" : ISODate("2021-06-24T20:48:38.598Z"),
"modifiedAt" : ISODate("2021-06-24T20:48:38.598Z"),
}
/* 2 */
{
"_id" : ObjectId("60d4efa6a95f446051f31495"),
"content" : "Nisi suscipit iste magni voluptatem.",
"deleted" : false,
"threadId" : ObjectId("60d4efa6a95f446051f31492"),
"type" : "TEXT",
"thread" : {
"$ref" : "threads",
"$id" : ObjectId("60d4efa6a95f446051f31492")
},
"readBy" : [
ObjectId("60d4efa6a95f446051f3148f")
],
"attributes" : [],
"createdBy" : "60d4efa6a95f446051f3148f",
"createdAt" : ISODate("2021-06-24T20:48:38.610Z"),
"modifiedAt" : ISODate("2021-06-24T20:48:38.610Z"),
}
and more...
My aggregation pipeline
db.threads.aggregate([
{ "$lookup": { "from": "messages", "localField": "_id", "foreignField": "threadId", "as": "message"}},
{ "$unwind": "$message" },
{ "$unwind": "$users" },
{ "$unwind": "$message.readBy" },
{ "$group": {
"_id": {
"user_id": "$users._id",
"thread_id": "$_id",
},
"unread_messages": {
"$sum": {
"$cond": [
{ "$ne": [ "$users._id", "$message.readBy" ] },
1,
0
]
}
}
}},
// { "$replaceRoot": { "newRoot": { "$mergeObjects": ["$_id", { "count": "$unread_messages" }]} } }
])
Aggregation results
{
"_id" : {
"user_id" : ObjectId("60d4efa6a95f446051f31491"),
"thread_id" : ObjectId("60d4efa6a95f446051f31492")
},
"unread_messages" : 4.0
},
{
"_id" : {
"user_id" : ObjectId("60d4efa6a95f446051f3148f"),
"thread_id" : ObjectId("60d4efa6a95f446051f31492")
},
"unread_messages" : 4.0
},
{
"_id" : {
"user_id" : ObjectId("60d4efa6a95f446051f31490"),
"thread_id" : ObjectId("60d4efa6a95f446051f31492")
},
"unread_messages" : 4.0
}
Results are partially OK, but, I want to keep my original structure of a document and copy aggregation results accordingly to the thread id back to the original document
I would be very grateful if you help me with the aggregation pipeline
Expected results
{
"_id": ObjectId("60d4efa6a95f446051f31492"),
"latestMessage": "Voluptatem eos officiis optio dolor est et.",
"type": "LISTING",
"users": [{
"_id": ObjectId("60d4efa6a95f446051f31491"),
"displayName": "Monique Connelly II"
},
{
"_id": ObjectId("60d4efa6a95f446051f31490"),
"displayName": "Ivory Jacobson DDS"
},
{
"_id": ObjectId("60d4efa6a95f446051f3148f"),
"displayName": "Ron Weimann"
}
],
"createdBy": "60d4efa6a95f446051f3148f",
"createdAt": ISODate("2021-06-24T20:48:38.537Z"),
"modifiedAt": ISODate("2021-06-24T20:48:38.620Z"),
"message": [...],
"stats": [{
"_id": {
"user_id": ObjectId("60d4efa6a95f446051f31491"),
"thread_id": ObjectId("60d4efa6a95f446051f31492")
},
"unread_messages": 4.0
},
{
"_id": {
"user_id": ObjectId("60d4efa6a95f446051f3148f"),
"thread_id": ObjectId("60d4efa6a95f446051f31492")
},
"unread_messages": 4.0
},
{
"_id": {
"user_id": ObjectId("60d4efa6a95f446051f31490"),
"thread_id": ObjectId("60d4efa6a95f446051f31492")
},
"unread_messages": 4.0
}
]
}
I post a solution that works perfectly for my case with $first + $replateRoot operations.
Final results have different ids as not in the first post that is because I re-created documents
db.threads.aggregate([
{ "$lookup": { "from": "messages", "localField": "_id", "foreignField": "threadId", "as": "message"}},
{ "$unwind": "$message" },
{ "$unwind": "$users" },
{ "$unwind": "$message.readBy" },
{ "$group": {
"_id": {
"user_id": "$users._id",
"thread_id": "$_id",
},
"thread": {
"$first": "$$ROOT"
},
"unread_messages": {
"$sum": {
"$cond": [
{ "$ne": [ "$users._id", "$message.readBy" ] },
1,
0
]
}
}
}},
{ "$group": {
"_id": "$_id.thread_id",
"thread": { "$first": "$thread" },
"stats": {
"$push": {
"userId": "$_id.user_id",
"unreadMessages": "$unread_messages"
}
}
}},
{ "$replaceRoot": { "newRoot": { "$mergeObjects": ["$thread", { stats: "$stats" }]} } }
])
Final results
/* 1 */
{
"_id" : ObjectId("60d574e242e59a48b886c586"),
"latestMessage" : "Molestias quo quod occaecati exercitationem veniam eaque.",
"type" : "LISTING",
"users" : {
"_id" : ObjectId("60d574e242e59a48b886c584"),
"displayName" : "Meggan Vandervort"
},
"unreadMessages" : NumberLong(0),
"createdBy" : "60d574e242e59a48b886c582",
"createdAt" : ISODate("2021-06-25T06:17:06.547Z"),
"modifiedAt" : ISODate("2021-06-25T06:17:06.617Z")
"message" : {
"_id" : ObjectId("60d574e242e59a48b886c58d"),
"content" : "Velit dolores vel.",
"deleted" : false,
"threadId" : ObjectId("60d574e242e59a48b886c586"),
"type" : "TEXT",
"thread" : {
"$ref" : "threads",
"$id" : ObjectId("60d574e242e59a48b886c586")
},
"readBy" : ObjectId("60d574e242e59a48b886c582"),
"attributes" : [],
"createdBy" : "60d574e242e59a48b886c582",
"createdAt" : ISODate("2021-06-25T06:17:06.595Z"),
"modifiedAt" : ISODate("2021-06-25T06:17:06.595Z")
},
"stats" : [
{
"userId" : ObjectId("60d574e242e59a48b886c584"),
"unreadessages" : 6.0
},
{
"userId" : ObjectId("60d574e242e59a48b886c583"),
"unreadessages" : 6.0
},
{
"userId" : ObjectId("60d574e242e59a48b886c582"),
"unreadessages" : 6.0
}
]
}
and more...

Mongodb aggregate with cond and query value

I'm new to mongodb. I need to know how it is possible to query item for set to the value with aggregate
Data
[
{
"_id" : "11111",
"parent_id" : "99",
"name" : "AAAA"
},
{
"_id" : "11112",
"parent_id" : "99",
"name" : "BBBB"
},
{
"_id" : "11113",
"parent_id" : "100",
"name" : "CCCC"
},
{
"_id" : "11114",
"parent_id" : "99",
"name" : "DDDD"
}
]
mongoshell
Assume $check is false
db.getCollection('test').aggregate(
[
{
"$group": {
"_id": "$id",
//...,
"item": {
"$last": {
"$cond": [
{"$eq": ["$check", true]},
"YES",
* * ANSWER **,
}
]
}
},
}
]
)
So i need the result for item is all the name contain with same parent_id as string of array
Expect result
[
{
"_id" : "11111",
"parent_id" : "99",
"name" : "AAAA",
"item" : ["AAAA","BBBB","DDDD"]
},
{
"_id" : "11112",
"parent_id" : "99",
"name" : "BBBB",
"item" : ["AAAA","BBBB","DDDD"]
},
{
"_id" : "11113",
"parent_id" : "100",
"name" : "CCCC",
"item" : ["CCCC"]
},
{
"_id" : "11114",
"parent_id" : "99",
"name" : "DDDD",
"item" : ["AAAA","BBBB","DDDD"]
}
]
Try this..
Sample live demo
db.collection.aggregate([
{
"$group": {
"_id": "$parent_id",
"item": {
"$push": "$name"
},
"data": {
"$push": {
"_id": "$_id",
"name": "$name"
}
}
}
},
{
"$unwind": "$data"
},
{
"$project": {
"_id": "$data._id",
"parent_id": "$_id",
"name": "$data.name",
"item": 1
}
}
])

mongodb aggregation with array

I have data like this:
{
"_id" : ObjectId("..."),
"name" : "Entry 1",
"time" : ISODate("2013-12-28T06:00:00.000Z"),
"value" : 100
},
{
"_id" : ObjectId("..."),
"name" : "Entry 2",
"time" : ISODate("2013-12-28T06:00:00.000Z"),
"value" : 200
},
{
"_id" : ObjectId("..."),
"name" : "Entry 1",
"time" : ISODate("2013-12-28T11:00:00.000Z"),
"value" : 110
},
{
"_id" : ObjectId("..."),
"name" : "Entry 2",
"time" : ISODate("2013-12-28T11:00:00.000Z"),
"value" : 230
},
{
"_id" : ObjectId("..."),
"name" : "Entry 3",
"time" : ISODate("2013-12-28T11:00:00.000Z"),
"value" : 25
},
{
"_id" : ObjectId("..."),
"name" : "Entry 4",
"time" : ISODate("2013-12-28T11:00:00.000Z"),
"value" : 15
}
I need the result grouped by time with percentage for each entry like this (group entries by volume "others" when entries for time period more than two, but it's not necessary):
{
"_id": ISODate("2013-12-28T11:00:00.000Z"),
"entries": [
{
"name": "Entry 1",
"percentage": 33.3
},
{
"name": "Entry 2",
"percentage": 66.6
},
]
},
{
"_id": ISODate("2013-12-28T06:00:00.000Z"),
"entries": [
{
"name": "Entry 1",
"percentage": 28.9
},
{
"name": "Entry 2",
"percentage": 60.5
},
{
"name": "Others",
"percentage": 10.5
}
]
}
So the request I was try:
db.collection.aggregate([
{
"$addFields": {
"full_datetime": {"$substr": ["$time", 0, 19]}
}
},
{
"$group": {
"_id": "$full_datetime",
"value_sum": {"$sum": "$value"},
"entries": {
"$push": {
"name": "$name",
"percentage": {
"$multiply": [{
"$divide": ["$value", {"$literal": "$value_sum" }]
}, 100 ]
}
}
}
}
}
])
This request is not work because $value_sum does not exists inside $push.
Please help me how I can to send this $value_sum into the $push statement
You can use one more stage to calculate percentage using $map as,
db.collection.aggregate([
"$addFields": {
"full_datetime": {
"$substr": ["$time", 0, 19]
}
}
}, {
"$group": {
"_id": "$full_datetime",
"value_sum": {
"$sum": "$value"
},
"entries": {
"$push": {
"name": "$name",
"value": "$value"
}
}
}
}, {
"$project": {
"entriesNew": {
"$map": {
"input": "$entries",
"as": "entry",
"in": {
"name": "$$entry.name",
"percentage": {
"$multiply": [{
"$divide": ["$$entry.value", "$value_sum"]
}, 100]
}
}
}
}
}
}])
Output:
/* 1 */
{
"_id" : "2013-12-28T11:00:00",
"entries" : [
{
"name" : "Entry 1",
"percentage" : 28.9473684210526
},
{
"name" : "Entry 2",
"percentage" : 60.5263157894737
},
{
"name" : "Entry 3",
"percentage" : 6.57894736842105
},
{
"name" : "Entry 4",
"percentage" : 3.94736842105263
}
]
}
/* 2 */
{
"_id" : "2013-12-28T06:00:00",
"entries" : [
{
"name" : "Entry 1",
"percentage" : 33.3333333333333
},
{
"name" : "Entry 2",
"percentage" : 66.6666666666667
}
]
}