How to select only an element of an array within a Mongoose.find - javascript

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.

Related

MongoDB Aggregation - match documents with array of objects, by another array of objects filter

I have documents that consist of an array of objects, and each object in this array consists of another array of objects.
For simplicity, irrelevant fields of the documents were omitted.
It looks like this (2 documents):
{
title: 'abc',
parts: [
{
part: "verse",
progressions: [
{
progression: "62a4a87da7fdbdabf787e47f",
key: "Ab",
_id: "62b5aaa0c9e9fe8a7d7240d3"
},
{
progression: "62adf477ed11cbbe156d5769",
key: "C",
_id: "62b5aaa0c9e9fe8a7d7240d3"
},
],
_id: "62b5aaa0c9e9fe8a7d7240d2"
},
{
part: "chorus",
progressions: [
{
progression: "62a4a51b4693c43dce9be09c",
key: "E",
_id: "62b5aaa0c9e9fe8a7d7240d9"
}
],
_id: "62b5aaa0c9e9fe8a7d7240d8"
}
],
}
{
title: 'def',
parts: [
{
part: "verse",
progressions: [
{
progression: "33a4a87da7fopvvbf787erwe",
key: "E",
_id: "62b5aaa0c9e9fe8a7d7240d3"
},
{
progression: "98opf477ewfscbbe156d5442",
key: "Bb",
_id: "62b5aaa0c9e9fe8a7d7240d3"
},
],
_id: "12r3aaa0c4r5me8a7d72oi8u"
},
{
part: "bridge",
progressions: [
{
progression: "62a4a51b4693c43dce9be09c",
key: "C#",
_id: "62b5aaa0c9e9fe8a7d7240d9"
}
],
_id: "62b5aaa0rwfvse8a7d7240d8"
}
],
}
The parameters that the client sends with a request are an array of objects:
[
{ part: 'verse', progressions: ['62a4a87da7fdbdabf787e47f', '62a4a51b4693c43dce9be09c'] },
{ part: 'chorus', progressions: ['62adf477ed11cbbe156d5769'] }
]
I want to retrieve, through mongodb aggregation, the documents that at least one of objects in the input array above is matching them:
In this example, documents that have in their parts array field, an object that has the value 'verse' in the part property and one of the progressions id's ['62a4a87da7fdbdabf787e47f', '62a4a51b4693c43dce9be09c'] in the progression property in one of the objects in the progressions property, or documents that have in their parts array field, an object that has the value 'chorus' in the part property and one of the progressions id's ['62adf477ed11cbbe156d5769'] in the progression property in one of the objects in the progressions property.
In this example, the matching document is the first one (with the title 'abc'), but in actual use, there might be many matching documents.
I tried to create an aggregation pipeline myself (using the mongoose 'aggregate' method):
// parsedProgressions = [
// { part: 'verse', progressions: ['62a4a87da7fdbdabf787e47f', '62a4a51b4693c43dce9be09c'] },
// { part: 'chorus', progressions: ['62adf477ed11cbbe156d5769'] }
// ]
songs.aggregate([
{
$addFields: {
"tempMapResults": {
$map: {
input: parsedProgressions,
as: "parsedProgression",
in: {
$cond: {
if: { parts: { $elemMatch: { part: "$$parsedProgression.part", "progressions.progression": mongoose.Types.ObjectId("$$parsedProgression.progression") } } },
then: true, else: false
}
}
}
}
}
},
{
$addFields: {
"isMatched": { $anyElementTrue: ["$tempMapResults"] }
}
},
{ $match: { isMatched: true } },
{ $project: { title: 1, "parts.part": 1, "parts.progressions.progression": 1 } }
]);
But it didn't work - as I understand it, because the $elemMatch can be used only in the $match stage.
Anyway, I guess I overcomplicated the aggregation pipeline, so I will be glad if you can fix my aggregation pipeline/offer a better working one.
This is not a simple case as these are both nested arrays and we need to match both the part and the progressions, which are not on the same level
One option looks complicated a bit, but keeps your data small:
In order to make things easier, $set a new array field called matchCond which includes an array called progs containing the parts.progressions. To each sub-object inside it insert the matching progressions input array. We do need to be careful here and handle the case where there is no matching progressions input arrayprogressions input array, as this is the case for the "bridge" part on the second document.
Now we just need to check if for any of these progs items, the progression field is matching one option in input array. This is done using $filter, and $rediceing the number of results.
Just match document which have results and format the answer
db.collection.aggregate([
{
$set: {
matchCond: {
$map: {
input: "$parts",
as: "parts",
in: {progs: {
$map: {
input: "$$parts.progressions",
in: {$mergeObjects: [
"$$this",
{input: {progressions: []}},
{input: {$first: {
$filter: {
input: inputData,
as: "inputPart",
cond: {$eq: ["$$inputPart.part", "$$parts.part"]}
}
}}}
]}
}
}}
}
}
}
},
{$set: {
matchCond: {
$reduce: {
input: "$matchCond",
initialValue: 0,
in: {$add: [
"$$value",
{$size: {
$filter: {
input: "$$this.progs",
as: "part",
cond: {$in: ["$$part.progression", "$$part.input.progressions"]}
}
}
}
]
}
}
}
}
},
{$match: {matchCond: {$gt: 0}}},
{$project: {title: 1, parts: 1}}
])
See how it works on the playground example
Another option is to use $unwind, which looks simple, but will duplicate your data, thus, likely to be slower:
db.collection.aggregate([
{$addFields: {inputData: inputData, cond: "$parts"}},
{$unwind: "$cond"},
{$unwind: "$cond.progressions"},
{$unwind: "$inputData"},
{$match: {
$expr: {
$and: [
{$eq: ["$cond.part", "$inputData.part"]},
{$in: ["$cond.progressions.progression", "$inputData.progressions"]}
]
}
}
},
{$project: {title: 1, parts: 1}}
])
See how it works on the playground example - unwind
There are several options between these two...

Edit multiple objects in array using mongoose (MongoDB)

So I tried several ways, but I can't, I can modify several objects with the same key but I can't modify any with different keys, if anyone can help me is quite a complex problem
{
id: 123,
"infos": [
{ name: 'Joe', value: 'Disabled', id: 0 },
{ name: 'Adam', value: 'Enabled', id: 0 }
]
};
In my database I have a collection with an array and several objects inside which gives this.
I want to modify these objects, filter by their name and modify the value.
To give you a better example, my site returns me an object with the new data, and I want to modify the database object with the new object, without clearing the array, the name key never changes.
const object = [
{ name: 'Joe', value: 'Hey', id: 1 },
{ name: 'Adam', value: 'None', id: 1 }
];
for(const obj in object) {
Schema.findOneAndUpdate({ id: 123 }, {
$set: {
[`infos.${obj}.value`]: "Test"
}
})
}
This code works but it is not optimized, it makes several requests, I would like to do everything in one request, and also it doesn't update the id, only the value.
If anyone can help me that would be great, I've looked everywhere and can't find anything
My schema structure
new Schema({
id: { "type": String, "required": true, "unique": true },
infos: []
})
I use the $addToSet method to insert objects into the infos array
Try This :
db.collection.update({
id: 123,
},
{
$set: {
"infos.$[x].value": "Value",
"infos.$[x].name": "User"
}
},
{
arrayFilters: [
{
"x.id": {
$in: [
1
]
}
},
],
multi: true
})
The all positional $[] operator acts as a placeholder for all elements in the array field.
In $in you can use dynamic array of id.
Ex :
const ids = [1,2,..n]
db.collection.update(
//Same code as it is...
{
arrayFilters: [
{
"x.id": {
$in: ids
}
},
],
multi: true
})
MongoPlayGround Link : https://mongoplayground.net/p/Tuz831lkPqk
Maybe you look for something like this:
db.collection.update({},
{
$set: {
"infos.$[x].value": "test1",
"infos.$[x].id": 10,
"infos.$[y].value": "test2",
"infos.$[y].id": 20
}
},
{
arrayFilters: [
{
"x.name": "Adam"
},
{
"y.name": "Joe"
}
],
multi: true
})
Explained:
You define arrayFilters for all names in objects you have and update the values & id in all documents ...
playground

Mongo aggregate – return zero count

I need your help in aggregate functions in Mongo.
I have such aggregation:
const likes = await this.aggregate([
{
$match: { post: postId },
},
{
$group: {
_id: '$likeType',
count: { $sum: 1 },
},
},
]);
It collects all likes/dislikes for a post and returns this:
[ { _id: 'pos', count: 40 }, { _id: 'neg', count: 3 } ]
I faced a problem: if there is only one type of likes (for example only 'pos'), it returns this:
[ { _id: 'pos', count: 40 } ]
But I need this array to show zero value too:
[ { _id: 'pos', count: 40 }, { _id: 'neg', count: 0 } ]
Is there any way to set default values for all types of _ids?
I understand that it can't find any 'neg's and it can't return them. So I want to set defaults to let the system know, that there are only two types: 'pos' and 'neg'.
Are there any solutions for such cases?
Thanks!
My suggestion is:
Get distinct Ids: https://docs.mongodb.com/manual/reference/method/db.collection.distinct/
Do your search with your query param.
Filter distinct Ids which is not your query param. Append default values to result.

How to find specific nested objects without knowing the parent key in mongodb

I'm using javascript and the mongoose module.
I have an object like this
my_object = {
ShopName: String,
employees: [String],
info: {
NameEmployee_1: {
age: String,
work: String,
city: String,
},
NameEmployee_2: {
age: String,
work: String,
city: String,
}
}
}
and i want
find all the emoplyees that have a specific age but without knowing the name of the emplyee, is there a way for do this?
I know that you for example i can so something like this
db.collection.find({'info.Max': {$exists: true}})
for find all the Shops that have atleast one employee with name Max
but what if i want all the Shops that have atleast one emplyee that has age 33
db.collection.find({'info.<name>.age': '33'}) ?
You can utilize the $objectToArray (mongoDB 3.4.4 and up), $filter and $project and get something like this:
db.collection.aggregate([
{
$project: {
obj: {
$objectToArray: "$info"
}
}
},
{
$project: {
_id: 0,
obj: {
$filter: {
input: "$obj",
as: "item",
cond: {
$eq: [
"$$item.v.city",
"NY"
]
}
}
}
}
},
{
$project: {
info: {
$arrayToObject: "$obj"
}
}
},
])
You can see it working here
The idea is to break the object to array, filter it and then convert that array back to object.
I filtered on city but I am sure you get the idea.

How to query documents by a condition on the subdocument with the latest date [duplicate]

This question already has an answer here:
Query on Last Array Value
(1 answer)
Closed 4 years ago.
I'm trying to figure out the best way to query documents based on a criteria on the latest subdocument.
So my data might look like this:
[{
_id: '59bb31efae69726bd5fc9391',
name: 'Something',
terms: [
{
_id: '58e54f5aad59a6000cdcd590',
begDate: '2017-06-13T07:00:00.000Z',
endDate: '2018-01-01T07:59:59.999Z'
},
{
_id: '59bb32765e651d28909ed706',
begDate: '2018-01-01T08:00:00.000Z',
endDate: '2019-01-01T07:59:59.999Z'
}
]
}, {
_id: '59f20ddeef426f6bca3abbf1',
name: 'Something',
terms: [
{
_id: '59f20e35c8257b5b0f22d2a6',
begDate: '2018-06-13T07:00:00.000Z',
endDate: '2019-01-01T07:59:59.999Z'
},
{
_id: '59f20e9394c8108d9db33bf9',
begDate: '2019-01-01T08:00:00.000Z',
endDate: '2020-01-01T07:59:59.999Z'
}
]
}]
What I want is to get all documents whose last term's endDate is 2019-01-01T07:59:59.999Z This could be done by either getting the last term in an array, or more reliably sorting terms, and then grabbing the last one.
I can see how I could do this with $where but I know if I can find another way it would be more performant.
I also want to add, whatever I do here would accompany other query parameters. For example:
{
_id: {
'$in': [
ObjectId("591e5e37abddad14afe1b272"),
ObjectId("591e5e37abddad14afe1b123")
]
}
}
UPDATE:
As noted, this question has a duplicate (which was hard for me to find as the question referenced is difficult to understand). That being said, I'm not only looking for the last in an array but also the most recent (I agree that's not clear in the body of the question). I'm not arguing against the duplicate question reference, but for the sake of making this easier for future readers, you'll find in the accepted answer a clean solution for mongo 3.6+ as well as a reference to another question in the comments which should help if you want to query by date in subdocuments.
Using $expr to perform a 'complex' match and $let to have an intermediate variable storing the last element of arrays found with "$arrayElemAt": [ "$terms", -1 ] in order to compare it to the date in question:
db.collection.find({
$expr: {
$let: {
vars: { "last": { $arrayElemAt: [ "$terms", -1 ] } },
in: { $eq: [ "$$last.endDate", "2019-01-01T07:59:59.999Z" ] }
}
}
})
which returns with the input you provided the first record.
And, as per your requirements, in order not to exclude the possibility to add additional filters, you can add them using $and:
db.collection.find({
$and: [
{ $expr: { $let: {
vars: { "last": { $arrayElemAt: [ "$terms", -1 ] } },
in: { $eq: [ "$$last.endDate", "2019-01-01T07:59:59.999Z" ] }
}}},
{ "_id": { $ne: "sss" } } // actually whatever additional filter
]
})
Exact same thing can be achieved with an aggregate pipeline, if you wish to perform additional stages with your matching documents:
db.collection.aggregate([
{ $match: {
$and: [
{ $expr: { $let: {
vars: { "last": { $arrayElemAt: [ "$terms", -1 ] } },
in: { $eq: [ "$$last.endDate", "2019-01-01T07:59:59.999Z" ] }
}}},
{ "_id": { $ne: "sss" } }
]
}},
{ ... }
])

Categories

Resources