Need help aggregating mongoose data - mongodb

I have a MERN stack application and when I make a GET call I want to return the votes of all of my MongoDB objects.
This is what my objects look like:
{_id: ObjectId("6092d48d96984d233cf77152")
user: "John Doe"
movies: [
0:
_id: ObjectId("6092b19345f48a33447468a7"),
title: "Alpha",
ranking: 3
1:
_id: ObjectId("6092b19345f48a33447468a7"),
title: "Bravo",
ranking: 2
2:
_id: ObjectId("6092b19345f48a33447468a7"),
title: "Charlie",
ranking: 1
]}
{_id: ObjectId("6092d48d96984d233cf77152")
user: "John Doe"
movies: [
0:
_id: ObjectId("6092b19345f48a33447468a7"),
title: "Alpha",
ranking: 3
1:
_id: ObjectId("6092b19345f48a33447468a7"),
title: "Bravo",
ranking: 2
2:
_id: ObjectId("6092b19345f48a33447468a7"),
title: "Charlie",
ranking: 1
]}
Obviously there will be more data but basically I would like it to return:
[{title: "Alpha", ranking: 6},
{title: "Bravo", ranking: 4},
{title: "Charlie", ranking: 2}]
I assume I have to use the $match function but this is my first time using Mongoose/MongoDB like this. Thanks in advance!

$unwind deconstruct movies array
$group by movies.title and sum ranking
let result = await YourModelName.aggregate([ // replace your model name
{ $unwind: "$movies" },
{
$group: {
_id: "$movies.title",
ranking: { $sum: "$movies.ranking" }
}
}
])
Playground

Related

MongoDB - How to access a newly created array field created with $lookup and aggregate function

Assuming I have two collections:
courses:
[
{
_id: 1,
name: "Geometry",
teacher_id: 1
},
{
_id: 2,
name: "English",
teacher_id: 2
}
]
teachers:
[
{
_id: 1,
firstName: "John",
lastName: "Adams"
},
{
_id: 2,
firstName: "Mary",
lastName: "Jane"
}
]
Now I perform an aggregation on the two collections to create something similar to a join in SQL:
db.collection("courses").aggregate([
{
$lookup:{
from: "teachers",
localField: "teacher_id",
foreignField: "_id",
as: "teacher_info"
}
},
{
$match:{
//I want to perform a match or filter here on the teacher_info
}
}
]);
The $lookup and aggregation will return a list of documents that have a new teacher_info array field.
[
{
_id: 1,
name: "Geometry",
teacher_id: 1,
teacher_info: [
{
_id: 1,
firstName: "John",
lastName: "Adams"
},
]
},
{
_id: 2,
name: "English",
teacher_id: 1,
teacher_info: [
{
_id: 2,
firstName: "Mary",
lastName: "Jane"
},
]
}
]
I need to perform a match operation in the newly created teacher_info array field. For example, only keep the teacher that has the first name "John". How can I do so? Is that possible?
You can work with dot notation in your $match stage.
{
$match: {
"teacher_info.firstName": "John"
}
}
Demo # Mongo Playground

MongoDB: Add field from Aggregation Output to Query

I would like to perform an aggregation query, then a find query, and apply the output of the aggregation as a new field in the find results, ie:
A have dataset like this:
{id: 1, city: "Paris", comment: "...", status: "Active"},
{id: 2, city: "London", comment: "...", status: "Active"},
{id: 3, city: "Paris", comment: "...", status: "Active"},
{id: 4, city: "New York", comment: "...", status: "Active"},
{id: 5, city: "London", comment: "...", status: "Active"},
{id: 6, city: "London", comment: "...", status: "Active"},
{id: 7, city: "London", comment: "...", status: "Disabled"}
I want to get the counts for each active city:
collection.aggregate([
{$match: {status: "Active"}},
{$group: {_id: "$city", count: {$sum: 1}}}
])
But I would like to apply the count to each entry, matched according to city. It would return something like this:
{id: 1, city: "Paris", comment: "...", status: "Active", count: 2},
{id: 2, city: "London", comment: "...", status: "Active", count: 3},
{id: 3, city: "Paris", comment: "...", status: "Active", count: 2},
{id: 4, city: "New York", comment: "...", status: "Active", count: 1},
{id: 5, city: "London", comment: "...", status: "Active", count: 3},
{id: 6, city: "London", comment: "...", status: "Active", count: 3},
{id: 7, city: "London", comment: "...", status: "Disabled", count: 3}
Ideally I would like to do this in a single query so that it can be sorted and paginated according to count.
$group by city and push root object to a root field, count status that is Active only
$unwind deconstruct root array
$mergeObjects to merge $root object and count field
$replaceRoot to replace merged object to root
db.collection.aggregate([
{
$group: {
_id: "$city",
root: { $push: "$$ROOT" },
count: {
$sum: {
$cond: [{ $eq: ["$status", "Active"] }, 1, 0]
}
}
}
},
{ $unwind: "$root" },
{
$replaceRoot: {
newRoot: { $mergeObjects: ["$root", { count: "$count" }] }
}
}
])
Playground

mongodb aggregation pipeline merging two collections both with $match criteria required

Hi I have two collections (product and order) and am trying to create an aggregation that will result in all the products being listed for a particular supplier with the qty from a particular order being appended to the data if the order contains that particular product in it.
Product Collection
{
_id: objectId,
supplierId: objectId1, // supplier 1
sku: "prod1"
},
{
_id: objectId,
supplierId: objectId1, // supplier 1
sku: "prod2"
},
{
_id: objectId,
supplierId: objectId1, // supplier 1
sku: "prod3"
}
{
_id: objectId,
supplierId: objectId2, // supplier 2
sku: "prod4"
},
{
_id: objectId,
supplierId: objectId2, // supplier 2
sku: "prod5"
}
Order Collection
{
_id: objectId5 // order 1
product: [{
_id: objectId,
supplierId: objectId1,
sku: prod1,
qty: 5
},
{
_id: objectId,
supplierId: objectId1,
sku: prod2,
qty: 1
}]
},
{
_id: objectId6 // order 2
product: [{
_id: objectId,
supplierId: objectId1,
sku: prod1,
qty: 100
}, ...
]
}
result should be
{
_id: objectId,
supplierId: objectId1, // supplier 1
sku: "prod1",
qty: 5
},
{
_id: objectId,
supplierId: objectId1, // supplier 1
sku: "prod2",
qty: 1
},
{
_id: objectId,
supplierId: objectId1, // supplier 1
sku: "prod3",
qty: 0 || null
}
I've not got very far with my attempt but have noted down some pseudocode as to where my head is at with this problem. I'm no doubt over simplyfing what needs to be done here but any help would be greatly appreciated.
product.aggregate([{$match: {supplierId: objectId1}},
{$lookup: {join order collection but only where orderId: objectId5}},
{$projection: {append qty where there is one append to each product that matches. prodcuts that don't match append 0 or null}}
])
You can try below aggregation with $lookup pipeline in 3.6 version.
$lookup pipeline
db.products.aggregate([
{"$match":{"supplierId":objectId1}},
{"$lookup":{
"from":"orders",
"let":{"supplierId":"$supplierId","sku": "$sku"},
"pipeline":[
{"$match":{"_id":objectId5, "$expr":{"$and":[{"$in":["$$supplierId","$product.supplierId"]},{"$in":["$$sku","$product.sku"]}]}}},
{"$unwind":"$product"},
{"$match":{"$expr":{"$and":[{"$eq":["$$supplierId","$product.supplierId"]},{"$eq":["$$sku","$product.sku"]}]}}}
],
"as":"mproduct"
}},
{"$replaceRoot":{"newRoot":{"$mergeObjects":["$$ROOT",{"$cond":[{"$ne":["$mproduct",[]]},{"qty":{"$arrayElemAt":["$mproduct.product.qty",0]}},{"qty":0}]}]}}},
{"$project":{"mproduct":0}}
])

Why is $match not used in the Mongo Aggregation query?

As described in the mongo documentation:
https://docs.mongodb.com/manual/reference/sql-aggregation-comparison/
There is a query for the following SQL query:
SELECT cust_id,
SUM(li.qty) as qty
FROM orders o,
order_lineitem li
WHERE li.order_id = o.id
GROUP BY cust_id
And the equivalent mongo aggregation query is as follows:
db.orders.aggregate( [
{ $unwind: "$items" },
{
$group: {
_id: "$cust_id",
qty: { $sum: "$items.qty" }
}
}
] )
However, the query is workinf fine as expected. My question, why is there no $match clause for the corresponding WHERE clause in SQL? And how is $unwind compensating the $match clause?
The comment by #Veeram is correct. The where clause in the SQL is unnecessary because the items list is embedded in the orders collection, where in a relational database you would have both an orders table and an orders_lineitem table (names taken from the description at https://docs.mongodb.com/manual/reference/sql-aggregation-comparison/)
Per the example data, you start with documents like this:
{
cust_id: "abc123",
ord_date: ISODate("2012-11-02T17:04:11.102Z"),
status: 'A',
price: 50,
items: [ { sku: "xxx", qty: 25, price: 1 },
{ sku: "yyy", qty: 25, price: 1 } ]
}
When you $unwind, the items are unwound but the rest of the data is projected. If you run a query like
db.orders.aggregate([ {"$unwind": "$items"} ])
you get the output
{
cust_id: "abc123",
ord_date: ISODate("2012-11-02T17:04:11.102Z"),
status: 'A',
price: 50,
items: { sku: "xxx", qty: 25, price: 1 }
},
{
cust_id: "abc123",
ord_date: ISODate("2012-11-02T17:04:11.102Z"),
status: 'A',
price: 50,
items: { sku: "yyy", qty: 25, price: 1 }
}
That has flattened the items array, allowing the $group to add the items.qty field:
db.orders.aggregate([
{"$unwind": "$items"},
{"$group": {
"_id": "$cust_id",
"qty": {"$sum": "$items.qty"}
}
}])
With the output:
{ "_id": "abc123",
"qty": 50
}

MongoDB .Need to count fileds of an array

I have a collection like below
{ _id: 1, zipcode: "63109", students: [
{ name: "john", school: 102, age: 10 },
{ name: "jess", school: 102, age: 11 },
{ name: "jeff", school: 108, age: 15 }
] }
{ _id: 2, zipcode: "63110", students: [
{ name: "ajax", school: 100, age: 7 },
{ name: "achilles", school: 100, age: 8 },
] }
{ _id: 3, zipcode: "63109", students: [
{ school: 100, age: 7 },
{ school: 100, age: 8 },
] }
{ _id: 4, zipcode: "63109", students: [
{ name: "barney", school: 102, age: 7 },
{ name: "ruth", school: 102, age: 16 },
] }
Note:Some document doesnot have name field.
I want to findout the count of name field.
Kindly suggest query to find count in mongo
You can do this with aggregation framework with $unwind. Let say your collection name is test:
db.test.aggregate(
{$unwind: '$students'},
{$match:{'students.name':{$exists:1}}},
{$group:{_id: '$students.name', count:{$sum:1}}},
{$project:{tmp:{name:'$_id', count:'$count'}}},
{$group:{_id:'Total Names', total:{$sum:1}, data:{$addToSet:'$tmp'}}}
)
Hope this is what you want!
You need to use the aggregation framework in this case.
The first aggregation step would be to use $unwind to turn the arrays into streams of documents.
Then you can use $group as a second aggregation step with $sum:1 to get the count of every name.
db.collection.aggregate([
{
$unwind: "$students"
},
{
$group: {
_id:"$students.name",
count: { $sum:1 }
}
}
]
This is doable through the aggregation framework
I'll assume your collection is called mycoll:
db.mycoll.aggregate([
{$unwind:'$students'},
{$group:{_id:'$name', 'count':{$sum:1}},
{$match:{'students.name':{$exists:1}}}
]);
References:
Aggregations http://docs.mongodb.org/manual/tutorial/aggregation-zip-code-data-set/
Exists: http://docs.mongodb.org/manual/reference/operator/query/exists/
Summing '1' essentially turns it into a count.