MongoDB $facet aggregation - mongodb

I'm having a problem in my $facet aggregation. I want to know also if it is byAllProduct I will know that this product is the bestSeller, newlyAdded, and mostReviewed.
$facet: {
"byBestSeller": [
{ $sort: { "sold":-1 }},
{ $limit: 1 }
],
"byMostReviewed": [
{ $addFields: { "noOfReviews": {$size:"$reviews.message"}}},
{ $sort: { "noOfReviews":-1 }},
{ $limit: 1 }
],
"byNewlyAdded": [
{ $sort: { "createdAt":-1 }},
{ $limit: 1 }
],
"byAllProducts": [
{ $limit: 100 }
],
}
Now, my result is this.
"byBestSeller": [
{
"sold": 150,
"name": "Big Mac",
"shop": [
{
"name": "McDonald's"
}
],
"createdAt": {
"2020-05-11T01:55:17.623Z"
},
"reviews": [
{
"message": "Yummylicious"
},
{
"message": "Yummy burger"
}
]
}
],
"byMostReviewed": [
{
"sold": 100,
"name": "Spaghetti",
"shop": [
{
"name": "McDonald's"
}
],
"createdAt": {
"2020-06-11T01:55:17.623Z"
},
"reviews": [
{
"message": "Yummylicious"
},
{
"message": "Yummy Spaghetti"
},
{
"message": "Yummy super rich spag."
}
],
"noOfReviews": 3
}
],
"byNewlyArrived": [
{
"sold": 0,
"name": "Fries",
"shop": [
{
"name": "McDonald's"
}
],
"createdAt": {
"2020-7-11T01:55:17.623Z"
},
"reviews": [
{
"message": "Yummylicious"
},
]
}
],
"byAllProducts": [
{
"sold": 150,
"name": "Big Mac",
"shop": [
{
"name": "McDonald's"
}
],
"createdAt": {
"2020-05-11T01:55:17.623Z"
},
"reviews": [
{
"message": "Yummylicious"
},
{
"message": "Yummy burger"
}
]
}
{
"sold": 100,
"name": "Fries",
"shop": [
{
"name": "McDonald's"
}
],
"createdAt": {
"2020-06-11T01:55:17.623Z"
},
"reviews": [
{
"message": "Yummylicious"
},
{
"message": "Yummy Spaghetti"
},
{
"message": "Yummy super rich spag."
}
]
},
{
"sold": 0,
"name": "Fries",
"shop": [
{
"name": "McDonald's"
}
],
"createdAt": {
"2020-07-11T01:55:17.623Z"
},
"reviews": [
{
"message": "Yummylicious"
},
]
}
],
Is there a way so that even if it is included in all products I can know that it is the bestSeller, mostReviewed, and newlyarrived?

Related

Remove unwanted key on nested unique keys MongoDB

I have this kind of mongodb document example
"data": {
"2023-02-01": {
"123": {
"price": 100,
},
"234": {
"price": 100,
},
},
"2023-02-02": {
"123": {
"price": 100,
},
"234": {
"price": 100,
},
},
"2023-02-03": {
"123": {
"price": 100,
},
"234": {
"price": 100,
},
},
}
I have list of mapped ID on my aystem, it should be like
ids = [123]
I want to remove the key that not in the list (ids) from the document, started from a specific date (today/"2023-02-02"), the date always updated and so the ID, my expected result is
"data": {
"2023-02-01": {
"123": {
"price": 100,
},
"234": {
"price": 100,
},
},
"2023-02-02": {
"123": {
"price": 100,
},
},
"2023-02-03": {
"123": {
"price": 100,
},
},
}
Could I achieve that on MongoDB aggregation? I'm using pymongo
Following the discussion in comments, if refactoring the schema is an option, you can achieve what you need in very simple query.
db.collection.update({
"date": {
$gte: ISODate("2023-02-02")
}
},
[
{
$set: {
value: {
$filter: {
input: "$value",
as: "v",
cond: {
$in: [
"$$v.key",
[
"123"
]
]
}
}
}
}
}
],
{
multi: true
})
Mongo Playground
The schema I am proposing:
[
{
"date": ISODate("2023-02-01"),
"value": [
{
"key": "123",
"price": 100
},
{
"key": "234",
"price": 100
}
]
},
{
"date": ISODate("2023-02-02"),
"value": [
{
"key": "123",
"price": 100
},
{
"key": "234",
"price": 100
}
]
},
{
"date": ISODate("2023-02-03"),
"value": [
{
"key": "123",
"price": 100
},
{
"key": "234",
"price": 100
}
]
}
]
You can see there is a few things:
avoided using dynamic value as field name
formatted date as proper date objects
avoided highly nesting arrays/objects

Get a Single Object rather than and Array with One Object in MongoDB aggration

I have the following query from my Playground
Sample dataset:
[
{
"_id": {
"$oid": "637742400b359f51cb95798d"
},
"learnerId": "6377422e8a43630dcfdfd410",
"targetExam": "LON11PLUS",
"subject": "VR",
"sections": [
{
"name": "Verbal Reasoning",
"timePerSection": {
"$numberInt": "960"
},
"events": [
{
"$numberInt": "2329"
},
{
"$numberInt": "2053"
},
{
"$numberInt": "3486"
},
{
"$numberInt": "3826"
},
{
"$numberInt": "3336"
},
{
"$numberInt": "2950"
},
{
"$numberInt": "2009"
},
{
"$numberInt": "4637"
},
{
"$numberInt": "3308"
},
{
"$numberInt": "2884"
},
{
"$numberInt": "2072"
},
{
"$numberInt": "3269"
},
{
"$numberInt": "2498"
},
{
"$numberInt": "2647"
},
{
"$numberInt": "2619"
},
{
"$numberInt": "3600"
},
{
"$numberInt": "2283"
},
{
"$numberInt": "3597"
},
{
"$numberInt": "2419"
},
{
"$numberInt": "1991"
}
]
}
],
"created": "2022-11-18 08:28:48.105743",
"session": {
"id": "63778c23960b1e7e97353eea"
},
"completed": {
"$date": {
"$numberLong": "1668779074955"
}
},
"score": {
"$numberInt": "2"
}
},
{
"_id": {
"$oid": "637742590b359f51cb957990"
},
"learnerId": "6377422e8a43630dcfdfd410",
"targetExam": "LON11PLUS",
"subject": "ENG",
"sections": [
{
"name": "English",
"timePerSection": {
"$numberInt": "1500"
},
"events": [
{
"$numberInt": "779"
},
{
"$numberInt": "785"
},
{
"$numberInt": "791"
},
{
"$numberInt": "786"
},
{
"$numberInt": "784"
},
{
"$numberInt": "792"
},
{
"$numberInt": "783"
},
{
"$numberInt": "795"
},
{
"$numberInt": "781"
},
{
"$numberInt": "3041"
},
{
"$numberInt": "2053"
},
{
"$numberInt": "3497"
},
{
"$numberInt": "3840"
},
{
"$numberInt": "3023"
},
{
"$numberInt": "2285"
},
{
"$numberInt": "3022"
},
{
"$numberInt": "4644"
},
{
"$numberInt": "2477"
},
{
"$numberInt": "2472"
},
{
"$numberInt": "3338"
},
{
"$numberInt": "3270"
},
{
"$numberInt": "2018"
},
{
"$numberInt": "2288"
},
{
"$numberInt": "2260"
},
{
"$numberInt": "3603"
},
{
"$numberInt": "2463"
},
{
"$numberInt": "3600"
},
{
"$numberInt": "3312"
}
]
}
],
"created": "2022-11-18 08:29:13.804332",
"session": {
"id": "63777e1c960b1e7e97353d15"
},
"completed": {
"$date": {
"$numberLong": "1668775515232"
}
},
"score": {
"$numberInt": "6"
}
}
]
aggregate pipeline:
db.collection.aggregate([
{
$match: {
learnerId: "6377422e8a43630dcfdfd410",
targetExam: "LON11PLUS",
completed: {
$exists: true
},
},
},
{
$sort: {
completed: -1,
},
},
{
$group: {
_id: "$subject",
history: {
$push: "$$ROOT",
},
},
},
{
$group: {
_id: null,
data: {
$push: {
"k": "$_id",
"v": "$history"
},
},
},
},
{
"$replaceRoot": {
"newRoot": {
"$arrayToObject": "$data"
}
}
}
])
current output:
[
{
"ENG": [
{
"_id": ObjectId("637742590b359f51cb957990"),
"completed": ISODate("2022-11-18T12:45:15.232Z"),
"created": "2022-11-18 08:29:13.804332",
"learnerId": "6377422e8a43630dcfdfd410",
"score": 6,
"sections": [
{
"events": [
779,
785,
791,
786,
784,
792,
783,
795,
781,
3041,
2053,
3497,
3840,
3023,
2285,
3022,
4644,
2477,
2472,
3338,
3270,
2018,
2288,
2260,
3603,
2463,
3600,
3312
],
"name": "English",
"timePerSection": 1500
}
],
"session": {
"id": "63777e1c960b1e7e97353d15"
},
"subject": "ENG",
"targetExam": "LON11PLUS"
}
],
"VR": [
{
"_id": ObjectId("637742400b359f51cb95798d"),
"completed": ISODate("2022-11-18T13:44:34.955Z"),
"created": "2022-11-18 08:28:48.105743",
"learnerId": "6377422e8a43630dcfdfd410",
"score": 2,
"sections": [
{
"events": [
2329,
2053,
3486,
3826,
3336,
2950,
2009,
4637,
3308,
2884,
2072,
3269,
2498,
2647,
2619,
3600,
2283,
3597,
2419,
1991
],
"name": "Verbal Reasoning",
"timePerSection": 960
}
],
"session": {
"id": "63778c23960b1e7e97353eea"
},
"subject": "VR",
"targetExam": "LON11PLUS"
}
]
}
]
I get an array with a single object, while the way I have structured the query, there should always be a single object.
How can I get this single object as root rather than nested in an array?
If I understand the question correctly, you want the aggregate method to return a single object and not an Array that contains one object.
If that understanding is correct, then it is worth mentioning that aggregate method always returns a cursor (ref) and as a rule of thumb in MongoDB, anything that returns a cursor (find, aggregate), will always return a list a.k.a. an array.
My apologies, if you did not want to hear this, but you cannot have an aggregation returning a single object instead of an array having a single object.
Your best bet here is to just hardcode the 0'th position to get the document. Something like:
let result = await db.users.aggregate([...]);
let my_user_aggregated_data = result[0];
I hope it clarifies.

Adding a nested value as a field - MongDB aggregation

So I have a parent document with users, as well as an array that has users too. I want to add the DisplayName from the nested users array to the aggregation output. Any ideas?
Output I'm looking to achieve:
[
{
"user": {
"_id": "11",
"Name": "Dave",
"DocID": "1",
"DocDisplyName": "ABC"
},
{
"user": {
"_id": "33",
"Name": "Henry",
"DocID": "1",
"DocDisplyName": "ABC",
"BranchDisplayName:"BranchA"
}
}
]
And so on.. So an array of all users and for users that belong to a branch, add the branch display Name to the output.
// Doc 1
{
"_id": "1",
"DisplayName": "ABC",
"Users": [
{ "_id": "11", "Name": "Dave" },
{ "_id": "22", "Name": "Steve" }
],
"Branches": [
{
"_id": "111",
"DisplayName": "BranchA",
"Users": [
{ "_id": "33", "Name": "Henry" },
{ "_id": "44", "Name": "Josh" },
],
},
{
"_id": "222",
"DisplayName": "BranchB",
"Users": [
{ "_id": "55", "Name": "Mark" },
{ "_id": "66", "Name": "Anton" },
],
}
]
}
``Doc 2
{
"_id": "2",
"DisplayName": "DEF",
"Users": [
{ "_id": "77", "Name": "Josh" },
{ "_id": "88", "Name": "Steve" }
],
"Branches": [
{
"_id": "333",
"DisplayName": "BranchA",
"Users": [
{ "_id": "99", "Name": "Henry" },
{ "_id": "10", "Name": "Josh" },
],
},
{
"_id": "444",
"DisplayName": "BranchB",
"Users": [
{ "_id": "112", "Name": "Susan" },
{ "_id": "112", "Name": "Mary" },
],
}
]
}
Collection.aggregate([
{
$addFields: {
branchUsers: {
$reduce: {
input: "$Branches.Users",
initialValue: [],
in: {
$concatArrays: ["$$this", "$$value"],
},
},
},
},
},
{
$addFields: {
user: {
$concatArrays: ["$branchUsers", "$Users"],
},
},
},
{
$addFields: {
"user.DocID": "$_id","user.DocDisaplyName": "$DisplayName"
},
},
{
$unwind: "$user",
},
{
$project: {
_id: 0,
user: 1,
},
}
])
Thanks in advance!
OK I found a solution.
{
$addFields: {
"branchUsers.BranchDisplayName": {
$let: {
vars: {
first: {
$arrayElemAt: [ "$Branches", 0 ]
}
},
in: "$$first.DisplayName"
}
}
}
},
This creates the field only for the users that belong to the branch

MongoDB count value of attribute and group by month and year

I have documents in my mongodb and i try to count every same description i have and classify to month they created.
I want to return an array that include array of objects that includes month number with sub array of description value with the count.
I want to be able to choose which description value to count and choose by what year he will show me the data
for example this is my documents:
[
{
"username": "ron",
"skills": [
{
"rank": "high",
list: [
{
"subject": "Football"
},
{
"subject": "Swim"
}
]
},
{
"rank": "low",
list: [
{
"subject": "Baseball"
},
]
}
],
"duration": 0,
"date": ISODate("2021-07-24T12:05:27.127Z"),
"createdAt": ISODate("2021-07-24T12:05:49.985Z"),
"updatedAt": ISODate("2021-07-24T12:05:49.985Z"),
"__v": 0
},
{
"username": "john",
"skills": [
{
"rank": "low",
list: [
{
"subject": "Football"
},
]
}
],
"duration": 0,
"date": ISODate("2021-07-25T12:05:53.000Z"),
"createdAt": ISODate("2021-07-24T12:05:59.249Z"),
"updatedAt": ISODate("2021-07-24T12:05:59.249Z"),
"__v": 0
},
{
"username": "david",
"skills": [
{
"rank": "high",
list: [
{
"subject": "Football"
},
{
"subject": "Baseball"
}
]
},
{
"rank": "low",
list: [
{
"subject": "Swim"
},
]
}
],
"duration": 0,
"date": ISODate("2021-08-26T12:06:13.000Z"),
"createdAt": ISODate("2021-07-24T12:06:21.328Z"),
"updatedAt": ISODate("2021-07-24T12:06:21.328Z"),
"__v": 0
},
{
"username": "david",
"skills": [
{
"request": "high",
list: [
{
"subject": "Swim"
},
]
},
{
"request": "low",
list: [
{
"subject": "Football"
},
{
"subject": "Baseball"
},
]
}
],
"duration": 0,
"date": ISODate("2021-01-21T13:07:50.000Z"),
"createdAt": ISODate("2021-07-24T12:08:05.552Z"),
"updatedAt": ISODate("2021-07-24T12:14:51.285Z"),
"__v": 0
},
{
"username": "david",
"skills": [
{
"rank": "high",
list: [
{
"subject": "Football"
},
]
},
],
"duration": 0,
"date": ISODate("2022-01-21T13:07:50.000Z"),
"createdAt": ISODate("2022-07-24T12:08:05.552Z"),
"updatedAt": ISODate("2022-07-24T12:14:51.285Z"),
"__v": 0
}
]
The result I expect to get if i want to get match only the description that equal to "Football" or "Baseball":
{
[ {"month_number":7,"result":[{"description":'Football',"count":2},{"description":"Baseball","count":1}]},
{"month_number":8,"result":[{"description":'Football',"count":1}]}]
}
I'm new to mongodb ... so far I have been able to count how many there are of each value and display only the values I want but I do not know how to classify it into months depending on the year I choose.
I tried this:
db.exercises.aggregate([{$match:{ $and:[{description:{$in: ["Baseball","Football"]}}]}} ,{$group:{_id:"$description",count:{$sum:1}}}])
and this
db.exercises.aggregate(
[
{
$project:
{
_id: 0,
year: { $year: "$date" },
month: { $month: "$date" },
description:"$description"
}
},
{
$match:{$and:[{description:{$in: ["Baseball","Football","Swim"]},year:2021}]}
},
{$group:{_id:"$description" , count:{$sum:1}}}
]
)
You need add 1 more grouping i.e. by month.
Working playground
db.collection.aggregate([
{
$project: {
_id: 0,
year: {
$year: "$date"
},
month: {
$month: "$date"
},
description: "$description"
}
},
{
$match: {
$and: [
{
description: {
$in: [
"Baseball",
"Football",
"Swim"
]
},
year: 2021
}
]
}
},
{
$group: {
_id: {
description: "$description",
month: "$month"
},
count: {
$sum: 1
}
}
},
{
$group: {
_id: {
month_number: "$_id.month"
},
results: {
$push: {
description: "$_id.description",
count: "$count"
}
}
}
},
{
$project: {
_id: 0,
month_number: "$_id.month_number",
results: 1
}
}
])

mongodb aggregate lookup with a query

I have collections with following values:
reports
{
"_id": { "$oid": "5f05e1d13e0f6637739e215b" },
"testReport": [
{
"name": "Calcium",
"value": "87",
"slug": "ca",
"details": {
"description": "description....",
"recommendation": "recommendation....",
"isNormal": false
}
},
{
"name": "Magnesium",
"value": "-98",
"slug": "mg",
"details": {
"description": "description....",
"recommendation": "recommendation....",
"isNormal": false
}
}
],
"patientName": "Patient Name",
"clinicName": "Clinic",
"gender": "Male",
"bloodGroup": "A",
"createdAt": { "$date": "2020-07-08T15:10:09.612Z" },
"updatedAt": { "$date": "2020-07-08T15:10:09.612Z" }
},
setups
{
"_id": { "$oid": "5efcba7503f4693d164e651d" },
"code": "Ca",
"codeLower": "ca",
"name": "Calcium",
"valueFrom": -75,
"valueTo": -51,
"treatmentDescription": "description...",
"isNormal": false,
"gender": "",
"recommendation": "recommendation...",
"createdAt": { "$date": "2020-07-01T16:31:50.205Z" },
"updatedAt": { "$date": "2020-07-01T16:31:50.205Z" }
},
{
"_id": { "$oid": "5efcba7503f4693d164e651e" }, // <=== should find this for Calcium
"code": "Ca",
"codeLower": "ca",
"name": "Calcium",
"valueFrom": 76,
"valueTo": 100,
"treatmentDescription": "description...",
"isNormal": false,
"gender": "",
"recommendation": "recommendation...",
"createdAt": { "$date": "2020-07-01T16:31:50.205Z" },
"updatedAt": { "$date": "2020-07-01T16:31:50.205Z" }
},
{
"_id": { "$oid": "5efcba7603f4693d164e65bb" }, // <=== should find this for Magnesium
"code": "Mg",
"codeLower": "mg",
"name": "Magnesium",
"valueFrom": -100,
"valueTo": -76,
"treatmentDescription": "description...",
"isNormal": false,
"gender": "",
"recommendation": "recommendation...",
"createdAt": { "$date": "2020-07-01T16:31:50.205Z" },
"updatedAt": { "$date": "2020-07-01T16:31:50.205Z" }
},
{
"_id": { "$oid": "5efcba7503f4693d164e6550" },
"code": "Mg",
"codeLower": "mg",
"name": "Magnesium",
"valueFrom": 76,
"valueTo": 100,
"treatmentDescription": "description...",
"isNormal": false,
"gender": "",
"recommendation": "recommendation...",
"createdAt": { "$date": "2020-07-01T16:31:50.205Z" },
"updatedAt": { "$date": "2020-07-01T16:31:50.205Z" }
}
I want to search the value from reports collection and check whether the value is in range from the setups collection and return the _id and add the returned _ids in setupIds field on reports collection.
I tried with the following aggregation framework:
db.reports.aggegrate([
{
'$match': {
'_id': new ObjectId('5f05e1d13e0f6637739e215b')
}
}, {
'$lookup': {
'from': 'setups',
'let': {
'testValue': '$testReport.value',
'testName': '$testReport.name'
},
'pipeline': [
{
'$match': {
'$expr': {
{
'$and': [
{
'$eq': [
'$name', '$$testName'
]
}, {
'$gte': [
'$valueTo', '$$testValue'
]
}, {
'$lte': [
'$valueFrom', '$$testValue'
]
}
]
}
}
}
}
],
'as': 'setupIds'
}
}
])
This query didn't find the expected results.
This is the updated reports collection I want:
{
"_id": { "$oid": "5f05e1d13e0f6637739e215b" },
"setupIds": [{ "$oid": "5efcba7503f4693d164e651e" }, { "$oid": "5efcba7603f4693d164e65bb" }], // <=== Here, array of the ObjectId (ref: "Setups")
"patientName": "Patient Name",
"clinicName": "Clinic",
"gender": "Male",
"bloodGroup": "A",
"createdAt": { "$date": "2020-07-08T15:10:09.612Z" },
"updatedAt": { "$date": "2020-07-08T15:10:09.612Z" }
},
You can try like following
[{
$match: {
_id: ObjectId('5f05e1d13e0f6637739e215b')
}
}, {
$unwind: {
path: "$testReport"
}
}, {
$lookup: {
from: 'setup',
'let': {
testValue: {
$toInt: '$testReport.value'
},
testName: '$testReport.name'
},
pipeline: [{
$match: {
$expr: {
$and: [{
"$eq": [
"$name",
"$$testName"
]
},
{
"$gte": [
"$valueTo",
"$$testValue"
]
},
{
"$lte": [
"$valueFrom",
"$$testValue"
]
}
]
}
}
}],
as: 'setupIds'
}
}, {
$group: {
_id: "$_id",
patientName: {
$first: "$patientName"
},
clinicName: {
$first: "$clinicName"
},
gender: {
$first: "$gender"
},
bloodGroup: {
$first: "$bloodGroup"
},
createdAt: {
$first: "$createdAt"
},
updatedAt: {
$first: "$updatedAt"
},
setupIds: {
$addToSet: "$setupIds._id"
}
}
}, {
$addFields: {
setupIds: {
$reduce: {
input: "$setupIds",
initialValue: [],
in: {
$setUnion: ["$$this", "$$value"]
}
}
}
}
}]
Working Mongo playground