Get $sum of grouped value Mongoose - node.js

I have query like this:
{"$match": {$or: [{"to": system.mongoose.Types.ObjectId(userId)}, {"from": system.mongoose.Types.ObjectId(userId)}]}},
{"$sort": {"createDate": -1}},
{
"$group": {
"_id": "$conversationId",
"from": {"$first": "$from"},
"to": {"$first": "$to"},
"content": {"$first": "$content"},
"createDate": {"$first": "$createDate"},
"unreaded": {"$sum": {"$cond":
{if :
{
$and: [
{
"$eq": [
"$unreaded", 1
]
},
{
"$eq": ["$to", system.mongoose.Types.ObjectId(userId)]
}
]
}, then: 1, else: 0}}}
unreaded will return unreaded messages for each conversation, how can I get sum of all unreaded messages?

Related

Mongodb aggregation to pass both a matched array and an unmatched array

I've got a MongoDB / Nodes aggregation that looks a little like this (there are other values in there, but this is the basic idea).
[
{
'$unwind': {
'path': '$Vehicles'
}
},
{
'$match': {
'Vehicles.Manufacturer': 'FORD'
}
},
{
'$facet': {
'makes': [
{
'$group': {
'_id': '$Vehicles.Manufacturer',
'count': {
'$sum': 1
}
}
}
]
}
},
{
'$project': {
'makes': {
'$sortArray': {
'input': '$makes',
'sortBy': 1
}
}
}
}
]
This works fine. But I would also like to pass an unmatched list through. IE an an array of vehicles whose Manufacturer = FORD and an other list of all Manufacturer.
Can't get it to work. Any ideas please?
Thanks in advance.
Edit:-
The current output looks like this:
[{
"makes": [
{
"_id": "FORD",
"count": 285
}
]
}]
and ideally it would look something like this:
[{
"makes": [
{
"_id": "FORD",
"count": 285
}
],
"unfiltered_makes": [
{
"_id": "ABARTH",
"count": 1
},
{
"_id": "AUDI",
"count": 7
},
{
"_id": "BMW",
"count": 2
},
{
"_id": "CITROEN",
"count": 4
},
{
"_id": "DS",
"count": 1
},
{
"_id": "FIAT",
"count": 1
}.... etc
]
}]
The data looks a bit like this:
"Vehicles": [
{
"Id": 1404908,
"Manufacturer": "MG",
"Model": "3",
"Price": 11995 .... etc
},{
"Id": 1404909,
"Manufacturer": "FORD",
"ManufacturerId": 34,
"Model": "Focus",
"Price": 12000 .... etc
} ... etc
]
In this case you can do something like:
db.collection.aggregate([
{$unwind: "$Vehicles"},
{$group: {
_id: "$Vehicles.Manufacturer",
count: {$sum: 1}}
},
{$facet: {
makes: [{$match: {_id: "FORD"}}],
unfiltered_makes: [{$group: {_id: 0, data: {$push: "$$ROOT"}}}]
}
},
{$project: {makes: 1, unfiltered_makes: "$unfiltered_makes.data"}}
])
See how it works on the playground example
Another option is:
db.collection.aggregate([
{$unwind: "$Vehicles"},
{$group: {
_id: "$Vehicles.Manufacturer",
count: {$sum: 1}}
},
{$group: {
_id: 0,
unfiltered_makes: {$push: "$$ROOT"},
makes: {$push: {$cond: [{$eq: ["$_id", "FORD"]}, "$$ROOT", "$$REMOVE"]}}
}
}
])
See how it works on the playground example
Here's another way to do it using "$function" to generate a histogram of "Manufacturer" and format the returned array. The javascript function only traverses the "Vehicles" array once, so this may be fairly efficient, although I did not do algorithm timing comparisons on a large collection.
N.B.: I'm a javascript noob and there may be a better way to do this.
db.collection.aggregate([
{
"$set": {
"unfiltered_makes": {
"$function": {
// generate histogram of manufacturers and format output
"body": "function(makes) {const m = new Object();makes.forEach((elem) => {m[elem.Manufacturer] = m[elem.Manufacturer] + 1 || 1});return Object.entries(m).map(([make, count]) => {return {'_id':make, 'count':count}})}",
"args": ["$Vehicles"],
"lang": "js"
}
}
}
},
{
"$project": {
"_id": 0,
"unfiltered_makes": 1,
"makes": {
"$filter": {
"input": "$unfiltered_makes",
"as": "make",
"cond": {
"$eq": [
"$$make._id",
// your search "Manufacturer" goes here
"FORD"
]
}
}
}
}
}
])
Try it on mongoplayground.net.

Mongodb Join on _id field from to ObjectId

I have two collections
Reports
{"brand": "Nike", "created": "2021-05-03T20:12:32.911000", "id": ObjectId("60929848088f18212809221c")}
Status
{"id": "60929848088f18212809221c", "report": ObjectId("60929848088f18212809221c"), "created": "2021-05-05T20:12:32.911000"}
I want to join these collections based on the report(in Status collection) - _id ( in Reports collection).
I tried the below query(The code here is complex because it does a few more things that are necessary to me):
query = [
{'$group': {'_id': '$_id', 'data': {'$last': '$$ROOT'}}},
{'$match': match},
{'$sort': {'data.created': pymongo.DESCENDING}},
{'$facet': {
'stage1': [{'$group': {'_id': None, 'count': {'$sum': 1}}}],
'stage2': [{'$skip': offset}, {'$limit': limit}]
}},
{'$unwind': '$stage1'},
{'$project': {
'count': '$stage1.count',
'data': '$stage2'
}},
{
"$lookup": {
"from": "status",
"localField": "data._id",
"foreignField": "report",
"as": "report"
}
}
]
I expect such a result:
[
{
"count": 3,
"data": [
{
"_id": ObjectId("5a934e000102030405000001"),
"data": {
"_id": ObjectId("5a934e000102030405000001"),
"brand": "Nike",
"created": "2021-05-03T20:12:32.911000",
},
"report": {
"_id": ObjectId("5a934e000102030405000003"),
"created": "2021-05-05T20:12:32.911000",
"id": "60929848088f18212809221c",
"report": ObjectId("5a934e000102030405000001")
},
},
}
]
}
]
I mean I mean it will be in every result
I tried to get help with that too but it did not work Here.
I'm using Mongo DB 4+

How do I merge these two separate aggregate queries into a single query?

I have posted two questions concerning aggregation queries in mongo.
Question 1 Gets all the chores for a specific person
Question 2 Gets all the chores for all people on a specific ordinal (day)
Now, however, I would like to combine both of those queries into a single query. Returning only the chores for a specific person on a specific day.
Here is what I have thus far :
ChoreChart.aggregate([
{ "$match": { "affiliation": affiliation, "year": weekYear, "weekNumber": weekNumber } },
{ "$addFields": {
"chart": {
"$map": {
"input": "$chart",
"as": "cc",
"in": {
"_id": "$$cc._id",
"ordinal": "$$cc.ordinal",
"ordinalString": "$$cc.ordinalString",
"chorePerson": {
"$filter": {
"input": "$$cc.chorePerson",
"as": "dd",
"cond":
{
"$and": [
{"$eq": ["$$dd.personID", personID]},
{"$eq": ["$$cc.ordinal", ordinal ]}
] }
}
}
}
}
}
}}
])
This is what I get for an ordinal of 4, however, my output includes ordinals that I am not interested in.
[
{
"_id": "5e2d482cd8593e00162d0568",
"affiliation": "800_800",
"year": 2020,
"month": "January",
"weekNumber": 5,
"weekStart": "01/26/2020",
"weekEnd": "02/01/2020",
"chart": [
{
"_id": "5e330310c66e9e4084cda785",
"ordinal": 0,
"ordinalString": "Sunday",
"chorePerson": []
},
{
"_id": "5e330310c66e9e4084cda783",
"ordinal": 1,
"ordinalString": "Monday",
"chorePerson": []
},
{
"_id": "5e330310c66e9e4084cda780",
"ordinal": 2,
"ordinalString": "Tuesday",
"chorePerson": []
},
{
"_id": "5e330310c66e9e4084cda77e",
"ordinal": 3,
"ordinalString": "Wednesday",
"chorePerson": []
},
{
"_id": "5e330310c66e9e4084cda77c",
"ordinal": 4,
"ordinalString": "Thursday",
"chorePerson": [
{
"_id": "5e330310c66e9e4084cda77d",
"person": "Jo",
"personID": "5e2890268c63351b7c07dc26",
"phone": "8008008001",
"chore": "DC 1",
"choreID": "5e2929cf285338cb8cf375fc"
},
{
"_id": "5e330310c66e9e4084cda77e",
"person": "Jo",
"personID": "5e2890268c63351b7c07dc26",
"phone": "8008008001",
"chore": "DC 2",
"choreID": "5e2929cf285338cb8cf375fd"
}
]
},
{
"_id": "5e330310c66e9e4084cda77a",
"ordinal": 5,
"ordinalString": "Friday",
"chorePerson": []
},
{
"_id": "5e330310c66e9e4084cda778",
"ordinal": 6,
"ordinalString": "Saturday",
"chorePerson": []
}
],
"date": "2020-01-30T16:23:44.713Z",
"__v": 0
}
]
This is what I really want :
[
{
"_id": "5e2d482cd8593e00162d0568",
"affiliation": "800_800",
"year": 2020,
"month": "January",
"weekNumber": 5,
"weekStart": "01/26/2020",
"weekEnd": "02/01/2020",
"chart": [
{
"_id": "5e330310c66e9e4084cda77c",
"ordinal": 4,
"ordinalString": "Thursday",
"chorePerson": [
{
"_id": "5e330310c66e9e4084cda77d",
"person": "Jo",
"personID": "5e2890268c63351b7c07dc26",
"phone": "8008008001",
"chore": "DC 1",
"choreID": "5e2929cf285338cb8cf375fc"
},
{
"_id": "5e330310c66e9e4084cda77e",
"person": "Jo",
"personID": "5e2890268c63351b7c07dc26",
"phone": "8008008001",
"chore": "DC 2",
"choreID": "5e2929cf285338cb8cf375fd"
}
]
}
],
"date": "2020-01-30T16:23:44.713Z",
"__v": 0
}
]
You can try this :
ChoreChart.aggregate([
{ "$match": { "affiliation": affiliation, "year": weekYear, "weekNumber": weekNumber } }, {
"$addFields": {
"chart": {
"$map": {
/** $filter in input to retain only objects in array that match ordinal filter */
"input": { $filter: { input: '$chart', as: 'c', cond: { "$eq": ["$$c.ordinal", ordinal] } } },
"as": "cp",
"in": {
/** $mergeObjects to merge chorePerson filtered array field on each 'chart.chorePerson' */
$mergeObjects: ['$$cp', { chorePerson: { $filter: { input: '$$cp.chorePerson', as: 'each', cond: { "$eq": ["$$each.personID", personID] } } } }]
}
}
}
}
}
])
Test : MongoDB-Playground

MongoDB - $lookup and $aggregate in 2 collections

I have two collections like this :
1st collection name is Promotions:
{
"_id": "A019283847466",
"code": "AAA",
"aliases" : [ "AAA1","AAA2","AAA3"]
}
2nd Collection name is PromotionUsages:
{
"customerId": "_1234567890"
"code": "AAA1"
}
{
"customerId": "_0987654321"
"code": "AAA1"
}
Expected output is :
{
"code": "AAA"
"aliasCode": "AAA1"
"countUsages": 2
}
I used mongo $group and $aggregate but I am not getting required output
any help please
Thank You!!!
You can try below aggregation
db.collection.aggregate([
{ "$unwind": "$aliases" },
{ "$addFields": { "aliasesCode": "$aliases" }},
{ "$lookup": {
"from": PromotionUsages.collection.name,
"let": { "aliases": "$aliases" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$code", "$$aliases" ] } } },
{ "$count": "countUsages" }
],
"as": "aliases"
}},
{ "$unwind": "$aliases" },
{ "$project": { "code": 1, "aliasCode": 1, "countUsages": "$aliases.countUsages" }}
])
Output
{
"code": "AAA"
"aliasCode": "AAA1"
"countUsages": 2
}
Made some changes in solution suggested by Anthony Winzlet
db.promotions.aggregate([
{ "$unwind": "$aliases" },
{ "$addFields": { "aliasesCode": "$aliases" }},
{ "$lookup": {
"from": "promotionusages",
"let": { "aliases": "$aliases" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$code", "$$aliases" ] } } },
{ "$count": "countUsages" }
],
"as": "aliases"
}},
{ "$unwind": "$aliases" },
{ "$project": { "_id" : 0, "code": 1, "aliasesCode": 1, "countUsages": "$aliases.countUsages" }}
])

Pagination on mongoDB aggregations [Node.js]

I have query something like this:
Message.aggregate([{
"$match": {
$or: [{
"to": userId
}, {
"from": userId
}]
}
},
{
"$sort": {
"createDate": -1
}
},
{
"$group": {
"_id": "$conversationId",
"from": {
"$first": "$from"
},
"to": {
"$first": "$to"
},
"content": {
"$first": "$content"
},
"createDate": {
"$first": "$createDate"
},
"unreaded": {
"$sum": {
"$cond": {
if: {
$and: [{
"$eq": [
"$unreaded", 1
]
},
{
"$eq": ["$to", userId]
}
]
},
then: 1,
else: 0
}
}
}
}
},
{
"$sort": {
"createDate": -1
}
},
{
"$lookup": {
"from": "users",
"localField": "from",
"foreignField": "_id",
"as": "from"
}
},
{
"$lookup": {
"from": "users",
"localField": "to",
"foreignField": "_id",
"as": "to"
}
},
{
"$unwind": {
"path": "$from"
}
},
{
"$unwind": {
"path": "$to"
}
},
{
"$project": {
"from.firstName": "$from.firstName",
"from.lastName": "$from.lastName",
"from.picture": "$from.picture",
"to.firstName": "$to.firstName",
"to.lastName": "$to.lastName",
"to.picture": "$to.picture",
"content": 1,
"createDate": 1,
"unreaded": 1,
"reciver": {
"$cond": {
if: {
"$eq": ["$from._id", mongoose.Types.ObjectId(userId)]
},
then: {
"firstName": "$to.firstName",
"lastName": "$to.lastName",
"_id": "$to._id"
},
else: {
"firstName": "$from.firstName",
"lastName": "$from.lastName",
"_id": "$from._id"
}
}
}
}
},
{
"$limit": 50
}
I am able now to limit records to 50 per request but problem is when I try to make pagination... I get this error when I try to add skip next to limit:
Error: Arguments must be aggregate pipeline operators
any idea how can I do that?
Check your args and correct mistake.
Message.aggregate([{"$match": {$or: [{"to": userId}, {"from": userId}]}}, ..., {$skip: 1}, {$limit: 1}])

Resources