Update a nested array mongoose - mongodb

I've asked this before, but people just want to label this a duplicate instead of answering it.
Looking for a simple answer here. Can the below be done or not?
I have a huge schema that I inherited. It has an array of objects and I am targeting an array of objects nested within that. cssClass is what I'm trying to update. Here is the schema
board: {
"lists": [
{ "id": 12,
"cards": [
{ "id": 123,
"cssClass": "default"
},
{ "id": 124,
"cssClass": "default"
}
]
}
]
}

Related

Search and update in array of objects MongoDB

I have a collection in MongoDB containing search history of a user where each document is stored like:
"_id": "user1"
searchHistory: {
"product1": [
{
"timestamp": 1623482432,
"query": {
"query": "chocolate",
"qty": 2
}
},
{
"timestamp": 1623481234,
"query": {
"query": "lindor",
"qty": 4
}
},
],
"product2": [
{
"timestamp": 1623473622,
"query": {
"query": "table",
"qty": 1
}
},
{
"timestamp": 1623438232,
"query": {
"query": "ike",
"qty": 1
}
},
]
}
Here _id of document acts like a foreign key to the user document in another collection.
I have backend running on nodejs and this function is used to store a new search history in the record.
exports.updateUserSearchCount = function (userId, productId, searchDetails) {
let addToSetData = {}
let key = `searchHistory.${productId}`
addToSetData[key] = { "timestamp": new Date().getTime(), "query": searchDetails }
return client.db("mydb").collection("userSearchHistory").updateOne({ "_id": userId }, { "$addToSet": addToSetData }, { upsert: true }, async (err, res) => {
})
}
Now, I want to get search history of a user based on query only using the db.find().
I want something like this:
db.find({"_id": "user1", "searchHistory.somewildcard.query": "some query"})
I need a wildcard which will replace ".somewildcard." to search in all products searched.
I saw a suggestion that we should store document like:
"_id": "user1"
searchHistory: [
{
"key": "product1",
"value": [
{
"timestamp": 1623482432,
"query": {
"query": "chocolate",
"qty": 2
}
}
]
}
]
However if I store document like this, then adding search history to existing document becomes a tideous and confusing task.
What should I do?
It's always a bad idea to save values are keys, for this exact reason you're facing. It heavily limits querying that field, obviously the trade off is that it makes updates much easier.
I personally recommend you do not save these searches in nested form at all, this will cause you scaling issues quite quickly, assuming these fields are indexed you will start seeing performance issues when the arrays get's too large ( few hundred searches ).
So my personal recommendation is for you to save it in a new collection like so:
{
"user_id": "1",
"key": "product1",
"timestamp": 1623482432,
"query": {
"query": "chocolate",
"qty": 2
}
}
Now querying a specific user or a specific product or even a query substring is all very easily supported by creating some basic indexes. an "update" in this case would just be to insert a new document which is also much faster.
If you still prefer to keep the nested structure, then I recommend you do switch to the recommended structure you posted, as you mentioned updates will become slightly more tedious, but you can still do it quite easily using arrayFilters for updating a specific element or just using $push for adding a new search

How can I update a property within an array of objects based on it's existing value in Mongo?

I have some documents with the following structure...
{
user: "Joe",
lists: [
{ listId: "1234", listName: "dogs" },
{ listId: "5678", listName: "cats" }
]
}
I am trying to prepend a string to each listId field but I am stuck. Amongst other things I have tried...
db.users.updateMany(
{"lists.listId": /^[0-9a-f]{20,}$/},
[{$set:
{"lists.listId.$[]": {"$concat": ["0000", "$lists.listId"]}}
}]
)
But I got the error message: "FieldPath field names may not start with '$'"
Variations on this write results into the appropriate field, but not the results I'm after.
I've bashed my head against the docs for a few hours now but all the references I can find to using the positional operator to reference the value of the field that is being updated use the field name directly, not referenced as a property like I am doing. I've not really messed with pipelines a lot before and I'm finding it all a bit confusing! Someone kindly helped me with a closely related problem yesterday, using $map, and that worked great for a plain array of strings but I haven't had any luck adapting that to an array of objects with string properties. Sorry if this is Mongo 101, the docs are good, but there's a lot of them and I'm not sure which bits are relevant to this.
You can do it like this:
db.collection.users({},
[
{
"$set": {
lists: {
$map: {
input: "$lists",
in: {
$mergeObjects: [
{
"listName": "$$this.listName",
"listId": {
$concat: [
"0000",
"$$this.listId"
]
}
}
]
}
}
}
}
}
],
{
"multi": true
})
Here is the working example: https://mongoplayground.net/p/Q8kUTB6X5JY

which is the best way to implement: two separate array or array in array?

I need an advice!
I have an array of objects with many students data(more then 200 students). So, now, i want to implement lesson for this students, so, every day, i will push an array with data inside every array of students. Later i will work with all lesson data!
So my question is:
a) Is the best way to push array inside of every students array?
b) Or make another array with unique _id, and later filter lesson by students _id?
So, i'm looking for performance and speed...
As I understood from your architecture, a suitable option will be moving lessons to a separate collection and storing lesson._id in students[].lessons. You can reach it by using ref property in your mongoose schema.
Here's the example:
lessons collection data:
[
{
"_id": ObjectId("5a934e000102030405000001"),
"name": "First lesson"
},
{
"_id": ObjectId("5a934e000102030405000002"),
"name": "Second lesson"
}
]
groups collection data:
[
{
"_id": ObjectId("5a934e000102030405000003"),
"name": "Group 1",
"students": [
{
"_id": ObjectId("5a934e000102030405000004"),
"name": "John",
"lessons": [ObjectId("5a934e000102030405000001")]
},
{
"_id": ObjectId("5a934e000102030405000005"),
"name": "James",
"lessons": [ObjectId("5a934e000102030405000001"), ObjectId("5a934e000102030405000002")]
}
]
}
]
But I would also moved every student to separate students collection if it is possible (if you currently have students as array field).

How to search through a list of objects nested inside an array with Spring data MongoDB?

I have got a collection of documents and each documents contains a nested array of objects.
{
"id": "309324739",
"debters": [
{
"user": {
"name": "John Doe",
"internal": true
},
"debt": 1463,
},
{
"user": {
"name": "Alex Tree",
"internal": false
},
"debt": 53443,
},
}
What I'm trying to do is to return find the document by id and then find inside the debters list that has a false flag?
I tried the following query...
Debters findByIdAndDebters_User_InternalIsFalse(#Param("id") String id,);
But I'm getting an error saying that it can find "internal" property. What am I doing wrong and how can I loop through array using this magic mongo repository query?
you need to write a native query for that which is similar to
#Query("{'debters.user.internal':false,'_id':''}")
Debters findByIdAndDebtersUserInternalIsFalse(#Param("id") String id,);

Querying across a collection in MongoDB

I have a document that was stored through the C# driver. It has a property of SortedList. Here's how the document looks in MongoDB:
{
"_id": {
"$oid": "47f1f704c42f56380ac80000"
},
"Things": {
"abc": {
"Color": "blue",
"Shape": "square",
}
"def": {
"Color": "red",
"Shape": "circle"
}
}
}
Here Things is the SortedList, and MyClass has properties of Color and Shape. The problem I'm having is trying to query inside Things.
Specifically, what I want to do is set the color of every MyClass that has a certain color. I can't figure out how to do it since it's indexed on what seems to be effectively a dynamic field name.
The best thing to do might be changing your document structure.
Make the "things" key point to an array of MyClass and take what you were using as they key and make it the name key of the documents in the array.
{
"_id": {
"oid": "47f1f704c42f56380ac80000"
},
"Things": [
{
"Name": "abc",
"Color": "blue",
"Shape": "square",
},
{
"Name": "def",
"Color": "red",
"Shape": "circle"
}
]
}
Once you have the document setup that way you can use the positional operator to update the first MyClass in the Things array that matches your query like this:
db.things.update( { "Things.Color": "blue" }, {$set: {"Things.$.Color": "red"} })
If you want to change all of the matching items, what you actually asked about, you probably need to do a $where query that iterates over the Things in the array.
What's 'abc' and 'def'? Never tried serializing a SortedList. If you use a List of T it will create an embedded array instead of the embedded document you have here.
You can query inside embedded documents and arrays using the dot notation, but here you'd have to do Things.abc.Color or Things.def.Color.