MongoDB - Modify the field name in a nested list of lists - mongodb

As the title says, the field I need to modify is nested like this:
basicData.owners.relatedJson.basicData.devices.equipmentID
which owners and devices are both lists.
The object looks like this:
{
"basicData": {
"owners": [
{
"relatedJson": {
"basicData": {
"devices": [
{
"equipmentID": "abcd",
"type": "camera"
}
],
"otherFieldsBasicData": "other values",
"evenMoreFieldsBasicData": "other values"
},
"otherFieldsRelatedJson": "other values",
"evenMoreFieldsRelatedJson": "other values"
}
}
]
}
}
I want to rename equipmentID to equipmentId.
I've also asked this question, and I've been trying to create a query using that as a starting point, but with no success.
I was able to build a query that could get as far down as the devices list, but. then I wanted to call $set on that list, and I get an error because you can't call set inside $mergeObjects.
I thought there was some way I could use $[] to help iterate through the first array, but I can't get it to work. I think this approach is wrong.
This is what I've ended up with, which doesn't work:
db.myCollection.updateMany({"basicData.owners.relatedJson.basicData.devices.equipmentID": {$exists: true}},
[
{
$set: {
"basicData.owners$[].relatedJson.basicData.devices": {
$map: {
input: "$basicData.owners$[].relatedJson.basicData.devices", in: {
$mergeObjects: ["$$this",
{equipmentId: "$$this.equipmentID",}]
}
}
}
}
},
{
$unset: "basicData.owners.relatedJson.basicData.devices.equipmentID"
}
])
Any advice would be greatly appreciated.

Think you need two $map (with nested) operators.
First $map to iterate basicData.owners array while another $map is used to iterate relatedJson.basicData.devices array.
db.collection.updateMany({
"basicData.owners.relatedJson.basicData.devices.equipmentID": {
$exists: true
}
},
[
{
$set: {
"basicData.owners": {
$map: {
input: "$basicData.owners",
in: {
$mergeObjects: [
"$$this",
{
"relatedJson": {
$mergeObjects: [
"$$this.relatedJson",
{
"basicData": {
$mergeObjects: [
"$$this.relatedJson.basicData",
{
"devices": {
$map: {
input: "$$this.relatedJson.basicData.devices",
in: {
$mergeObjects: [
"$$this",
{
equipmentId: "$$this.equipmentID",
}
]
}
}
}
}
]
}
}
]
}
}
]
}
}
}
}
},
{
$unset: "basicData.owners.relatedJson.basicData.devices.equipmentID"
}
])
Demo # Mongo Playground

Related

MongoDB4 update nested objects item

i've a problem updating a nested object in MongoDB 4.
I wrote a playground where I'm doing some tests to be able to solve the problem ..
My desiderata is:
I need the "email" item to be overwritten with the lowercase of itself (result obtained).
I need that in all the N objects contained in the "emailsStatus" array the "emailAddress" item is overwritten with itself but in lowercase
I can not carry out the second point, in the playground you will find everything ready and with the test I am carrying out but I am not succeeding .. What am I wrong?
Playground: https://mongoplayground.net/p/tJ25souNlYZ
Try $map to iterate loop of emailsStatus array and convert emailAddress lower case, merge current object with update email field using $mergeObjects,
one more suggestion for query part is you can check single condition for email using $type is string
db.collection.update(
{ email: { $type: "string } },
[{
$set: {
email: { $toLower: "$email" },
emailsStatus: {
$map: {
input: "$emailsStatus",
in: {
$mergeObjects: [
"$$this",
{ emailAddress: { $toLower: "$$this.emailAddress" } }
]
}
}
}
}
}],
{ multi: true }
)
Playground
Check null condition in emailAddress array using $cond and $type,
$map: {
input: "$emailsStatus",
in: {
$mergeObjects: [
"$$this",
{
emailAddress: {
$cond: [
{ $eq: [{ $type: "$$this.emailAddress" }, "string"] },
{ $toLower: "$$this.emailAddress" },
"$$this.emailAddress"
]
}
}
]
}
}
Playground

How to query MongoDB for complex data

I have a table structured as follows:
db.l2vpn_fdb_database.findOne()
{
_id: ObjectId("5f5257f11324c04122714445"),
hostname: "spine01-drt-red",
l2vpn_fdb_database: {
MAC: [
{
IfIndex: "1631",
MacAddr: "00-00-0C-07-AC-01",
SrvID: "1",
VsiName: "EVPN",
},
{
IfIndex: "0",
MacAddr: "00-00-0C-07-AC-02",
SrvID: "0",
VsiName: "EVPN",
},
{
IfIndex: "1631",
MacAddr: "00-00-0C-07-AC-0A",
SrvID: "1",
VsiName: "EVPN",
},
],
},
}
I'd like to search for "MacAddr" object, could you help me figure out based on above? So essentially I'd like to be able to parse database for a MacAddress assuming it's there and be able to get "IfIndex" for further processing.
Thank you.
You can use $filter to get matched objects
db.collection.aggregate([
{
$project: {
l2vpn_fdb_database: {
$filter: {
input: "$l2vpn_fdb_database.MAC",
cond: {
$eq: [
"$$this.IfIndex",
"1631"
]
}
}
}
}
}
])
Working Mongo playground
for Hostname with macAddr try like this,
db.collection.aggregate([
{
$project: {
l2vpn_fdb_database: {
$filter: {
input: "$l2vpn_fdb_database.MAC",
cond: {
$eq: [
"$$this.IfIndex",
"1631"
]
}
}
},
hostname:{
$eq:['$hostname','spine01-drt-red']
}
}
}
])
This query could help you.
b.l2vpn_fdb_database.findOne({
"l2vpn_fdb_database.MAC.MacAddr": "00-00-0C-07-AC-01",
},
{
"l2vpn_fdb_database.MAC.$": 1
})
The result is the same document just with 1 element in the array
Result:
{
"_id": ObjectId("5f5257f11324c04122714445"),
"l2vpn_fdb_database": {
"MAC": [
{
"IfIndex": "1631",
"MacAddr": "00-00-0C-07-AC-01",
"SrvID": "1",
"VsiName": "EVPN"
}
]
}
}

find and update with pull and get the pulled document in return

Here is my collection
[
{_id:1,
persons:[{name:"Jack",age:12},{name:"Ma",age:13}]
}
]
I want to remove {name:"Jack",age:12} in persons by pull but I also want after pulling is completed I will be returned the pulled {name:"Jack",age:12}. How can I do this?
I want to do it like this
db.getCollection('test').findOneAndUpdate({_id:1},{$pull:{"persons":{name:"Jack"}}},
{projection:{"person":{
$filter : {
input: "$persons",
as : "ele",
cond : {$eq : ["$$ele.name","Jack"]}
}
}}})
You can use $reduce, because $filter will return array, also aggregation array operators will support from MongoDB 4.4,
db.getCollection('test').findOneAndUpdate({_id:1},
{ $pull: { "persons": { name: "Jack" } } },
{
projection: {
"person":{
$reduce: {
input: "$persons",
initialValue: {},
in: { $cond: [{$eq: ["$$this.name","Jack"]}, "$$this", "$$value"] }
}
}
}
}
)
Playground

Conditional check inside nested array in MongoDb

After a few pipeline stages, I came up with following sample result which is one document. If the videos.views.userId contains 10, I need to indicate videos.isWatched = true else false. We can use $unwind, check the condition, then group it.
This is sample output, The original document contains a lot of field, so I just like to do with less code unless I need to unwind Is there any way to do without unwind("videos")?
[
{
"_id":"someId",
"videos":[
{
"_id":"1",
"name":"A",
"views":[
{
"userId":10,
"group":"X"
}
]
},
{
"_id":"2",
"name":"B",
"views":[
{
"userId":20,
"group":"Y"
}
]
}
],
"Assessment":[
{
"_id":"22",
"title": "Hello world"
}
]
}
]
Expected result
[
{
"_id":"someId",
"videos":[
{
_id:"1",
name:"A",
views:[
{
userId:10,
group:"X"
}
],
"isWatched":true
},
{
_id:"2",
name:"B",
views:[
{
userId:20,
group:"Y"
}
],
"isWatched":false
}
],
"Assessment":[
{
_id:"22",
title: "Hello world"
}
]
}
]
You can use $map along with $mergeObjects to add a new field to an existing array. $anyElementTrue can be used to determine whether there's any userId equal to 10:
db.collection.aggregate([
{
$addFields: {
videos: {
$map: {
input: "$videos",
in: {
$mergeObjects: [
"$$this",
{
isWatched: {
$anyElementTrue: {
$map: { input: "$$this.views", in: { $eq: [ "$$this.userId", 10 ] } }
}
}
}
]
}
}
}
}
}
])
Mongo Playground

how to use $elemMatch on array specifying an upper field as part of the query

I'd like to retrieve for a specific user, his chats with unread messages.
Lets say I have a simplified chat model like that :
{
lastMessageAt: Date,
participants: [
{
user: String(id),
lastReadAt: Date
}
]
}
How can I achieve my query ?
I have tried several thing like with $elemMatch, but lastMessageAt is unknown at this level...
ChatDB.find({
'participants': {
$elemMatch: { user: '12345', lastReadAt: { $lt: '$lastMessageAt' } }
}
}
Thanks in advance for your help ! :)
$elemMatch operator will find those documents in ChatDB collection that have at least 1 element in participants that matches your criteria. Also my research ended with the conslusion that it is not yet possible to access other document field in $elemMatch operator. Anyway, if this is your goal, then you can use this query:
ChatDB.aggregate([
{
$match: {
"participants.user": "12345",
$expr: {
$lt: [
"$participants.lastReadAt",
"$lastMessageAt"
]
}
}
}
])
Mongo playground
If you also want to filter participants that really matched the criteria, then you need to add a projection stage:
ChatDB.aggregate([
{
$match: {
"participants.user": "12345",
$expr: {
$lt: [
"$participants.lastReadAt",
"$lastMessageAt"
]
}
}
},
{
$project: {
participants: {
$filter: {
input: "$participants",
as: "participant",
cond: {
$and: [
{
$eq: [
"$$participant.user",
"12345"
]
},
{
$lt: [
"$$participant.lastReadAt",
"$lastMessageAt"
]
}
]
}
}
}
}
}
])
Mongo playground
I have found the solution witch is to use the aggregator with the $unwind operator.
await ChatDB.aggregate([
{
$unwind: '$participants'
},
{
$match: {
'participants.user': '12345',
$expr: {
$lt: [
'$participants.lastReadAt',
'$lastMessageAt'
]
}
}
}]);
Hope this will be usefull