Find just one subdocument of query (two nested arrays) with mongoose - node.js

I'm trying to extract just the right element of an array nested in an array inside a mongoDB document.
This is my example document:
[
{
"name": "name1",
"list1": [
{
"name2": "elem1",
"list2": [
{
"name3": "elem 11"
},
{
"name3": "elem22"
}
]
}
],
{...}
}
]
I want to take just the element of the array with name=name1, name2=eleme1 and name3=elem22.
I want to avoid to use "aggregate", but the idea is the use of findOne().
I tried the following query
this.findOne({ name: "name1", list1: { $elemMatch: { name2: "elem1"}}, 'list1.list2': { $elemMatch: { name3: "elem22"} }})
.select({ list1: { $elemMatch: { name1: "elem1" }})
.select({ "list1.list2": { $elemMatch: { name2: "elem22" }}).
There is an error in the syntax, but If I remove the second select, it selects just an element of the array "list1".
Thanks for your help

Related

Mongoose add/remove items from nested array

I have a document that has a nested array that I need to add/remove items from. Normally you query a nested object by id but in this case a dynamic object _id wont work and I need to update by the nested profile id.
Document
{
title: 'Title',
members: [
{
profile: { id: '123', name: 'Foo' },
items: ['id1', 'id2', 'id3']
}
]
}
Query
My thinking is I need some sort of $elemMatch & $pushAll combo but cant seem to get it working. For removing items from the array I would swap push for pull
await this.repo.findByIdAndUpdate(id, {
members: {
$elemMatch: { 'profile.id': '123' },
$pushAll: {
items: ['xxx', 'yyy'],
},
},
})
You need to use arrayFilters & $push with $each since $pushAll has been deprecated
db.collection.update({},
{
$push: {
"members.$[x].items": {
$each: [
"xxx",
"yyy"
]
}
}
},
{
arrayFilters: [
{
"x.profile.id": "123"
}
]
})
playground

Mongoose conditional update with array of objects

I have a Cart schema in Mongoose including CartItems as an array of objects.
{
_id: string;
items: [
{
product: string;
options: {
size: string;
color: string;
}
quantity: number;
}
]
}
In this case, is it possible to push an item when product and options doesn't match or add quantity when it does?
Here is the code that I have tried which does not work.
Cart.findByIdAndUpdate(_id, {
items: {
$cond: [
{
// Does not work
$elemMatch: {
product,
"options.color": options.color,
"options.size": options.size,
},
},
{
// Should add quantity where it is matched.
},
{
$push: {
product,
productName,
options,
quantity,
},
},
],
},
});
Query
pipeline update requires MongoDB >= 4.2
newProduct is the product you want to add (a js variable)
check if product already exists => add not-exist? field
if not-exists add it in the end
else map to find it and update the quantity
unset the 2 fields newProduct and not-exists
*it does 2 array reads, alternative could be to use $reduce but if you have many products $concatArrays is slow to be inside a reduce, so this is faster solutions (even if reduce would read the array 1 time only)
*you need a method to do update with pipeline, i don't know if mongoose is updated to support it, we are MongoDB 5 so i guess it will be(java is), in worst case you can use updateCommand and call it with runCommand(...)
Test code here
update({"_id" : "1"},
[{"$set":
{"newProduct":
{"product":"p1",
"options":{"size":"s1", "color":"c1"},
"quantity":1}}},
{"$set":
{"not-exists?":
{"$eq":
[{"$filter":
{"input":"$items",
"cond":
{"$and":
[{"$eq":["$$this.product", "$newProduct.product"]},
{"$eq":["$$this.options.size", "$newProduct.options.size"]},
{"$eq":["$$this.options.color", "$newProduct.options.color"]}]}}},
[]]}}},
{"$set":
{"items":
{"$cond":
["$not-exists?", {"$concatArrays":["$items", ["$newProduct"]]},
{"$map":
{"input":"$items",
"in":
{"$cond":
[{"$and":
[{"$eq":["$$this.product", "$newProduct.product"]},
{"$eq":["$$this.options.size", "$newProduct.options.size"]},
{"$eq":["$$this.options.color", "$newProduct.options.color"]}]},
{"$mergeObjects":
["$$this", {"quantity":{"$add":["$$this.quantity", 1]}}]},
"$$this"]}}}]}}},
{"$unset":["not-exists?", "newProduct"]}])
Query2
if you don't want to use update pipeline you can do it with more queries
Check if exists
db.collection.find({
"_id" : "1",
"items": {
"$elemMatch": {
"product": "p1",
"options": {
"size": "s1",
"color": "c1"
}
}
}
})
If not exists
db.collection.update({
"_id": "1"
},
{
"$push": {
"items": "NEWITEM" //put here the new object
}
})
else If exists
db.collection.update({"_id" : "1"},
{
"$inc": {
"items.$[i].quantity": 1
}
},
{
arrayFilters: [
{
"i.product": "p1",
"i.options.size": "s1",
"i.options.color": "c1"
}
]
})

Mongoose Aggregate match if an Array contains any value of another Array

I am trying to match by comparing the values inside the values of another array, and return it if any one of the values in the 1st array match any one value of the 2nd array. I have a user profile like this
{
"user": "bob"
"hobbies": [jogging]
},
{
"user": "bill"
"hobbies": [reading, drawing]
},
{
"user": "thomas"
"hobbies": [reading, cooking]
}
{
"user": "susan"
"hobbies": [coding, piano]
}
My mongoose search query as an example is this array [coding, reading] (but could include any hobby value) and i would like to have this as an output:
{
"user": "bill"
"hobbies": [reading, drawing]
},
{
"user": "thomas"
"hobbies": [reading, cooking]
}
{
"user": "susan"
"hobbies": [coding, piano]
}
I tried:
{"$match": {"$expr": {"$in": [searchArray.toString(), "$hobbies"]}}}
but this only works aslong as the search array has only one value in it.
const searchArray = ["reading", "coding"];
const orArray = searchArray.map((seachValue) => {
return {
hobies: searchValue,
}
});
collection.aggregate([{
$match: { $or: orArray }
}])
The query:
db.collection.aggregate([
{
$match: {
"$or": [
{
hobbies: "reading"
},
{
hobbies: "coding"
}
]
}
}
])
Or you can use another way, no need to handle arr:
db.collection.aggregate([
{
$match: {
hobbies: {
$in: [
"reading",
"coding"
]
}
}
}
])
Run here

How to query an object with a specific key/value pair in an array with Mongodb?

This is my data structure:
{
studentName: 'zzz',
teachers: [
{
teacherName: 'xxx',
feedbacks: []
}, {
teacherName: 'yyy',
feedbacks: []
}
]
}
Now I am trying to code a query to add an 'feedback' object to the 'feedbacks' array that belongs to the teacher named 'yyy'.
await collection.updateOne({
studentName: 'zzz',
teachers: {
$elemMatch: {
teacherName: {
$eq: 'yyy'
}
}
}
}, {
$push: {
'teachers.$.feedbacks': {}
}
})
The query part is faulty somehow. If I change '$' to 0 or 1, then the code works finally. Otherwise, the object cannot be pushed.
This update works fine, adds the string element "Nice work" to the teachers.feedbacks nested array.
db.test.updateOne(
{
studentName: "zzz",
"teachers.teacherName": "yyy"
},
{ $push: { "teachers.$.feedbacks" : "Nice work" } }
)
The $elemMatch syntax is not required, as the query condition for the array elements is for a single field.
The updated document:
{
"studentName" : "zzz",
"teachers" : [
{
"teacherName" : "xxx",
"feedbacks" : [ ]
},
{
"teacherName" : "yyy",
"feedbacks" : [
"Nice work"
]
}
]
}

Mongoose - get single field from array of subdocuments [duplicate]

This question already has answers here:
Getting a list of unique embedded/nested objects in a MongoDB document
(4 answers)
Closed 5 years ago.
I have a document, Model like this:
{
name: String,
user: String,
components: [{
alias: String,
name: String,
...etc...
}]
}
I'd like to formulate a reponse that just returns an array of component.alias for a given document.
E.g, if I have:
{
name: "doc1",
components: [{alias: "alias1", ...}, {alias: "alias2", ...}]
}
I'd like to just end up with ["alias1", "alias2"]
So far I have:
Model.findById(
modelId,
{ "components.alias": 1, _id: 0 },
(err, docs) => {
console.log(docs)
}
);
But this gives me:
{
"components": [
{
"alias": "alias1"
}
]
}
Any ideas?
Use aggregate for that, MongoDB query for that.
db.getCollection('Model').aggregate([
{ "$unwind": "$components" },
{ "$group": { "_id": "$components.alias" }}
]).map(function(el) { return el._id })
Node.js code
Model.aggregate([
{ "$match": { "name": "doc1" } }, // any condition
{ "$unwind": "$components" },
{ "$group": { "_id": "$name", components: { $push: "$components.alias" } } }
]).then((data)=>console.log(data))
Output:
{
"_id": "doc1",
"emails": [
"alias1",
"alias2"
]
}

Resources