I'm querying a Collection that has 350k+ Documents. I'm querying a field using the IN clause where the IN is an array of 27k+ fields.
This actually seems to return rather fast in Mongoose. However, some of the matches of each item in the IN can have multiple Documents associated with them. I'd like to only have 1 Document returned per each match (sorted by another field). Is this possible?
Example
Let's say I have a Collection of Fruit.
[
{type:'apple', price:10},{type:'apple', price:5},{type:'apple', price:3},
{type:'orange', price:2},
{type:'pear', price:12}, {type:'pear', price:2}
]
So, currently I have
const types = ['apple', 'orange', 'pear'];
//Will return full example above
//Returns 12k Docs in real app but bc multiple Docs are returned per item in IN
Fruit.find({type: { $in: types }}, (err, results) => {
if (err) return console.error(err);
console.log(results);
});
I'd like to just have
[
{type:'apple', price:10}
{type:'orange', price:2},
{type:'pear', price:12}
]
How can I adjust my query to do something like this? Thanks!
returned. So instead of all documents matching the type - I just get only 1 with the highest price.
You need to $group by type and use $max to get highest prices:
db.collection.aggregate([
{
$match: { type: { $in: ['apple', 'orange', 'pear'] } }
},
{
$group: {
_id: "$type",
price: { $max: "$price" }
}
},
{
$project: {
type: "$_id",
_id: 0,
price: 1
}
}
])
Mongo Playground
Related
I'm trying to execute a query that returns all the documents that match based on query parameters.
I have the following schema:
_id: ObjectId('631b875491b16c38eecfa4e9')
brandName: "Nick"
categories: Array
products: Array
0: Object
productName: "Vans Shoes dsds Old Skool"
description: "amazing Shoes."
categoryId: ObjectId('62f3eaff3ded19dcce71081e')
price: 240
numberOfBuyers: 0
_id: ObjectId(631b875491b16c38eecfa4ec)
1: Object
2: Object
3: Object
__v: 0
The following query should give me all the documents that match, but it returns only the first document:
const products = await Brand.find(
{
_id: brandId
},
{
products: {
$elemMatch: {
categoryId: categoryId,
price: {
$gte: minPrice,
$lte: maxPrice
}
}
}
})
What is wrong?
You are querying on "Brand" documents. This means your query tells Mongoose: if one of the products is matching (categoryId and price), return the (whole) Brand document.
In order to retrieve only specific elements of this array, you should include your $elemMatch object in the projection step of your find call:
const products = await Brand.find({
_id: brandId
}, {
//include other properties you want to include in your output document
products: {
$elemMatch: {
categoryId: "62f3eaff3ded19dcce71081e",
price: 240
}
}
}
})
Update after comment
Your products array will only contain the first element that was matched. This is intended behaviour (as described here: cs/manual/reference/operator/projection/elemMatch/):
Definition
$elemMatch
The
$elemMatch
operator limits the contents of an field from the query results to contain only the first element matching the
$elemMatch
condition.
In order to get several results you should probably use an aggregation pipeline using $unwind and $group.
In MongoDB shell version v4.4.6
the following code works perfectly.
db['pri-msgs'].findOne({tag:'aaa&%qqq'},{msgs:{$slice:-2}})
But in nodeJs mongoDB the following code doesn't work.
db.collection('pri-msgs').findOne({
tag: 'aaa&%qqq'
}, {
msgs: {
slice: -2
}
})
My document-->
{"_id":{"$oid":"60c4730fadf6891850db90f9"},"tag":"aaa&%qqq","msgs":[{"msg":"abc","sender":0,"mID":"ctYAR5FDa","time":1},{"msg":"bcd","sender":0,"mID":"gCjgPf85z","time":2},{"msg":"def","sender":0,"mID":"lAhc4yLr6","time":3},{"msg":"efg","sender":0,"mID":"XcBLC2rGf","time":4,"edited":true},{"msg":"fgh","sender":0,"mID":"9RWVcEOlD","time":5},{"msg":"hij","sender":0,"mID":"TJXVTuWrR","time":6},{"msg":"jkl","sender":0,"mID":"HxUuzwrYN","time":7},{"msg":"klm","sender":0,"mID":"jXEOhARC2","time":8},{"msg":"mno","sender":0,"mID":"B8sVt4kCy","time":9}]}
Actually what I'm trying to do is Get last 2 itmes from msgs Array where time is greater than 'n'. Here 'n' is a number.
You can use aggregation-pipeline to get the results you are looking for. The steps are the following.
Match the documents you want by tag.
Unwind the msgs array.
Sort descending by msgs.time.
Limit first 2 elements.
Match the time you are looking for using a range query.
Group the documents back by _id.
Your query should look something like this:
db['pri-msgs'].aggregate([
{ $match: { tag: 'aaa&%qqq' } },
{ $unwind: '$msgs' },
{
$sort: {
'msgs.time': -1 //DESC
}
},
{ $limit: 2 },
{
$match: {
'msgs.time': {
$gt: 2 //n
}
}
},
{
$group: {
_id: '$_id',
tag: { $first: '$tag' },
msgs: {
$push: { msg: '$msgs.msg', sender: '$msgs.sender', mID: '$msgs.mID', time: '$msgs.time' }
}
}
}
]);
I have a collection named group on the mongoDb database . The collection includes 2 or more object and each object contains an array named members . what is the best possible and efficient way to concate and get all the members data from the database. what would be the mongoDB query ?
my collection looks like
[
{
id: ObjcetId("15215252"),
groupName: "travellers of Bangladesh",
members: ["1","2","3"]
},
{
id: ObjcetId("32643724362"),
groupName: "People from Bangladesh",
members: ["4","5","6"]
}
]
and i Want just this exact data
members: ["1","2","3","4","5","6"]
Use can use aggregations
$unwind to deconstruct the array
$group to reconstruct the array
Here is the code
db.collection.aggregate([
{ $unwind: "$members" },
{
$group: {
_id: null,
members: { $push: "$members" }
}
}
])
Working Mongo playground
I'm using collection espData which contains documents of the following type:
{
mac: String,
hash: String,
rssi: Number
}
Using Mongoose I want to select those lines with same mac and same hash and if the count(*) is equal to 2 then the line is selected. Then I want to perform an aggregation which return mac and the respectively average of the rssi.
I made this piece of code but it doesn't work.
EspDataModel.aggregate([
{ $group: {
_id:{
mac:"$mac",
hash:"$hash",
},
count: {$sum:1}
}},
{$match: {count : 2}
}
], function(err,result){
result.map(function(doc){
EspDataModel.aggregate([
{
$match: { mac: doc._id.mac, hash:doc._id.hash }
},
{
$group:{
mac:"$_id.mac",
averageRSSI: {$avg: "$rssi"}
}
}], function(err,result){
console.log(result)
})
})
})
The first aggregate works and effectively select those lines I'm interested in but is there a proper way to match mac and hash of the original collection and compute the average?
Thank you for your help!
Let's say I've got these values in database:
{
name: '1',
values: [{
subname: 'awesome'
}, {
surname: 'cool'
}]
}
how could I filter the array with only the value I'm interested in?
I would like to get as result of my find:
{
name: '1',
values: [{
subname: 'awesome'
}]
}
I thought maybe there is a possibility with select? Something like
MyCollection.find({name: '1'}).select(BLACK_MAGIC);
Where BLACK_MAGIC filters my array with the values I'm interested in, in this example values.subname = 'awesome'
Thx in advance for any ideas
Side note: I'm interesting to solve this with Mongoose queries and functions, not a solution with a post javascript on the resulting array
I think you could use aggregation for this.
You would $unwind so that each values object is in a separate document.
Then filter the results with $match.
MyCollection.aggregate([
{
$unwind: '$values'
},
{
$match: {
'values.subname': 'awesome'
}
},
// EDIT
{
$group: {
_id: '$_id',
name: {
$first: "$name"
},
values: {
$push: { subname: "$values.subname" }
}
}
}
], function (err, results) {
});
If it works, you are little bit closer. The only thing is that values is an object, not an array of one object. You could probably use $group with $first to get desired result.