How to find nested array object by mongoose? - node.js

[
{
_id: new ObjectId("63cc22dfe258792490ac0fb5"),
categoryName: 'Rental Equipment',
products: [
{
_id: new ObjectId("63ce9701ba256f972473cd6a"),
partName: 'product one'
partNumber: 'ym129150-35151'
},
{
_id: new ObjectId("63ce2221207c858d01a3c8ac"),
partName: 'product two',
partNumber: 'ym129150-35151',
}
]
},{
_id: new ObjectId("63cc22dfe258792490ac0fb5"),
categoryName: 'Engine Parts',
products: [
{
_id: new ObjectId("23ce9701ba156f972473cd3a"),
partName: 'product one'
partNumber: 'ym129150-35151'
},
{
_id: new ObjectId("73ce2221203c858d01a3c83b"),
partName: 'product two',
partNumber: 'ym129150-35151',
}
]
}
]
I want to find the specific array(products) of object which one is located in the products array. I tried to find out the array of object but it's return a whole collections of object where the products _id is located.
const product = db.collection.find({
products: {
$elemMatch: {
_id: "63ce9701ba256f972473cd6a",
},
},
});
I Want to find a specific object from the products array.
The output would be:
{
_id: new ObjectId("63ce9701ba256f972473cd6a"),
partName: 'product one'
partNumber: 'ym129150-35151'
}

Try using the aggregation framework:
db.collection.aggregate([
{
"$unwind": "$products"
},
{
"$match": {
"products._id": "63ce9701ba256f972473cd6a",
}
},
{
"$project": {
"_id": 0,
"products._id": 1,
"products.partName": 1,
"products.partNumber": 1,
}
},
{
"$replaceRoot": {
"newRoot": "$products"
}
}
])
Link to playground

Related

Mongodb $lookup inside $addfield?

I have a collection named users, and this is how one specific user will look like:
{
_id: 'Object ID',
name: 'String',
cart: [
{
product_id: 'Product object ID',
quantity: 'Number',
},
...
],
}
I want my desired results to look like this:
{
_id: 'Object ID',
name: 'String',
cart: [
{
product_id: 'Product object ID',
quantity: 'Number',
product_details: {
'all the details of the product from Products collection which matches the product_id',
},
},
...
],
}
I tried adding addFields into lookup but it's getting too complicated and doesn't work as desired. What's the best way to aggregate this?
You can achieve this in several different ways, here's what I consider to be the most simple:
db.users.aggregate([
{
"$lookup": {
"from": "products",
let: {
cart: "$cart"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$_id",
"$$cart.product_id"
]
}
}
},
{
$replaceRoot: {
newRoot: {
"$mergeObjects": [
"$$ROOT",
{
"$arrayElemAt": [
{
$filter: {
input: "$$cart",
cond: {
$eq: [
"$_id",
"$$this.product_id"
]
}
}
},
0
]
}
]
}
}
}
],
"as": "cart"
}
}
])
Mongo Playground

Retrieve embedded document using Mongo DB filter

Pet: [
{ _id: "pet_id1", moko: "zzzzz", status: "st_id2"},
{ _id: "pet_id2", moko: "eeeeeezasxsdsf", status: "st_id3"},
{ _id: "pet_id3", moko: "aqsz", status: "st_id4" },
{ _id: "pet_id4", moko: "goreefggg", status: "st_id3" },
{ _id: "pet_id5", moko: "zzzzz", status: "st_id2" }
],
PetStatus: [
{ _id: "st_id1", code: 1, petId: "pet_id5" },
{ _id: "st_id2", code: 3, petId: "pet_id1" }
]
I want to retrieve all documents of Pet Table and for each element, replace status by some object like
petModel.find({ $and: [moko: "zzzzz"], ...})
Filter result i want :
[
{ _id: "pet_id1", moko: "zzzzz", status: { _id: "st_id2",code: 3,petId: "pet_id1"},},
{ _id: "pet_id5", moko: "zzzzz", status: { _id: "st_id2",code: 3, petId: "pet_id1"},},
]
I found this as solution, it works now:
const filter = [
{ $match: { breederId: "zzzzz",},},
{
$lookup: {
from: 'PetStatus',let: { status_id: '$moko',},
pipeline: [
{$match: { $expr: {
$and: [
{$eq: [3, '$code'],},
{$eq: ['$_id', '$$status_id'],},],
},
},
},
],
as: 'status',},},
{$match: {$expr: { $gt: [
{$size: '$status', }, 0,],},
},},];
petModel.aggregate(filter);

How to calculate the percentage using facet in MongoDB?

I am calculating the notification percentage in my app for tracking some statistics.
My Collection:
[
{
_id: "123",
status: "seen",
userId: "589"
},
{
_id: "223",
status: "seen",
userId: "589"
},
{
_id: "474",
status: "unseen",
userId: "589"
},
{
_id: "875",
status: "seen",
userId: "112"
},
{
_id: "891",
status: "unseen",
userId: "112"
}
]
Expected Result:
Here we can see that, UserId - 589 has received 3 notifications out of which 2 are seen. So the calculation is (totalNumOfSeen/totalNumOfNoticationsSent) * 100
[{
userId: "589",
notificationPercentage : 66.66
},{
userId: "112",
notificationPercentage : 50
}]
I am using a facet for grouping and matching but that is returning me an array of object and I am not getting how to perform divide on this.
My Query:
db.collection.aggregate([
{
$facet: {
totalNumOfSeen: [
{
$match: {
userId: "589",
status: "seen"
}
},
{
$group: {
_id: "$userId",
totalNumOfSeen: {
$sum: 1
}
}
}
],
totalNumOfNoticationsSent: [
{
$match: {
userId: "589",
}
},
{
$group: {
_id: "$userId",
totalNumOfNoticationsSent: {
$sum: 1
}
}
}
]
}
}
])
The Above Query is giving me the below Result:
[
{
"totalNumOfNoticationsSent": [
{
"_id": "589",
"totalNumOfNoticationsSent": 3
}
],
"totalNumOfSeen": [
{
"_id": "589",
"totalNumOfSeen": 2
}
]
}
]
MongoPlayground - https://mongoplayground.net/p/jHn2ZlshgDL
Now I need to add one more field as notificationPercentage and calculate the notification percentage based on the above facet result. Really appreciate the help.
You can try,
$group by userId and get totalSeen count using $cond if status is seen, get total count of notification using $sum,
$project to show required fields, and calculate percentage using $divide and $multiply
db.collection.aggregate([
{
$group: {
_id: "$userId",
totalSeen: {
$sum: { $cond: [{ $eq: ["$status", "seen"] }, 1, 0] }
},
total: { $sum: 1 }
}
},
{
$project: {
_id: 0,
userId: "$_id",
notificationPercentage: {
$multiply: [{ $divide: ["$totalSeen", "$total"] }, 100]
}
}
}
])
Playground

Join MongoDB subdocuments in same parent document

Given this Orders collection:
// Order documents
[
{
_id: "order_123",
items: [
{ _id: "item_123", type: "T-Shirt" },
{ _id: "item_234", type: "Hoodie" },
{ _id: "item_345", type: "Hat" },
],
refunds: [
{
_id: "refund_123",
items: ["item_123", "item_234"],
},
{
_id: "refund_234",
items: ["item_345"],
},
],
},
]
Is it possible to map refunds.items -> items._id, allowing us to filter by type?
This is how we currently get the refund sub-documents:
db.orders.aggregate([
{
$replaceRoot: {
newRoot: {
order: "$$ROOT",
refunds: "$$ROOT.refunds",
},
},
},
{
$unwind: "$refunds",
},
{
$project: {
order: "$order",
refund: "$refunds",
},
},
]);
Which gives us:
// Refund documents
[
{
refund: {
_id: "refund_123",
items: ["item_123", "item_234"],
},
order: { ... }, // The original order document
},
{
refund: {
_id: "refund_234",
items: ["item_345"],
},
order: { ... }, // The original order document
},
]
From here, we want to map up refund.items -> order.items._id to produce the following output:
[
{
_id: "refund_123",
items: [
{ _id: "item_123", type: "T-Shirt" },
{ _id: "item_234", type: "Hoodie" },
],
},
{
_id: "refund_234",
items: [
{ _id: "item_345", type: "Hat" }
],
},
]
Allowing us to filter refund documents by type.
You can do this using $unwind and $filter,
$unwind deconstruct array refunds
$project to show refund id in _id, and filter items that are in refunds.items array using $filter
db.orders.aggregate([
{ $unwind: "$refunds" },
{
$project: {
_id: "$refunds._id",
items: {
$filter: {
input: "$items",
cond: { $in: ["$$this._id", "$refunds.items"] }
}
}
}
}
])
Playground

How to get aggregated sum of values in an array of mongoose subdocuments when query parent?

I'm trying to build some advanced hello world app on top of express and mongoose. Assume I have next Schemas:
const pollOptionsSchema = new Schema({
name: String,
votes: {
type: Number,
default: 0
}
});
const pollSchema = new Schema({
name: String,
dateCreated: { type: Date, default: Date.now },
author: { type: Schema.Types.ObjectId },
options: [pollOptionsSchema]
});
And when I simply call
Poll.findOne({_id: req.params.id}).exec((err, data) => {
if (err) console.log(err);
// I receive next data:
// { _id: 58ef3d2c526ced15688bd1ea,
// name: 'Question',
// author: 58dcdadfaea29624982e2fc6,
// __v: 0,
// options:
// [ { name: 'stack', _id: 58ef3d2c526ced15688bd1ec, votes: 5 },
// { name: 'overflow', _id: 58ef3d2c526ced15688bd1eb, votes: 3 } ],
// dateCreated: 2017-04-13T08:56:12.044Z }
});
The question is how I could receive same data + aggregated number of votes (i.e 8 in case above) after calling some method on Model level, for example:
// I want to receive:
// { _id: 58ef3d2c526ced15688bd1ea,
// name: 'Question',
// author: 58dcdadfaea29624982e2fc6,
// __v: 0,
// totalNumberOfVotes: 8,
// options:
// [ { name: 'stack', _id: 58ef3d2c526ced15688bd1ec, votes: 5 },
// { name: 'overflow', _id: 58ef3d2c526ced15688bd1eb, votes: 3 } ],
// dateCreated: 2017-04-13T08:56:12.044Z }
Or maybe I need to implement some extra method on document level i.e (data.aggregate)?
I've already reviewed:
http://mongoosejs.com/docs/api.html#model_Model.mapReduce
http://mongoosejs.com/docs/api.html#aggregate_Aggregate
https://docs.mongodb.com/manual/core/map-reduce/
https://docs.mongodb.com/manual/tutorial/map-reduce-examples/
But can't utilize it for my case :(
Any advice will be much appreciated. Thanks!
Use $reduce operator within an $addFields pipeline to create the totalNumberOfVotes field. In your aggregate pipeline, the first step is the $match which filters the document stream to allow only matching documents to pass unmodified into the next pipeline stage and uses standard MongoDB queries.
Consider running the following aggregate operation to get the desired result:
Poll.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(req.params.id) } },
{
"$addFields": {
"totalNumberOfVotes": {
"$reduce": {
"input": "$options",
"initialValue": 0,
"in": { "$add" : ["$$value", "$$this.votes"] }
}
}
}
}
]).exec((err, data) => {
if (err) console.log(err);
console.log(data);
});
NB: The above will work for MongoDB 3.4 and greater.
For other earlier versions you would need to $unwind the options array first before grouping the denormalised documents within a $group pipeline step and aggregating with the accumulators $sum, $push and $first.
The following example shows this approach:
Poll.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(req.params.id) } },
{ "$unwind": { "path": "$options", "preserveNullAndEmptyArrays": true } },
{
"$group": {
"_id": "$_id",
"totalNumberOfVotes": { "$sum": "$options.votes" },
"options": { "$push": "$options" },
"name": { "$first": "$name" },
"dateCreated": { "$first": "$dateCreated" },
"author": { "$first": "$author" }
}
}
]).exec((err, data) => {
if (err) console.log(err);
console.log(data);
});

Resources