MongoDB: Get most recent objects from embedded arrays and append parent field - mongodb

I have an array of companies where each company has an array of subscription objects. A subscription object has an array of items and a date when those items was added. I want to retrieve only the most recently added subscription object from each company. I also need to be able to identify where each subscription object came from, so I need to append the company name somehow. Sorry for the confusing semantics!
The array of companies:
[
{
"_id": "company1",
"name": "Company 1",
"subscriptions": [
{
"items": [
{
"_id": "5e13b0207b05e37f12f05beb",
"data": 40,
"price": 39
},
{
"_id": "5e13b0207b05e37f12f05bea",
"data": 100,
"price": 59
}
],
"_id": "5e13b0207b05e37f12f05be9",
"date_added": "2019-12-24T22:09:36.442Z"
}
]
},
{ "_id": "company2", "name": "Company 2", "subscriptions": [] },
{
"_id": "company3",
"name": "Company 3",
"subscriptions": [
{
"items": [
{
"_id": "5e13a47d7c370667c7e67d7a",
"data": 40,
"price": 69
},
{
"_id": "5e13a47d7c370667c7e67d79",
"data": 20,
"price": 39
}
],
"_id": "5e13a47d7c370667c7e67d78",
"date_added": "2019-12-24T21:19:57.804Z"
},
{
"items": [
{
"_id": "5e13a4f87c370667c7e67d7d",
"data": 35,
"price": 39
},
{
"_id": "5e13a4f87c370667c7e67d7c",
"data": 60,
"price": 59
}
],
"_id": "5e13a4f87c370667c7e67d7b",
"date_added": "2020-01-04T21:22:00.832Z"
}
]
}
]
Desired output:
[
{
"name": "Company 1",
"items": [
{
"_id": "5e13b0207b05e37f12f05beb",
"data": 40,
"price": 39
},
{
"_id": "5e13b0207b05e37f12f05bea",
"data": 100,
"price": 59
}
],
"_id": "5e13b0207b05e37f12f05be9",
"date_added": "2019-12-24T22:09:36.442Z"
},
{
"name": "Company 3",
"items": [
{
"_id": "5e13a4f87c370667c7e67d7d",
"data": 35,
"price": 39
},
{
"_id": "5e13a4f87c370667c7e67d7c",
"data": 60,
"price": 59
}
],
"_id": "5e13a4f87c370667c7e67d7b",
"date_added": "2020-01-04T21:22:00.832Z"
}
]
What I am trying:
await Companies.aggregate([
{ $unwind: '$subscriptions' },
{ $sort: { 'subscriptions.date_added': -1 } },
{ $group: { _id: '$_id' } }
])
Output:
[{"_id":"company1"},{"_id":"company3"}]
I think I filtered out the correct objects (the most recent ones) but I am only retrieving the _id of the root element, not the nested subscription objects. These id's might be used to identify the subscriptions, but I need also need the actual subscription items.

As all you want is latest subscription object, if that means if it's the last object being pushed to subscriptions array then, Try this :
Companies.aggregate([{$match : {$and :[{subscriptions: {$exists: true}}, {subscriptions: {$ne: []}}]}},
{$addFields :{subscriptions: {$arrayElemAt : ['$subscriptions',-1]} }}, {$project :{_id:0,subscriptions:1, name:1 }}])
Collection data :
/* 1 */
{
"_id" : "company1",
"name" : "Company 1",
"subscriptions" : [
{
"items" : [
{
"_id" : "5e13b0207b05e37f12f05beb",
"data" : 40.0,
"price" : 39.0
},
{
"_id" : "5e13b0207b05e37f12f05bea",
"data" : 100.0,
"price" : 59.0
}
],
"_id" : "5e13b0207b05e37f12f05be9",
"date_added" : "2019-12-24T22:09:36.442Z"
}
]
}
/* 2 */
{
"_id" : "company2",
"name" : "Company 2",
"subscriptions" : []
}
/* 3 */
{
"_id" : "company3",
"name" : "Company 3",
"subscriptions" : [
{
"items" : [
{
"_id" : "5e13a47d7c370667c7e67d7a",
"data" : 40.0,
"price" : 69.0
},
{
"_id" : "5e13a47d7c370667c7e67d79",
"data" : 20.0,
"price" : 39.0
}
],
"_id" : "5e13a47d7c370667c7e67d78",
"date_added" : "2019-12-24T21:19:57.804Z"
},
{
"items" : [
{
"_id" : "5e13a4f87c370667c7e67d7d",
"data" : 35.0,
"price" : 39.0
},
{
"_id" : "5e13a4f87c370667c7e67d7c",
"data" : 60.0,
"price" : 59.0
}
],
"_id" : "5e13a4f87c370667c7e67d7b",
"date_added" : "2020-01-04T21:22:00.832Z"
}
]
}
Result :
/* 1 */
{
"name" : "Company 1",
"subscriptions" : {
"items" : [
{
"_id" : "5e13b0207b05e37f12f05beb",
"data" : 40.0,
"price" : 39.0
},
{
"_id" : "5e13b0207b05e37f12f05bea",
"data" : 100.0,
"price" : 59.0
}
],
"_id" : "5e13b0207b05e37f12f05be9",
"date_added" : "2019-12-24T22:09:36.442Z"
}
}
/* 2 */
{
"name" : "Company 3",
"subscriptions" : {
"items" : [
{
"_id" : "5e13a4f87c370667c7e67d7d",
"data" : 35.0,
"price" : 39.0
},
{
"_id" : "5e13a4f87c370667c7e67d7c",
"data" : 60.0,
"price" : 59.0
}
],
"_id" : "5e13a4f87c370667c7e67d7b",
"date_added" : "2020-01-04T21:22:00.832Z"
}
}

In your solution, you need to accumulate subscriptions with $first operator and with $project you can get desired result.
db.companies.aggregate([
{
$unwind: "$subscriptions"
},
{
$sort: {
"subscriptions.date_added": -1
}
},
{
$group: {
_id: "$_id",
name: {
$first: "$name"
},
subscriptions: {
$first: "$subscriptions"
}
}
},
{
$project: {
_id: "$subscriptions._id",
name: "$name",
date_added: "$subscriptions.date_added",
items: "$subscriptions.items"
}
}
])
MongoPlayground

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"
}
},
}
}
])

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
}
]
}

MongoDb aggregation query with $group and $push into subdocument

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
}
}
]
}
}

MongoDB: Return objects in a nested array that has the most recent date

I have the following collection:
{
"_id" : ObjectId("56f036e032ea1f27634e2f1f"),
"mockups" : [
{
"versions" : [
{
"title" : "About us 1",
"timestamp" : "2016-01-10T12:31:23.104Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17922")
},
{
"title" : "About us 3",
"timestamp" : "2016-03-11T15:34:11.108Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17923")
},
{
"title" : "About us 2",
"timestamp" : "2016-02-21T16:15:23.101Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17924")
}
],
"_id" : ObjectId("56ec65a9041c87dd6bd17921")
},
{
"versions" : [
{
"title" : "Contact us 1",
"timestamp" : "2016-04-10T11:34:33.103Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17924")
},
{
"title" : "Contact us 3",
"timestamp" : "2016-06-21T16:13:26.101Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17926")
},
{
"title" : "Contact us 2",
"timestamp" : "2016-05-11T13:34:13.106Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17925")
}
],
"_id" : ObjectId("56ec65a9041c87dd6bd17929")
}
]
}
I want to return all mockups with the latest version that would result in something similar to the following:
{
"_id" : ObjectId("56f036e032ea1f27634e2f1f"),
"mockups" : [
{
"versions" : [
{
"title" : "About us 3",
"timestamp" : "2016-03-11T15:34:11.108Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17923")
}
],
"_id" : ObjectId("56ec65a9041c87dd6bd17921")
},
{
"versions" : [
{
"title" : "Contact us 3",
"timestamp" : "2016-06-21T16:13:26.101Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17926")
}
],
"_id" : ObjectId("56ec65a9041c87dd6bd17929")
}
]
}
I have been playing with aggregate, sort, and limit, but I am extremely new to mongodb. I am currently left with having to just return everything and using something like lodash to get the version I need.
Is there a way to query this properly so that I am getting the results I need from the mongo as opposed to using something like lodash after I get the results back?
For a solution using aggregation, would suggest running the following pipeline to get the desired output:
db.collection.aggregate([
{ "$unwind": "$mockups" },
{ "$unwind": "$mockups.versions" },
{ "$sort": { "mockups.versions.timestamp": -1 } },
{
"$group": {
"_id": "$mockups._id",
"title": { "$first": "$mockups.versions.title" },
"timestamp": { "$first": "$mockups.versions.timestamp" },
"id" : { "$first": "$mockups.versions._id" },
"main_id": { "$first": "$_id" }
}
},
{
"$group": {
"_id": "$_id",
"versions": {
"$push": {
"title": "$title",
"timestamp": "$timestamp",
"_id": "$id"
}
},
"main_id": { "$first": "$main_id" }
}
},
{
"$group": {
"_id": "$main_id",
"mockups": {
"$push": {
"versions": "$versions",
"_id": "$_id"
}
}
}
}
])
I didn't actually understood what is the range for "latest" version ,however I think you can use some thing like this:
example:
db.collection.find({
versions.timestamp: {
$gte: ISODate("2010-04-29T00:00:00.000Z"),
$lt: ISODate("2010-05-01T00:00:00.000Z")
}
})
if you want to use the aggregate you can put this condition in the $match .