MongoDb update with ElemMatch - javascript

I have a collection that has document structure like following:
Mongo PlayGround
{
"basicDetails": {
"id": "1",
"name": "xyz"
},
"tasks": [{
"id": "10",
"name": "task10",
"subtasks": [{
"id": "120",
"name": "subTask120",
"description": "ABC"
}]
}]
}
As you can see, each document has basicDetails object and a tasks array. Each task contains some properties of its own and a subtasks array.
I want to update subtasks's description from ABC to XYZ
where root level id is 1, task'id is 10 and subTasks.id =120
How do I do so?
I know I could find correct document via:
db.collection.find({
"basicDetails.id": "1",
"tasks": {
"$elemMatch": {
"id": "10",
"subtasks": {
"$elemMatch": {
"id": "120"
}
}
}
}
})
But how do I update it? I want to update only one single property of a single subtasks i.e description

To update nested arrays, the filtered positional operator $[identifier] identifies the array elements that match the arrayFilters conditions for an update operation.
Try the following query to $set in nested array:
db.collection.updateOne({
"basicDetails.id": "1"
},
{
"$set": {
"tasks.$[tasks].subtasks.$[subtasks].description": "XYZ"
}
},
{
"arrayFilters": [
{
"tasks.id": "10"
},
{
"subtasks.id": "120"
}
]
})
MongoDB Playground

Related

Getting second level nested object inside array of objects mongodb

My database looks like this:
[
{
"title": "man",
"articlesType": [
{
"title": "shoes",
"articles": [
{
"title": "shoes1",
"id": "randomId"
},
{
"title": "shoes2",
"id": "alsoRandomId"
}
]
}
]
},
{
"title": "woman",
"articlesType": [
{
"title": "pants",
"articles": [
{
"title": "pants1",
"id": "anotherRandomId"
},
{
"title": "pants1",
"id": "justId"
}
]
}
]
}
]
I expect something like this: , is it possible to get whole object in this nested just using an ID?
{
"title": "shoes2",
"id": "alsoRandomId"
}
I found this, but does not work for me
You can try an aggregation pipeline using double $unwind to deconstruct the array twice and then filter by your desired id (I'm assuming you want to match by the id).
And the last step, $project is to output the result in the same way as you want.
db.collection.aggregate([
{
"$unwind": "$articlesType"
},
{
"$unwind": "$articlesType.articles"
},
{
"$match": {
"articlesType.articles.id": "alsoRandomId"
}
},
{
"$project": {
"title": "$articlesType.articles.title",
"id": "$articlesType.articles.id"
}
}
])
Example here

How to add or update items in this multidimensional JSON?

Let's say we have some houses represented as JSON. Something like this:
[
{
"id": "1",
"code": "1",
"name": "Smith's",
"children": [
{
"id": "",
"code": "11",
"name": "Kitchen",
"children": [
{
"id": "",
"code": "111",
"name": "Sink",
"children": []
}
]
},
{
"id": "",
"code": "12",
"name": "Living Room",
"children": [
{
"id": "",
"code": "121",
"name": "Television",
"children": [
{
"id": "",
"code": "1211",
"name": "Panel buttons",
"children": [
{
"id": "",
"code": "12111",
"name": "Power button",
"children": []
},
{
"id": "",
"code": "12112",
"name": "Colors adjust button",
"children": []
}
]
},
{
"id": "",
"code": "1221",
"name": "Screen",
"children": []
}
]
}
]
}
]
},
{
"id": "2",
"code": "2",
"name": "Taylor's",
"children": [
// Here goes all house places and items like the example above
]
},
{
"id": "1",
"code": "1",
"name": "Wilson's",
"children": [
// Here goes all house places and items like the example above
]
}
]
Take notice that the "code" property, found in each item, is something to represent the "path" until that item, carrying its parents "code" property concatenated with its own position by incremental order. So the code "11" means house 1 and child 1. And 212 would be house 2, child 1, child 2. Also take notice that all items follow the same type. In other words, every item has a children that follows its own type. So, it could be infinite.
Now, I'd like to maintain these structure. Adding items, updating items and so on. Let's say we want to add a carpet in Smith's living room. We would go deep in the structure 2 levels, which are Smith's house (index 0 of the array) and living room (index 1 of the children array). And then add a carpet.
The problem is it won't be 2 levels in all cases. What if I wanted to add a bathroom? It would be level 1, alongside with kitchen in living room (the first children). What if I'd like to add a microwave in the kitchen and add to it buttons, display, etc?
I think I'm a recursive scenario where I have to visit all items and, if it is the one I'm looking to reach at, add/updated it.
I've tried following this example
I couldn't figure it out how to bring it to my case. though.
I appreciate if your contribution is in JavaScript, but feel free to represent it in other language in case you are better in other language =).
There are indeed some questions, like for instance what happens if you have more than 10 items as child and why do you need it?
And what happens if you remove any item on any level? will you recursively start updating all codes?
Nevertheless I gave it a go. In essence what I do in the code is first search for the parent (example: Kitchen) where you want to add it to and then add the new child item (example: Carpet) to it.
The search is a typical recursive search.
The child addition is a typical addition to an array.
For argument's sake I assumed that the fields code always exist and that children is always an array.
// Actual code is underneath the declaration of this array
let houseList = [
{
"id": "1",
"code": "1",
"name": "Smith's",
"children": [
{
"id": "",
"code": "11",
"name": "Kitchen",
"children": [
{
"id": "",
"code": "111",
"name": "Sink",
"children": []
}
]
},
{
"id": "",
"code": "12",
"name": "Living Room",
"children": [
{
"id": "",
"code": "121",
"name": "Television",
"children": [
{
"id": "",
"code": "1211",
"name": "Panel buttons",
"children": [
{
"id": "",
"code": "12111",
"name": "Power button",
"children": []
},
{
"id": "",
"code": "12112",
"name": "Colors adjust button",
"children": []
}
]
},
{
"id": "",
"code": "1221",
"name": "Screen",
"children": []
}
]
}
]
}
]
},
{
"id": "2",
"code": "2",
"name": "Taylor's",
"children": [
// Here goes all house places and items like the example above
]
},
{
"id": "1",
"code": "1",
"name": "Wilson's",
"children": [
// Here goes all house places and items like the example above
]
}
]
addChild(houseList,"11",{name:"Carpet" });
addChild(houseList,"1211",{name: "Volume Up Button"});
addChild(houseList,"1211",{name: "Volume Down Button"});
console.log('new houselist', houseList);
// child is just what you want to add and the parentCode refers to where you want to add it to
function addChild(houseList, parentCode, child) {
let parent = findInHouseList(houseList,parentCode,child);
let amountOfChildren = parent.children.length;
let newCodeName = parentCode +""+ (amountOfChildren+1);
child = {...{id: "", code: newCodeName, children: []}, ...child};
console.log('adding child ', child);
parent.children = [...parent.children, child];
}
function findInHouseList(houseList,code) {
for (let house of houseList) {
let foundElement = findElement(house,code);
if ( foundElement)
return foundElement;
}
}
function findElement(currentElement, code) {
if ( currentElement.code === code)
return currentElement;
if (currentElement.children?.length > 0)
{
for (let child of currentElement.children) {
let foundElement = findElement(child,code);
if ( foundElement)
return foundElement;
}
}
return null;
}
I decided to let the code manage the code names for new children. It seems the easiest.
What you're trying to do is updating a JSON value at a dynamic path.
This function will append a child to the item which holds the specified code.
You may add conditions to check if the item at the code is defined
function appendChild(houses, code, item) {
let path = code.split('')
let o = houses
for (let i = 0; i < path.length; i++) {
let n = path[i] - 1
o = o[n]["children"]
}
o.push(item)
return houses
}
However, you should start your code indexes at 0 and storing them inside the JSON is useless since they are simply the path to reach the item.

addToSet mongoDB not adding if one value is repeated

I have the following collection in MongoDB
[
{
"acronym": "front",
"references": [
{
"date": "2020-03-04",
"value": "5.6"
},
{
"date": "2020-03-05",
"value": "6.3"
}
]
}
]
I want to use the function $addToSet in order to add new document into references. I know that it can be done with the following code:
db.collection.update({
"acronym": "front"
},
{
$addToSet: {
"references": {
"date": "2020-03-06",
"value": "6"
}
}
})
And it will add the new document to the array references, so the result is the following:
[
{
"acronym": "front",
"references": [
{
"date": "2020-03-04",
"value": "5.6"
},
{
"date": "2020-03-05",
"value": "6.3"
},
{
"date": "2020-03-06",
"value": "6"
}
]
}
]
QUESTION: What I want to obtain is that in the case of adding a date that is already in the array, the update will no be produced.
Here is the playground: https://mongoplayground.net/p/DPER2RuROEs
Thanks!
You can add another qualifier to the update to prevent duplicated dates
db.collection.update({
"acronym": "front",
"references.date": {
$ne: "2020-03-04"
}
},
{
$addToSet: {
"references": {
"date": "2020-03-04",
"value": "6"
}
}
})
I got the solution from here

Return IDs list of nested normalized data in result

For example I have a simple JSON, like this:
{
"id": "123",
"author": {
"id": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": [
{
"id": "324",
"commenter": {
"id": "2",
"name": "Nicole"
}
},
{
"id": "325",
"commenter": {
"id": "3",
"name": "Alex"
}
}
]
}
And after normalizing with normalizr and schemas from example
import { normalize, schema } from 'normalizr';
// Define a users schema
const user = new schema.Entity('users');
// Define your comments schema
const comment = new schema.Entity('comments', {
commenter: user
});
// Define your article
const article = new schema.Entity('articles', {
author: user,
comments: [ comment ]
});
const normalizedData = normalize(originalData, article);
I will get this normalized JSON:
{
result: "123",
entities: {
"articles": {
"123": {
id: "123",
author: "1",
title: "My awesome blog post",
comments: [ "324", "325" ]
}
},
"users": {
"1": { "id": "1", "name": "Paul" },
"2": { "id": "2", "name": "Nicole" },
"3": { "id": "3", "name": "Alex" }
},
"comments": {
"324": { id: "324", "commenter": "2" },
"325": { id: "325", "commenter": "3" }
}
}
}
In normalizedData.result, I will get only articles IDs. But what if I need IDs of comments or users. Basically I can get it with Object.keys(), may be is there any other way, normalizr can provide us from API to get this data at step of normalization? I can't find anything about it it API. Or can you suggest any methods to do it, not automatically? Because Object.keys() not looks good for me.
Since the value you're normalizing is an article, the result value from Normalizr will be the Article's ID. As you suggested yourself, if you need to the IDs of a different, nested entity type, you'll have to use something like Object.keys(normalizedData.entities.comments)

Sorting an array of JavaScript objects by sub array property/value

I have the following data being returned from a server (the structure of this data is something that I do not have control over)...
var data = {
"TrackingResults": [
{
"Name": "Pack One",
"Products": {
"Product": [
{
"ProductName": "Soccer Ball"
},
{
"ProductName": "Tennis Racket"
},
{
"ProductName": "Gold Putter"
}
]
},
"status": "Despatched",
"Location": "Alabama",
"Type": "Parcel"
},
{
"Name": "Pack Two",
"Products": {
"Product": [
{
"ProductName": "Backet Ball Hoop"
},
{
"ProductName": "Base Ball Glove"
}
]
},
"status": "Despatched",
"Location": "Florida",
"Type": "Parcel"
}
]
};
I would like to be able to sort each Tracking Result by the first Product Name. I can't find any code that will sort by a sub array property/value.
You should use the Array.sort method with a custom comparator function:
var resultsComparator = function (res1, res2) {
var prod1 = res1.Products.Product[0].ProductName;
var prod2 = res2.Products.Product[0].ProductName;
return prod1.localeCompare(prod2);
}
This way the ordering is based on the current locale of the web browser. You just pass the function to the sort method:
data.TrackingResults.sort(resultsComparator);
You need to write it manually like: (with the hint on localeCompare from meskobalazs's comment)
var result = data.TrackingResults.sort(function(a,b){
return a.Products.Product[0].ProductName.localeCompare(b.Products.Product[0].ProductName)
});
This should work for sorting TrackingResults

Categories