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

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

Related

mongodb aggregation with filter options

I have two collections where I'm trying to do an aggregation query with filter options. I have looked online but I couldn't find solution for this.
Col 1
[
{
_id: ObjectId('st_123'),
stud_num: 123,
school: ObjectId('sc_123'),
gender: 'M'
},
{
_id: ObjectId('st_234'),
stud_num: 123,
school: ObjectId('sc_234'),
gender: 'F'
},
{
_id: ObjectId('st_345'),
stud_num: 123,
school: ObjectId('sc_345'),
gender: 'M'
}
]
Col 2
[
{
_id: ObjectId('f_123'),
stud_health_id: ObjectId('st_123'),
schoolYear: ObjectId('sy123')
},
{
_id: ObjectId('f_234'),
stud_health_id: ObjectId('st_234'),
schoolYear: ObjectId('sy234')
},
{
_id: ObjectId('f_345'),
stud_health_id: ObjectId('st_890'),
schoolYear: ObjectId('sy234')
},
{
_id: ObjectId('f_456'),
stud_health_id: ObjectId('st_345'),
schoolYear: ObjectId('sy345')
}
]
I am trying to filter the records from collection 1 which doesn't have entry in collection 2 with extra params.
If I send {schoolYear: ObjectID('sy234)} then it should return the first and third document of collection 1 because for that year those two students doesn't have record.
One option is using $lookup and $match:
db.col1.aggregate([
{$lookup: {
from: "col2",
as: "col2",
let: {schoolYear: "sy234", stud_id: "$_id"},
pipeline: [
{$match: {$expr: {
$and: [
{$eq: ["$schoolYear", "$$schoolYear"]},
{$eq: ["$stud_health_id", "$$stud_id"]}
]
}
}
}
]
}
},
{$match: {"col2.0": {$exists: false}}},
{$unset: "col2"}
])
See how it works on the playground example

Need help aggregating mongoose data

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

Group document mongodb

I'm trying to group document with mongodb but couldn't figure out how.
I have a document looks like this
{
_id: ObjectId('12345'),
username: 'asd',
region: 'zxc',
amount: 500,
type: 'car',
brand: 'vent',
order: 2
},
{
_id: ObjectId('98283'),
username: 'asd',
region: 'zxc',
amount: 1500,
type: 'car',
brand: 'dinosaur',
order: 1
}
And I want to group the document by username, region, type and make a new sub document from the result and order the sub-document ascending by the order. Also calculate the amount as a totalAmount. Which looks like this.
{
username: 'asd',
region: 'zxc',
type: 'car',
cart: [
{
brand: 'dinosaur',
amount: 1500
},
{
brand: 'vent',
amount: 500
}
],
totalAmount: 2000
}
I could only do this so far
db.test.aggregate([
{
$group: {
_id: {username: "$username"},
region: {$first: "$region"},
type: {$first: "$type"},
totalAmount: {$sum: "$amount"}
}
}
])
Thanks
You should put all of the fields you want to group on in the _id.
Use $push to collect values into an array, and $sum to compute the total:
db.collection.aggregate([
{
$group: {
_id: {
username: "$username",
region: "$region",
type: "$type"
},
cart: {
$push: {
brand: "$brand",
amount: "$amount"
}
},
total: {
$sum: "$amount"
}
}
}
])
Playground

How to group by employeeId, itemId and tranDate show one item row and tranDate?

Hello everyone I need help I have collection below and I want to group by employeeId, itemId and tranDate
this my collection
let colection = [
{
tranDate: '2020/3/15',
employeeId: '001',
itemId: '001',
qty: 10
},
{
tranDate: '2020/3/15',
employeeId: '001',
itemId: '001',
qty: 20
},
{
tranDate: '2020/3/16',
employeeId: '001',
itemId: '002',
qty: 100
},
{
tranDate: '2020/3/16',
employeeId: '001',
itemId: '001',
qty: 60
}
]
I want my collection like that and you can see the same employeeId, itemId, tranDate
//Result
let newcolection = [
{
employeeId: '001',
itemId: '001',
itemArr: [{ tranDate: '2020/3/15', qty: 30 }, { tranDate: '2020/3/16', qty: 60 }]
},
{
employeeId: '001',
itemId: '002',
itemArr: [{ tranDate: '2020/3/15', qty: 0 }, { tranDate: '2020/3/16', qty: 100 }]
},
]
Here is a pipeline that should do what you need:
[
{$group: {
_id: {
employeeId: '$employeeId',
itemId: '$itemId',
tranDate: '$tranDate'
},
qty: {
$sum: '$qty'
},
employeeId: {
$first: '$employeeId'
},
itemId: {
$first: '$itemId'
},
tranDate: {
$first: '$tranDate'
}
}},
{$project: {
employeeId: 1,
itemId: 1,
myObject: {
tranDate: "$tranDate",
qty: "$qty"
}
}},
{$group: {
_id: {employeeId: '$employeeId',
itemId: '$itemId'},
itemArray: {
$push: "$myObject"
}
}},
{
$project: {
_id: 0,
employeeId: "$_id.employeeId",
itemId: "$_id.itemId",
itemArray: 1
}
}, {
$out: 'newcollection'
}]
The first $group stage groups the documents by employee id, item id, and transaction date in order to calculate the quantities.
The first $project stage moves the transaction date and quantity into an object called myObject.
The next $group stage groups the documents by employee id and item id in order to create an itemArray that contains an object for each transaction date.
The next $project stage moves the employee id and the item id out of _id.
The final $out stage stores the results of the pipeline in a collection called newcollection.
Below are the documents stored in newcollection.
{"_id":{"$oid":"5e70c1e378117090fe4b6ed7"},"itemArray":[{"tranDate":{"$date":{"$numberLong":"1584331200000"}},"qty":{"$numberInt":"100"}}],"employeeId":"001","itemId":"002"}
{"_id":{"$oid":"5e70c1e378117090fe4b6ed8"},"itemArray":[{"tranDate":{"$date":{"$numberLong":"1584244800000"}},"qty":{"$numberInt":"30"}},{"tranDate":{"$date":{"$numberLong":"1584331200000"}},"qty":{"$numberInt":"60"}}],"employeeId":"001","itemId":"001"}

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
}