$sum by matching fields in array mongodb - javascript

I don't have much experience with mongodb, so the following query is making it difficult for me.
This is the document
[
{
"_id": "31-07-2019",
"date": "31-07-2019",
"grocerie1": [
{
"name": "Flour",
"price": 3.68,
"count": 1
},
{
"name": "Rice",
"price": 3,
"count": 1
},
{
"name": "Rice",
"price": 3,
"count": 1
},
{
"name": "Flour",
"price": 3.68,
"count": 1
}
],
"grocerie2": [
{
"name": "Flour",
"price": 3.68,
"count": 1
}
],
"grocerie1Total": 13.36,
"grocerie2Total": 3.68,
"total": 17.04
},
{
"_id": "09-08-2019",
"date": "09-08-2019",
"grocerie1": [
{
"name": "Rice",
"price": 3,
"count": 1
},
{
"name": "Rice",
"price": 3,
"count": 1
},
{
"name": "Milk",
"price": 5,
"count": 1
}
],
"grocerie2": [
{
"name": "Milk",
"price": 5,
"count": 1
},
{
"name": "Cheese",
"price": 2,
"count": 1
}
],
"grocerie1Total": 11,
"grocerie2Total": 7,
"total": 18
},
{
"_id": "22-08-2019",
"date": "22-08-2019",
"grocerie1": [
{
"name": "Rice",
"price": 3,
"count": 1
},
{
"name": "Cheese",
"price": 2,
"count": 1
},
{
"name": "Cheese",
"price": 2,
"count": 1
},
{
"name": "Rice",
"price": 3,
"count": 1
}
],
"grocerie2": [
{
"name": "Rice",
"price": 3,
"count": 1
},
{
"name": "Rice",
"price": 3,
"count": 1
},
{
"name": "Rice",
"price": 3,
"count": 1
}
],
"grocerie1Total": 10,
"grocerie2Total": 9,
"total": 19
}
]
The document is sorted by date, and contains two grocery stores, each with different products sold. Each product has a name, price and a "count" that I placed to, in the future, obtain the number of times the product was sold through the sum of this field.
Now I want to achieve something like this:
[
{
"_id": "31-07-2019",
"date": "31-07-2019",
"grocerie1": [
{
"name": "Flour",
"total": 7.56,
"count": 2
},
{
"name": "Rice",
"total": 6,
"count": 2
}
],
"grocerie2": [
{
"name": "Flour",
"total": 3.68,
"count": 1
}
],
"grocerie1Total": 13.36,
"grocerie2Total": 3.68,
"total": 17.04
},
{
"_id": "09-08-2019",
"date": "09-08-2019",
"grocerie1": [
{
"name": "Rice",
"total": 6,
"count": 2
},
{
"name": "Milk",
"total": 5,
"count": 1
}
],
"grocerie2": [
{
"name": "Milk",
"total": 5,
"count": 1
},
{
"name": "Cheese",
"total": 2,
"count": 1
}
],
"grocerie1Total": 11,
"grocerie2Total": 7,
"total": 18
},
{
"_id": "22-08-2019",
"date": "22-08-2019",
"grocerie1": [
{
"name": "Rice",
"total": 6,
"count": 2
},
{
"name": "Cheese",
"total": 4,
"count": 2
}
],
"grocerie2": [
{
"name": "Rice",
"total": 9,
"count": 3
}
],
"grocerie1Total": 10,
"grocerie2Total": 9,
"total": 19
}
]
I tried something like this, for example, for "grocerie1",
however, I got disastrous results:
{
$unwind:
{
path: "$grocerie1",
preserveNullAndEmptyArrays: true
}
},
{
"$group": {
"_id": "$grocerie1.name",
"eatHereInfo": {
"$push": {
"name": "$grocerie1.name",
"total": { "$sum": "$grocerie1.price" },
"count": { "$sum": "$grocerie1.count" } }
},
"grocerie2": { "$first": "$grocerie2" },
"date": { "$first": "$date" },
"grocerie1Total": { "$first": "$grocerie1Total" },
"grocerie2Total": { "$first": "$grocerie2Total" },
}
},
Is there any way to achieve it with the aggregation framework? or with javascript? Any help and suggestion are appreciated :)

Note: I assume your objects stored in grocerie collection.
Mongo way (Difficult and rigid)
db.getCollection('grocerie').aggregate([
// ---------------- We start with grocerie1 ------------------
//1. Split grocerie1 array into atomic object
{"$unwind":{ "path": "$grocerie1", "preserveNullAndEmptyArrays": true }},
//2. Group by date + grocerie1 name. If group only by grocerie1.name we may group from other days
// For same grocerie names, we accumulate their name, price, total "grocerie1": { "$push": "$grocerie1" },
{"$group": {
"_id": { "_id": "$_id", "name": "$grocerie1.name" },
"grocerie1": { "$push": "$grocerie1" },
"grocerie2": { "$first": "$grocerie2" },
"date": { "$first": "$date" },
"grocerie1Total": { "$first": "$grocerie1Total" },
"grocerie2Total": { "$first": "$grocerie2Total" }
}
},
//3. Now we have unique date + grocerie1 names + all same items inside grocerie1 array. Split again into atomic value
{"$unwind":{ "path": "$grocerie1", "preserveNullAndEmptyArrays": true }},
//4. We group again date + grocerie1 names, but now we sum price and count
{"$group": {
"_id": { "_id": "$date", "name": "$_id.name" },
"total": { "$sum": "$grocerie1.price" },
"count": { "$sum": "$grocerie1.count" },
"grocerie2": { "$first": "$grocerie2" },
"date": { "$first": "$date" },
"grocerie1Total": { "$first": "$grocerie1Total" },
"grocerie2Total": { "$first": "$grocerie2Total" }
}
},
//5. We group for date and push inside grocerie1 calculated price, total
{"$group":{
"_id": "$_id._id",
"grocerie1": { "$push": {
"name" : "$_id.name",
"total" : "$total",
"count" : "$count"
} },
"grocerie2": { "$first": "$grocerie2" },
"date": { "$first": "$date" },
"grocerie1Total": { "$first": "$grocerie1Total" },
"grocerie2Total": { "$first": "$grocerie2Total" }
}
},
// ---------------- We finished with grocerie1 ---------------
// ---------------- We start with grocerie2 ------------------
//1. Split grocerie2 array into atomic object
{"$unwind":{ "path": "$grocerie2", "preserveNullAndEmptyArrays": true }},
//2. Group by date + grocerie2 name. If group only by grocerie2.name we may group from other days
// For same grocerie names, we accumulate their name, price, total "grocerie2": { "$push": "$grocerie2" },
{"$group": {
"_id": { "_id": "$_id", "name": "$grocerie2.name" },
"grocerie1": { "$first": "$grocerie1" },
"grocerie2": { "$push": "$grocerie2" },
"date": { "$first": "$date" },
"grocerie1Total": { "$first": "$grocerie1Total" },
"grocerie2Total": { "$first": "$grocerie2Total" }
}
},
//3. Now we have unique date + grocerie2 names + all same items inside grocerie2 array. Split again into atomic value
{"$unwind":{ "path": "$grocerie2", "preserveNullAndEmptyArrays": true }},
//4. We group again date + grocerie2 names, but now we sum price and count
{"$group": {
"_id": { "_id": "$date", "name": "$_id.name" },
"total": { "$sum": "$grocerie2.price" },
"count": { "$sum": "$grocerie2.count" },
"grocerie1": { "$first": "$grocerie1" },
"date": { "$first": "$date" },
"grocerie1Total": { "$first": "$grocerie1Total" },
"grocerie2Total": { "$first": "$grocerie2Total" }
}
},
//5. We group for date and push inside grocerie2 calculated price, total
{"$group":{
"_id": "$_id._id",
"grocerie1": { "$first": "$grocerie1" },
"grocerie2": { "$push": {
"name" : "$_id.name",
"total" : "$total",
"count" : "$count"
} },
"date": { "$first": "$date" },
"grocerie1Total": { "$first": "$grocerie1Total" },
"grocerie2Total": { "$first": "$grocerie2Total" },
// Sum total values
"total" : {"$sum":{"$add":["$grocerie1Total", "$grocerie2Total"]}}
}
}
// ---------------- We finished with grocerie2 ---------------
])
Javascript way (Easy and flexible)
/**
* Group groceries with same name and sum fields
*/
function groupGroceries(){
//aux function to group groceries with same name
function _(grocerie){
for(var i=grocerie.length-1; i > -1; i--){
for(var j=0; j<i; j++){
// If grocerie.name already exists, we sum values and remove from array
if(grocerie[j].name == grocerie[i].name){
grocerie[j].price += grocerie[i].price;
grocerie[j].count += grocerie[i].count;
grocerie.splice(i, 1);
break;
}
}
}
//Change price into total
for(var i=0; i<grocerie.length; i++){
//Robo 3T bug: (""+grocerie[i].price).indexOf(".") > -1 ? grocerie[i].price : NumberInt(grocerie[i].price);
grocerie[i].total = grocerie[i].price;
delete grocerie[i].price;
}
}
var result = [];
//Iterate over grocerie collection
db.getCollection('grocerie').find({}).forEach(function(doc){
//Uncomment line below if _id disappears
//doc["_id"];
_(doc.grocerie1);
_(doc.grocerie2);
doc.total = doc.grocerie1Total + doc.grocerie2Total;
result.push(doc);
})
for(var i=0; i<result.length; i++){
print("/* " + (i+1) + " */")
print(result[i])
print("")
}
}
groupGroceries();
==Result==
/* 1 */
{
"_id" : "31-07-2019",
"grocerie1" : [
{
"name" : "Flour",
"total" : 7.36,
"count" : 2
},
{
"name" : "Rice",
"total" : 6,
"count" : 2
}
],
"grocerie2" : [
{
"name" : "Flour",
"total" : 3.68,
"count" : 1
}
],
"date" : "31-07-2019",
"grocerie1Total" : 13.36,
"grocerie2Total" : 3.68,
"total" : 17.04
}
/* 2 */
{
"_id" : "09-08-2019",
"grocerie1" : [
{
"name" : "Rice",
"total" : 6,
"count" : 2
},
{
"name" : "Milk",
"total" : 5,
"count" : 1
}
],
"grocerie2" : [
{
"name" : "Milk",
"total" : 5,
"count" : 1
},
{
"name" : "Cheese",
"total" : 2,
"count" : 1
}
],
"date" : "09-08-2019",
"grocerie1Total" : 11,
"grocerie2Total" : 7,
"total" : 36
}
/* 3 */
{
"_id" : "22-08-2019",
"grocerie1" : [
{
"name" : "Cheese",
"total" : 4,
"count" : 2
},
{
"name" : "Rice",
"total" : 6,
"count" : 2
}
],
"grocerie2" : [
{
"name" : "Rice",
"total" : 9,
"count" : 3
}
],
"date" : "22-08-2019",
"grocerie1Total" : 10,
"grocerie2Total" : 9,
"total" : 19
}

Related

MongoDB : - Merge Two Array with same key

I have 3 collections, users, attendances, shifts.
I am trying to get attendances and shifts with the help of users
I have a date in shifts and the Date key in attendances and I am trying to merge both data into 1 as per date, and if not present then that key should be none
Below is the query I tried it is working to some extent but not got the final result
User.aggregate([
{ $sort: { workerId: 1 } },
{
$lookup: {
from: "shifts",
localField: "_id",
foreignField: "employeeId",
pipeline: [
{
$match: {
date: {
$gte: new Date(fromDate),
$lte: new Date(toDate),
},
},
},
{
$project: {
date: 1,
shiftCode: 1,
},
},
{
$sort: {
date: 1,
},
},
],
as: "shifts",
},
},
{
$project: {
_id: 1,
workerId: 1,
shiftListData: "$shifts",
},
},
{
$lookup: {
from: "attendances",
localField: "_id",
foreignField: "employeeId",
pipeline: [
{
$match: {
Date: { $gte: new Date(fromDate), $lte: new Date(toDate) },
},
},
{
$project: {
inTime: 1,
name: 1,
Date: 1,
},
},
],
as: "attendances",
},
},
]);
OutPut
[
{
"workerId": "1005",
"shiftListData": [
{
"_id": "63875e8182ebbe13ee9531d4",
"shiftCode": "HOBGS_1100",
"date": "2022-12-31T00:00:00.000Z"
},
{
"_id": "63b277a2f6a8eccb2d95d407",
"shiftCode": "WO",
"date": "2023-01-01T00:00:00.000Z"
},
{
"_id": "63b27787f6a8eccb2d95cf30",
"shiftCode": "HOBGS_1100",
"date": "2023-01-02T00:00:00.000Z"
},
{
"_id": "63b277a2f6a8eccb2d95d409",
"shiftCode": "HOBGS_1100",
"date": "2023-01-03T00:00:00.000Z"
}
],
"attendances": [
{
"_id": "61307cd385b5055a15cec159",
"Date": "2022-12-31T00:00:00.000Z",
"inTime": "2022-12-31T11:16:10.000Z",
"name": "name2"
},
{
"_id": "63b236ef3980cffaf7715d62",
"inTime": "2023-01-02T07:14:08.000Z",
"Date": "2023-01-02T00:00:00.000Z",
"name": "name2"
}
]
},
{
"workerId": "1006",
"shiftListData": [
{
"_id": "63875e8182ebbe13ee9531d2",
"shiftCode": "HOBGS_1100",
"date": "2022-12-31T00:00:00.000Z"
},
{
"_id": "63b277a2f6a8eccb2d95d403",
"shiftCode": "WO",
"date": "2023-01-01T00:00:00.000Z"
},
{
"_id": "63b27787f6a8eccb2d95cf39",
"shiftCode": "HOBGS_1100",
"date": "2023-01-02T00:00:00.000Z"
},
{
"_id": "63b277a2f6a8eccb2d95d400",
"shiftCode": "HOBGS_1100",
"date": "2023-01-03T00:00:00.000Z"
}
],
"attendances": [
{
"_id": "61307cd385b5055a15cec158",
"Date": "2022-12-31T00:00:00.000Z",
"inTime": "2022-12-31T11:16:10.000Z",
"name": "name"
},
{
"_id": "63b236ef3980cffaf7715d69",
"inTime": "2023-01-02T07:14:08.000Z",
"Date": "2023-01-02T00:00:00.000Z",
"name": "name"
}
]
}
]
I want to merge shiftListData and attendances as per date into one array
Example : -
[
{
"workerId": "1005",
"newData": [
{
"_id": "63875e8182ebbe13ee9531d4",
"shiftCode": "HOBGS_1100",
"date": "2022-12-31T00:00:00.000Z",
"attendanceId": "61307cd385b5055a15cec159",
"attendanceDate": "2022-12-31T00:00:00.000Z",
"inTime": "2022-12-31T11:16:10.000Z",
"name": "name2"
},
{
"_id": "63b277a2f6a8eccb2d95d407",
"shiftCode": "WO",
"date": "2023-01-01T00:00:00.000Z"
},
{
"_id": "63b27787f6a8eccb2d95cf30",
"shiftCode": "HOBGS_1100",
"date": "2023-01-02T00:00:00.000Z",
"attendanceId": "63b236ef3980cffaf7715d62",
"inTime": "2023-01-02T07:14:08.000Z",
"attendanceDate": "2023-01-02T00:00:00.000Z",
"name": "name2"
},
{
"_id": "63b277a2f6a8eccb2d95d409",
"shiftCode": "HOBGS_1100",
"date": "2023-01-03T00:00:00.000Z"
}
]
},
{
"workerId": "1006",
"newData": [
{
"_id": "63875e8182ebbe13ee9531d2",
"shiftCode": "HOBGS_1100",
"date": "2022-12-31T00:00:00.000Z",
"attendanceId": "61307cd385b5055a15cec158",
"attendanceDate": "2022-12-31T00:00:00.000Z",
"inTime": "2022-12-31T11:16:10.000Z",
"name": "name"
},
{
"_id": "63b277a2f6a8eccb2d95d403",
"shiftCode": "WO",
"date": "2023-01-01T00:00:00.000Z"
},
{
"_id": "63b27787f6a8eccb2d95cf39",
"shiftCode": "HOBGS_1100",
"date": "2023-01-02T00:00:00.000Z",
"attendanceId": "63b236ef3980cffaf7715d69",
"inTime": "2023-01-02T07:14:08.000Z",
"attendanceDate": "2023-01-02T00:00:00.000Z",
"name": "name"
},
{
"_id": "63b277a2f6a8eccb2d95d400",
"shiftCode": "HOBGS_1100",
"date": "2023-01-03T00:00:00.000Z"
}
]
}
]
One option is to add one more step to your pipeline which finds for each shift item its matching item in attendances using $filter:
{$set: {
shiftListData: {$map: {
input: "$shiftListData",
as: "shift",
in: {$mergeObjects: [
"$$shift",
{$ifNull: [
{$first: {
$filter: {
input: "$attendances",
cond: {$eq: ["$$this.Date", "$$shift.date"]}
}
}},
{}
]}
]}
}},
attendances: "$$REMOVE"
}}
See how it works on the playground example

MongoDB nested aggregation lookup

I wont to connect two collection so as to workouts._id had an assigned value exercise.name.
first collection "workouts":
{
"_id": {
"$oid": "60ffbd531b1cba41e8a7fde3"
},
"id_user": {
"$oid": "60fd50ee1e9d064a5cee0bec"
},
"date": {
"$date": "2021-05-20T22:00:00.000Z"
},
"name": "wielki bicek",
"workouts": [{
"_id": {
"$oid": "60e07e588a2f1946f895475f"
},
"parts": [{
"_id": {
"$oid": "60ffbd531b1cba41e8a7fde5"
},
"weight": "30",
"repeating": "10"
}]
}, {
"_id": {
"$oid": "60e07e588a2f1946f895475f"
},
"parts": [{
"_id": {
"$oid": "60ffbd531b1cba41e8a7fde9"
},
"weight": "30",
"repeating": "10"
}, {
"_id": {
"$oid": "60ffbd531b1cba41e8a7fdea"
},
"weight": "30",
"repeating": "10"
}]
}],
"__v": 0
}
second collection "exercises":
{
"_id": {
"$oid": "60e07e588a2f1946f895475f"
},
"name": "Wyciskanie sztangi",
"image": "uploads\\2021-07-03T15-12-24.173Z-test.gif",
"type": "force",
"groupMuscle": {
"$oid": "60daf099c1aaa20b4cfdf0fa"
},
"instruction": "Opierając tułów znazewnątrz.",
"__v": 0
}
I have one collection that I want to run nested aggregation on this, but I don't know how to run this operation.
$unwind unwind workouts
$lookup lookpup exercises
$addFileds get first item
$addFileds get only name field
$project remove useless field
$group group by original id
db.workouts.aggregate([
{
"$unwind": "$workouts"
},
{
"$lookup": {
"from": "exercises",
pipeline: [
{
"$project": {
name: 1
}
}
],
"localField": "workouts._id",
"foreignField": "_id",
"as": "names"
}
},
{
"$addFields": {
"workouts.i": {
"$first": "$names"
}
}
},
{
"$addFields": {
"workouts.name": "$workouts.i.name"
}
},
{
"$project": {
"names": 0,
"workouts.i": 0
}
},
{
"$group": {
"_id": {
"_id": "$_id",
"id_user": "$id_user",
"date": "$date",
"name": "$name"
},
"workouts": {
$push: "$workouts"
}
}
}
])
mongoplayground

Filter document on items in an array ElasticSearch using Where In

I have data:
[
{
"NAME": "John Doe",
"CLASS":[1,10,30]
},
{
"NAME": "Albert",
"CLASS": [1,20,40]
},
{
"NAME": "XINN",
"CLASS": [10,30]
},
{
"NAME": "UJANG",
"CLASS": [20,40]
},
{
"NAME": "BAMBANG",
"CLASS": [30,40]
}
]
I have the following query SQL:
SELECT * FROM table_name WHERE CLASS IN (1,20)
and I want what will appear is:
[{"NAME": "John Doe","CLASS":[1,10,30]},{"NAME": "Albert","CLASS": [1,20,40]},{"NAME": "UJANG","CLASS": [20,40]}]
How do I change my search to match the SQL query?
You can use the terms query to get your expected results, adding a working example.
Index sample docs
{
"name" : "John Doe",
"class" : [1, 10,30]
}
{
"name": "Albert",
"class": [1,20,40]
}
{
"name": "XINN",
"class": [10,30]
}
{
"name": "UJANG",
"class": [20,40]
}
{
"name": "BAMBANG",
"class": [30,40]
}
And search query
{
"query": {
"terms": {
"class": [
1,20
]
}
}
}
And search results
"hits": [
{
"_index": "shingleside",
"_type": "_doc",
"_id": "1",
"_score": 1.0,
"_source": {
"name": "John Doe",
"class": [
1,
10,
30
]
}
},
{
"_index": "shingleside",
"_type": "_doc",
"_id": "2",
"_score": 1.0,
"_source": {
"name": "Albert",
"class": [
1,
20,
40
]
}
},
{
"_index": "shingleside",
"_type": "_doc",
"_id": "4",
"_score": 1.0,
"_source": {
"name": "UJANG",
"class": [
20,
40
]
}
}
]

How to get size of array embedded array mongoose?

I have a category collection and I want to get category by id with some options. This is collection's structure in database.
[
{
"_id": "5d67296bf35b984e74486924",
"name": "Dinrk",
"images": [],
"recipes": [
{
"name": "Coffee",
"time": 20,
"img": "https://google.com/image-example.jpg",
"des": "This is description",
"serving": 2,
"components": [
{
"name": "Dink 1",
"quantity": "1"
},
{
"name": "Dink 2",
"quantity": "1"
},
{
"name": "Dink 2",
"quantity": "1"
}
],
"cook_steps": [
{
"des": "This is description",
"pictures": []
},
{
"des": "This is description",
"pictures": []
}
]
},
{
"name": "Coffee",
"time": 20,
"img": "https://google.com/image-example.jpg",
"des": "This is description",
"serving": 2,
"components": [
{
"name": "Dink 1",
"quantity": "1"
},
{
"name": "Dink 2",
"quantity": "1"
}
],
"cook_steps": [
{
"des": "This is description",
"pictures": []
},
{
"des": "This is description",
"pictures": []
}
]
}
]
},
{
"_id": "5d67296bf35b984e74486435555",
"name": "Cake",
"images": [],
"recipes": [
{
"name": "Cake",
"time": 20,
"img": "https://google.com/image-example.jpg",
"des": "This is description",
"serving": 2,
"components": [
{
"name": "Cake 1",
"quantity": "1"
},
{
"name": "Cake 2",
"quantity": "1"
},
{
"name": "Cake 2",
"quantity": "1"
}
],
"cook_steps": [
{
"des": "This is description",
"pictures": []
},
{
"des": "This is description",
"pictures": []
}
]
},
{
"name": "Coffee",
"time": 20,
"img": "https://google.com/image-example.jpg",
"des": "This is description",
"serving": 2,
"components": [
{
"name": "Cake 1",
"quantity": "1"
}
],
"cook_steps": [
{
"des": "This is description",
"pictures": []
},
{
"des": "This is description",
"pictures": []
}
]
}
]
}
]
This is my code to try categoryId = "5d67296bf35b984e74486924"
Category.aggregate([
{
$match: {'_id': categoryId}
},
{
$unwind: '$recipes'
},
{
$project: {
'total_components': {'$size': '$recipes.components'},
'total_cook_steps': {'$size': '$recipes.cook_steps'}
}
}
]).then(function(data) {
}, function(err) {
})
And expected result is
{
"_id": "5d67296bf35b984e74486924",
"name": "Dinrk",
"images": [],
"recipes": [
{
"name": "Coffee",
"time": 20,
"img": "https://google.com/image-example.jpg",
"des": "This is description",
"serving": 2,
"total_components": 3,
"total_cook_steps": 2
},
{
"name": "Coffee",
"time": 20,
"img": "https://google.com/image-example.jpg",
"des": "This is description",
"serving": 2,
"total_components": 2,
"total_cook_steps": 2
}
]
}
But when I run above my code, result is [].
If you understand my problem, please help me. I have search a lot, but not found solution. So I want to ask everyone. Thankyou so much.
Your query is not giving you the desired result since Mongoose does not auto-cast the 24 char hex string to ObjectId in its aggregate pipeline since $project and $group can change the schema in surprising ways that it becomes hard to infer what should be an ObjectId.
You need to manually convert the categoryId
string to ObjectId using the mongoose.Types.ObjectId method.
Compute the new fields within a $map operator instead of $unwind as this allows you an aggregate operation with fewer pipeline steps
Category.aggregate([
{ '$match': { '_id': mongoose.Types.ObjectId(categoryId) } },
{ '$addFields': {
'recipes': {
'$map': {
'input': '$recipes',
'in': {
'name': '$$this.name',
'time': '$$this.time',
'img': '$$this.img',
'des': '$$this.des',
'serving': '$$this.serving',
'total_components': { '$size': '$$this.components' },
'total_cook_steps': { '$size': '$$this.cook_steps' }
}
}
}
} }
]).then(function(data) {
}, function(err) {
})

MongoDB aggregate count of items in two arrays across different documents is the same?

Here is my MongoDB collection schema:
company: String
model: String
cons: [String] // array of tags that were marked as "cons"
pros: [String] // array of tags that were marked as "pros"
Here is my query:
[
{ "$project": {
"company": 1,
"model": 1,
"data": {
"$setUnion": [
{ "$map": {
"input": "$pros",
"as": "pro",
"in": {
"type": "$pro",
"value": "$$pro"
}
}},
{ "$map": {
"input": "$cons",
"as": "con",
"in": {
"type": "$con",
"value": "$$con"
}
}}
]
}
}},
{ "$unwind": "$data" },
{ "$group": {
"_id": {
"company": "$company",
"model": "$model",
"theTag": "$data.value"
},
"sumPros": {
"$sum": {
"$cond": [
{ "$eq": [ "$data.type", "$pro" ] },
1,
0
]
}
},
"sumCons": {
"$sum": {
"$cond": [
{ "$eq": [ "$data.type", "$con" ] },
1,
0
]
}
}
}},
{ "$group": {
"_id": {
"company": "$_id.company",
"model": "$_id.model",
},
"tags": {$push: {
"tag": "$_id.theTag",
"pros": "$sumPros",
"cons": "$sumCons"
}
}}
}]
Here is the output:
{
"_id": {
"company": "Lenovo",
"model": "T400"
},
"tags": [
{
"tag": "Quality",
"pros": 64, // expected value is 54
"cons": 64 // expected value is 10
},
{
"tag": "Value",
"pros": 76, // expected value is 30
"cons": 76 // expected value is 46
}
]
}
...
Notice that pros and cons values are the same. They, for some reason, represent the sum of pros and cons and I can't figure-out why.
What am I doing wrong?
Update:
Here is a document from the collection:
{
"company": "Lenovo",
"model": "X200",
"cons": [
"Quality"
],
"pros": [
"Value",
"Styling"
]
}
As the author of the content you are using in the query and also after asking you to submit some information in the form of data that actually supports the claim in the question here, I have to say that what you are saying is incorrect.
For the record, this is your sample at time of answer:
{
"company": "Lenovo",
"model": "X200",
"cons": [
"Quality"
],
"pros": [
"Value",
"Styling"
]
}
On your sample here, if I run the following query ( and I do extend responsibity for any misleading operations in previous answers and will ammend those immediately ) then the results I see should be what is expected:
db.collection.aggregate([
{ "$project": {
"company": 1,
"model": 1,
"data": {
"$setUnion": [
{ "$map": {
"input": "$cons",
"as": "con",
"in": {
"type": { "$literal": "con" },
"value": "$$con"
}
}},
{ "$map": {
"input": "$pros",
"as": "pro",
"in": {
"type": { "$literal": "pro" },
"value": "$$pro"
}
}}
]
}
}},
{ "$unwind": "$data" },
{ "$group": {
"_id": {
"company": "$company",
"model": "$model",
"tag": "$data.value"
},
"pros": {
"$sum": {
"$cond": [
{ "$eq": [ "$data.type", "pro" ] },
1,
0
]
}
},
"cons": {
"$sum": {
"$cond": [
{ "$eq": [ "$data.type", "con" ] },
1,
0
]
}
}
}}
])
Which produces from your sample
{
"_id" : {
"company" : "Lenovo",
"model" : "X200",
"tag" : "Quality"
},
"pros" : 0,
"cons" : 1
}
{
"_id" : {
"company" : "Lenovo",
"model" : "X200",
"tag" : "Value"
},
"pros" : 1,
"cons" : 0
}
{
"_id" : {
"company" : "Lenovo",
"model" : "X200",
"tag" : "Styling"
},
"pros" : 1,
"cons" : 0
}
Which clearly correctly allocates both "pros" and "cons" totals across the grouping keys as should be expected.
Therefore what "I see" here is that the values are not in fact "the same" but are actually "different" as matches the different conditions given to each field accumulator.
Therefore taking that further, and based on your original question:
db.collection.aggregate([
{ "$project": {
"company": 1,
"model": 1,
"data": {
"$setUnion": [
{ "$map": {
"input": "$cons",
"as": "con",
"in": {
"type": { "$literal": "con" },
"value": "$$con"
}
}},
{ "$map": {
"input": "$pros",
"as": "pro",
"in": {
"type": { "$literal": "pro" },
"value": "$$pro"
}
}}
]
}
}},
{ "$unwind": "$data" },
{ "$group": {
"_id": {
"company": "$company",
"model": "$model",
"tag": "$data.value"
},
"pros": {
"$sum": {
"$cond": [
{ "$eq": [ "$data.type", "pro" ] },
1,
0
]
}
},
"cons": {
"$sum": {
"$cond": [
{ "$eq": [ "$data.type", "con" ] },
1,
0
]
}
}
}},
{ "$group": {
"_id": {
"company": "$_id.company",
"model": "$_id.model"
},
"data": { "$push": {
"tag": "$_id.tag",
"pros": "$pros",
"cons": "$cons"
}}
}}
])
Produces:
{
"_id" : {
"company" : "Lenovo",
"model" : "X200"
},
"data" : [
{
"tag" : "Quality",
"pros" : 0,
"cons" : 1
},
{
"tag" : "Value",
"pros" : 1,
"cons" : 0
},
{
"tag" : "Styling",
"pros" : 1,
"cons" : 0
}
]
}
Which is exactly what you are asking for.

Categories