Nested object text search in mongoDB - javascript

I am not sure how I am going to solve this problem:
I want to search in a mongoDB collection and return only the nested objects that fits the search query (using text search on all of the fields).
All documents in the collection have this format:
{
arr: [
{
_id: 1,
name: 'Random',
description: 'Hello world'
},
{
_id: 2,
name: 'World',
description: 'This is a random description'
},
{
_id: 3,
name: 'Random',
description: 'Hi'
}
]
}
In this case, if my search query is 'world', then this should be the result:
[
{
_id: 1,
name: 'Random',
description: 'Hello world'
},
{
_id: 2,
name: 'World',
description: 'This is a random description'
},
//... objects from other documents in the collection that fits the query
]
If this is not possible in mongoDB, are there any JavaScript libraries that can achieve this? Would greatly appreciate the help!

With the aggregation framework it could look like so
db.getCollection('yourCollection').aggregate([
{
$unwind: '$arr'
},
{
$match: {
$or: [
{ 'arr.name': /world/i },
{ 'arr.description': /world/i }
]
}
},
{
$project: {
_id: '$arr._id',
name: '$arr.name',
description: '$arr.description'
}
}
])
which will result in the following output for your example data:
{
"_id" : 1,
"name" : "Random",
"description" : "Hello world"
}
{
"_id" : 2,
"name" : "World",
"description" : "This is a random description"
}
If you have the need for a single array with the resulting documents as shown in your question, you can simply chain a toArray() call at the end of the pipeline - keep in mind though that this may cause increased memory consumption in case of large result sets as pointed out by SSDMS in the comments.

Related

MongoDB group $ROOT (collection) document items as key value pairs in the new field

consider the following array of documents returned from the aggregation:
[
{
id: '#abcdefg123',
title: 'Doc 1'
...
},
{
id: '#abcdefg124',
title: 'Doc 2'
...
},
{
id: '#abcdefg125',
title: 'Doc 3'
...
}
]
Each document contains like 20 fields...
I would like to get the following output:
{
edges: [
{
node: {
id: '#abcdefg123',
title: 'Doc 1',
...
}
cursor: '#abcdefg123'
},
{
node: {
id: '#abcdefg124',
title: 'Doc 2',
...
}
cursor: '#abcdefg124'
},
{
node: {
id: '#abcdefg125',
title: 'Doc 3',
...
}
cursor: '#abcdefg125'
}
]
}
I've tried to $group a new field edges with $push as k, v pair of '$ROOT' but with no such desired result.
And I've tried to use $map on something like '$$ROOT' as input and trying to return object
{
node: '$item',
cursor: '$item._id'
}
Of course with not luck either.
I don't want to iterate over the results and populate the right object shape of data after the aggregation. Considering the performance would drop.
I would prefer to achieve the desired result inside of aggregation pipeline.
Thanks in advance.
db.collection.aggregate([
{
$project: {
"node": "$$ROOT",
"cursor": "$id"
}
},
{
$group: {
"_id": null,
"edges": {
$push: "$$ROOT"
}
}
}
]);
Mongo Playground

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

Mongoose - renaming object key within array

I have this one schema
{
_id: "123456",
id: "123",
inventory: [
{
id: "foo",
count: 0
},
{
id: "bar",
count: 3
}
]
}
I wanted every "count" keys in the inventory array to be "price" which will look like this at the end:
{
_id: "123456",
id: "123",
inventory: [
{
id: "foo",
price: 0
},
{
id: "bar",
price: 3
}
]
}
And I've tried this
Model.updateOne({ id: "123" }, { $unset: { inventory: [{ count: 1 }] } } )
But it seems to be deleting the "inventory" field itself
The first thing here is to try to use $rename but how the docs explain:
$rename does not work if these fields are in array elements.
So is necessary to look for another method. So you can use this update with aggregation query:
This query uses mainly $map, $arrayToObject and $objectToArray. The trick here is:
Create a new field called inventory (overwrite existing one)
Iterate over every value of the array with $map, and then for each object in the array use $objectToArray to create an array and also iterate over that second array using again $map.
Into this second iteration create fields k and v. Field v will be the same (you don't want to change the value, only the key). And for field k you have to change only the one whose match with your condition, i.e. only change from count to price. If this condition is not matched then the key remain.
db.collection.update({},
[
{
$set: {
inventory: {
$map: {
input: "$inventory",
in: {
$arrayToObject: {
$map: {
input: {$objectToArray: "$$this"},
in: {
k: {
$cond: [
{
$eq: ["$$this.k","count"]
},
"price",
"$$this.k"
]
},
v: "$$this.v"
}
}
}
}
}
}
}
}
])
Example here

How to query an object inside an array by multiple value in mongoose (javascript)?

I have an object and the server will receive a user_id and a part_id. I need to get the certain user, then filter the parts by the provided part ID and get the price.
{
_id: 6086b8eec1f5325278846983,
user_id: '13',
car_name: 'Car name 1',
created_at: 2008-11-25T00:46:52.000Z,
parts: [
{
_id: 6086ee212681320190c3c8e0,
part_id: 'P456',
part_name: 'Part name 1',
image: 'image url',
stats: {
price: 10,
}
},
{
_id: 6087e7795e2ca925fc6ead27,
part_id: 'P905',
part_name: 'Part name 2',
image: 'image url',
stats: {
price: 15,
}
}
]
}
I tried to run the following, but ignores the part_id filter and returns every parts in the array.
Custumers.findOne({'user_id': '13', 'parts.part_id': 'P456'})
Also tried with aggregate but still no luck.
Customers.aggregate([
{ $match: { 'user_id': '13'}}
]).unwind('parts')
I checked the mongoose documentation but cannot wrap my head around it. Please let me know what I am missing.
Mongoose version: 5.12.4
Option - 1
This will work if you've only 1 matching parts
$ (projection)
The $ operator projects the first matching array element from each document in a collection based on some condition from the query statement.
Demo - https://mongoplayground.net/p/tgFL01fK3Te
db.collection.find(
{ "user_id": "13", "parts.part_id": "P456" },
{ "parts.$": 1, car_name: 1 } // add fields you need to projection
)
Option -2
$unwind
Demo - https://mongoplayground.net/p/_PeP0WHVpJH
db.collection.aggregate([
{
$match: {
"user_id": "13",
"parts.part_id": "P456"
}
},
{
$unwind: "$parts" // break into individual documents
},
{
$match: {
"parts.part_id": "P456"
}
}
])

Mongoose & getting average of ratings in a collection?

I have a NodeJS based application using Mongoose. I am wanting to create a response where there values are an average of ratings provided by people who have responded to a questionnaire, for each question asked.
My Schema looks as follows:
const questionnaireResultSchema = new Schema({
user: { type: ObjectId, ref: 'User' },
questionnaire: { type: ObjectId, ref: 'Questionnaire' },
rating: [{
id: Number,
question: String,
value: Number
}]
},{
timestamps: true
}).index({user: 1, questionnaire: 1}, {unique: true});
I have looked at the Mongoose aggregator operator, but I am not sure how I would apply it to my case. Pseudo code would look as follows:
Find all questionnaire results for questionnaire of id xyz
Provide a result where the ratings for each questionnaire result has be averaged
For example the response would look as follows:
[{
id: 1,
question: 'How strongly do you feel about candidate A?',
value: 12 // averaged value
},{
id: 2,
question: 'How strongly do you think we should change the sky color to green?',
value: 31 // averaged value
},{
id: 3,
question: 'How strongly do you think your answers count?',
value: 20 // averaged value
}]
I have tried:
QuestionnaireResultSchema.aggregate([{
$match: {
questionnaire: questionnaire
}},
{$project: {
scores: { $avg: '$scores'}
}}
]);
This just provides the JSON:
[{
"_id": "57bbd4b495407f6145b3ba9f",
"scores": null
}]
A sample document in a collection would like:
{
user: ObjectId("57bca30536e376c653f439bb")
questionnaire: ObjectId("37bca0feedb0bc470353ab")
scores: [{
id: 1,
question: 'How strongly do you feel about candidate A?',
value: 3
},{
id: 2,
question: 'How strongly do you think we should change the sky color to green?',
value: 4 // averaged value
},{
id: 3,
question: 'How strongly do you think your answers count?',
value: 5 // averaged value
}]
}
While I could calculate the averages myself, if Mongoose provides the functionality, I would rather leverage that.
Any help would be appreciated.
This should work for your aggregation pipeline:
[
{
$match: {
questionnaire: ObjectId("37bca0feedb0bc470353ab")
}
},
{
$unwind: "$rating"
},
{
$group:{
_id:{
"rating_id": "$rating.id",
"question": "$rating.question",
},
avg_rating: {$avg:"rating.value"}
}
},
{
$project:{
"id": "$_id.rating_id",
"question": "$_id.question",
"avg_rating": "$avg_rating"
}
}
]
Although your sample doc has "scores" instead of "rating" in which case you'd use:
[
{
$match: {
questionnaire: ObjectId("237bca0feedb0bc470353aba")
}
},
{
$unwind: "$scores"
},
{
$group:{
_id:{
"rating_id": "$scores.id",
"question": "$scores.question",
},
avg_rating: {$avg:"scores.value"}
}
},
{
$project:{
"id": "$_id.rating_id",
"question": "$_id.question",
"avg_rating": "$avg_rating"
}
}
]
Also, some of the ObjectId's that you are using are not valid. I'm assuming those are just stubbed.

Categories

Resources