How can I select document with conditional subdocs? - node.js

I need to find documents with Mongoose that contains at least one subdocument, respecting the $elemMatch condition.
My main doc sample is like this:
{
desc: 'Sample',
user: 10,
prices: [{ code: 1, price: 10 }, { code: 2, price: 0 }]
}
I need the documents that contains at least one subdoc with price bigger than 0. If doc no have price bigger than 0, the main doc may be discarded.
With $elemMatch, I can filter the subdocs, but not the main doc:
Prod.find({ user: 10}, { prices: { $elemMatch: { price: { $gt: 0 } } } })
With this, I have the document:
[{
desc: 'Sample',
user: 10,
prices: []
}]
When should it be:
[]
How can I do this?

Related

How do I display only part of the array after findOne in MongoDB?

Restaurants is a collection and has objects like below:
{
_id: new ObjectId("61723c7378b6d3a5a02d908e"),
name: 'The Blue Hotel',
location: 'Noon city, New York',
phoneNumber: '122-536-7890',
website: 'http://www.bluehotel.com',
priceRange: '$$$',
cuisines: [ 'Mexican', 'Italian' ],
overallRating: 0,
serviceOptions: { dineIn: true, takeOut: true, delivery: true },
reviews: [
{
_id: new ObjectId("61736a0f65b9931b9e428789"),
title: 'asd',
reviewer: 'khoh',
rating: 3,
dateOfReview: '5/12/2002',
review: 'hey'
},
_id: new ObjectId("61736a0f65b9931b9e428790"),
title: 'dom',
reviewer: 'firuu',
rating: 4,
dateOfReview: '25/1/2002',
review: ' bruh'
}
]
}
I am using the below code to find this object based on the review id provided
async get(reviewId) {
const restaurantsCollection = await restaurants();
reviewId = ObjectId(reviewId)
const r = await restaurantsCollection.findOne({reviews: {$elemMatch: {_id: reviewId}}})
return r
This returns the whole object from the restaurant collection, what do I do if I want only the review displayed whose id is provided in get(reviewID)
Output:
{
_id: new ObjectId("61736a0f65b9931b9e428790"),
title: 'dom',
reviewer: 'firuu',
rating: 4,
dateOfReview: '25/1/2002',
review: ' bruh'
}
With a projection, specify the fields to return
The following returns only the review whose id is provided in get(reviewID)
async get(reviewId) {
const restaurantsCollection = await restaurants();
reviewId = ObjectId(reviewId)
const r = await restaurantsCollection.findOne(
{ reviews: { $elemMatch: { _id: reviewId } } },
{ "reviews.$": 1 }
)
return r
}
Test Here
You can also use find instead of fineOne
Query
replace the ObjectId("61736a0f65b9931b9e428789") with reviewId
this will return the reviews that match the _id in an array
if you want to get the first only, in case there is always max 1
you can replace the last project with
{"$project": {"_id": 0, "review": {"$arrayElemAt": ["$reviews", 0]}}}
*not sure if this is what you need
Test code here
aggregate(
[{"$match": {"reviews._id": ObjectId("61736a0f65b9931b9e428789")}}
{"$set":
{"reviews":
{"$filter":
{"input": "$reviews",
"cond":
{"$eq": ["$$this._id", ObjectId("61736a0f65b9931b9e428789")]}}}}},
{"$project": {"_id": 0, "reviews": 1}}])
This might not be the correct answer of your question but you can try something like this.
const r = await restaurantsCollection.findOne({reviews: {$elemMatch: {_id: reviewId}}})?.reviews.find(review => review._id.equals(reviewId))

implement a search functionality that query MongoDB database and return all the objects which contain a user given value in node js and express

I want to implement a search functionality that would query my MongoDB database and return all the objects which contain (full/partially) the name I am searching for.
Example:
My object collection is products, and I want to see every product which contains the name I search, from the product names.
My 'Products' collection looks like this...
[ { _id: 5f79,
productName: 'Test-image12345',
price: 60,
details: 'Test product' },
{ _id: 5f7d,
productName: 'Test-image1234',
price: 60,
details: 'Test product'},
{ _id: 5fv4,
productName: 'Test',
price: 60,
details: 'Test product'},
]
Now I need to find all the products with "Test-image1234"
// search a product by name
productRoute.get('/getproduct/:name', async (req,res) => {
try {
const findname = req.params.name;
const objs = await Product.find({productName:{ $regex:'.*'+findname+'.*'} });
res.json(objs);
} catch (error) {
res.json({message: error});
}
})
Now I get the answer as follows...
[ { _id: 5f79,
productName: 'Test-image12345',
price: 60,
details: 'Test product' },
{ _id: 5f7d,
productName: 'Test-image1234',
price: 60,
details: 'Test product'}
]

How to use aggregate in mongoosejs with this example

I am here to ask a question about mongo aggregate function to achieve this example.
Scenario
I have 3 Mongo Schema i.e House, family and educations which as :
House: {
_id: ObjectId,
state: Number,
houseNumber: Number
}
Family: {
_id: ObjectId,
houseId: ObjectId,//ref: house
name: String,
gender: String
}
Education: {
_id: ObjectId,
familyId: ObjectId,//ref: Family
level: String, //might be one of ["primary","secondary","higher_secondary"]
}
Expected Output:
{
state1: {
primary: {
male: 3,
female: 4
},
secondary: {
male: 4,
female: 8
}
},
state2: {
primary: {
male: 5,
female: 4
},
secondary: {
male: 4,
female: 6
}
}
}
I want to group all the education level by gender and then ward.
What I did:
I am newbie in mongo world and recently shifted from sql to no-sql. I had done this:
let edu = await Education.find({level: "primary"}).populate({
path: "family",
match: {gender: "male"},
select: "house",
populate: {
path: "house",
match: {state: 1},
select: "_id"
}
});
let count = (await edu.filter(each => !isEmpty(each.family) && !isEmpty(each.family.house)).length) || 0;
By doing this I get count of male member who has studied primary from state 1. but I cannot afford to call this function one by one for each data.
As requestd the sample data are:
house = [
{
_id: AA1,
state: 1,
houseNumber: 101
},
{
_id: AA2,
state: 1,
houseNumber: 102
},
{
_id: AA3,
state: 2,
houseNumber: 201
}
];
family = [
{
_id: BB1,
houseId: AA1, //ref: house
name: "John",
gender: "male"
},
{
_id: BB2,
houseId: AA1, //ref: house
name: "Solena",
gender: "female"
},
{
_id: BB3,
houseId: AA2, //ref: house
name: "Efrain",
gender: "male"
},
{
_id: BB4,
houseId: AA3, //ref: house
name: "Naruto",
gender: "male"
}
];
education = [
{
_id: CC1,
familyId: AA1, //ref: Family
level: "primary"
},
{
_id: CC2,
familyId: AA2, //ref: Family
level: "secondary"
},
{
_id: CC3,
familyId: AA3, //ref: Family
level: "primary"
},
{
_id: CC4,
familyId: AA4, //ref: Family
level: "secondary"
}
];
P.S expected output is not relevant output to the sample data. And ObjectId has been replaced with some unique reference.
Any lead from here guyz?
You can use below aggregation query in 4.x version.
Query the family collection and join to education collection to get the level followed by join to house collection to get the state.
Once you have all the data group by state, level and gender to count all the matches followed by other groups for formatting result. Last stage to promote the aggregated result into its own document.
Last two groups formatting the results from previous stage into named key value document. First group to format the results into gender and count grouped by state. Second group to format the previously combined gender and count with education key.
Finally replace root stage to format the combined gender, count and education doc with state key.
Also added output after each stage for clarity.
db.family.aggregate(
[
{"$lookup":{
"from":"education",
"localField":"_id",
"foreignField":"familyId",
"as":"education"
}},
{"$unwind":"$education"},
{"$lookup":{
"from":"house",
"localField":"houseId",
"foreignField":"_id",
"as":"state"
}},
{"$unwind":"$state"},
{"$group":{
"_id":{
"state":"$state.state",
"education":"$education.level",
"gender":"$gender"
},
"count":{"$sum":1}
}},//{"_id":{"state" :1,"education" :"secondary","gender":"female"},"count":1}
{"$group":{
"_id":{"state":"$_id.state","education":"$_id.education"},
"gandc":{"$mergeObjects":{"$arrayToObject":[[["$_id.gender","$count"]]]}}
}},//{"_id":{"state":1,"education":"primary"},"gandc":{"male":2}}
{"$group":{
"_id":"$_id.state",
"egandc":{"$mergeObjects":{"$arrayToObject":[[["$_id.education","$gandc"]]]}}
}},//{"_id":1,"egandc":{"primary":{"male":2},"secondary":{"female":1}}}
{"$replaceRoot":{"newRoot":{"$arrayToObject":[[[{"$toString":"$_id"},"$egandc"]]]}}} ])
])
//{"1":{"primary":{"male" : 2 },"secondary":{"female":1}}}

How to fetch and group data from mongodb collection where each object have same value in a group

Already reviewed questions and solutions: Question 1 and Question 2 but not getting clear understanding regarding select data and group them.
MongoDB schema and data I have:
Currency Model
[ { currency: 'USD',
status: 1,
user_id: '123',
price: 43.67,
quantity: 22019 },
{ currency: 'USD',
status: 1,
user_id: '234',
price: 43.69,
quantity: 22019,
},
{ currency: 'USD',
status: 1,
user_id: '456',
price: 43.67,
quantity: 8,
}... more 100 data ]
What I want as a result:
Want to merge Quantity value for 1st and 3rd record as it has same price.
The same price containing record quantity get merged.
Is it possible to do with query/ filter/ projection? And how can we Effectively get data and merge based on the same price condition i.e. Total quantity is 22019 + 8 having price 43.67.
You can try something like this:
db.sales.aggregate(
[
{
$group:
{
_id: "$price",
quantity: { $sum: "$quantity" },
count: { $sum: 1 }
}
}
]
)
As solution given by #prasad_ in comment section,
- Got sum of quantity according to price group using aggregation in mongoose:
CurrencyModel.aggregate([
{ $group:
{ _id: "$price",
qty: { $sum: "$quantity" }
}
}
]).then(data => {console.log(data);})

MongoDB: Checking if nested array contains sub-array

I have a use case where the database is modelled like this:
name: XYZ
gradeCards: [{
id: 1234, // id of the report card
comments: ['GOOD','NICE','WOW']
}, {
id: 2345,
comments: ['GOOD','NICE TRY']
}]
Now, I have a query that I would like to query the schema as follows:
I would be given a list of ids and values.
For example: the given list is as follows:
[{
id: 1234,
comments: ['GOOD','NICE']
},{
id: 2345,
comments: ['GOOD']
}]
In short the ID should be matching and the comments should be a sub-array of the comments array for that id and also, all the conditions specified in the array should be matched, so it should be an AND condition on all the conditions provided.
I was able to get to this query, but it matches all of the elements in the comments array, I want that id should be exactly matched and comments should be a subarray.
For matching all elements in comments array:
db.getCollection('user').find({arr:{
$all: [{
id:1234,
comments:['GOOD','NICE','WOW']
},{
id:2345,
comments:['GOOD','NICE TRY']
}]
}})
You can try $all with $elemMatch to match on the query conditions.
db.collection.find({
gradeCards: {
$all: [{
"$elemMatch": {
id: 1234,
comments: {
$in: ['GOOD', 'NICE']
}
}
}, {
"$elemMatch": {
id: 2345,
comments: {
$in: ['GOOD']
}
}
}, ]
}
})

Resources