MongoDB update objects nested in arrays nested in other arrays without using the array index (nested updating) - mongodb

I have the following document:
{
"_id" : ObjectId("5720bd01232ac527623f2889"),
"planes" : [
{
"name" : "plane1",
"wings" : [
{
"name" : "rightDown"
},
{
"name" : "rightUp"
}
]
},
{
"name" : "plane2",
"wings" : [
{
"name" : "leftUp",
},
{
"name" : "leftDown",
}
]
}
]
}
I want to update an object in an array nested in an another array, without using the array index.
In my example, 'plane2' with the property 'wing' named 'leftDown'. Is it possible ?
db.planes.update({
planes: {
$elemMatch: {
wings: {
$elemMatch: {
name: 'leftUp'
}
}
}
}
},
// It would be wonderful if I the $set would look like this,
// but the error is:
// "Too many positional (i.e. '$') elements found in path 'planes.$.wings.$'"
//
// It seems that $ holds only the value of the first nested
// object in the array
{
$set: {
'planes.$.wings.$': {
name: 'leftMagic'
}
}
})
MongoDb 3.2 documentation says:
The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value
But I'm still waiting for that miracle ..
Are there any other clean/beautiful ways to do the update in a single shoot ?

Sadly no, the positional operator can only hold a single value. I don't think you can perform this update in a single shot with your current data structure.
You could perform one lookup to get the first array index, then a second with the positional operator, but at that point you could just traverse the array and resave the document.
With a little restructuring of your collections you could get the update down to a single operation. Check out MongooseJS populate.

Related

Search for empty arrays in mongodb atlas

I needed some help with the atlas search aggregation query.
I want to use $search syntax while doing the atlas search :
I have one collection; inside which I have one array field of ObjectIds like so :
arrayFieldOfObjectIds : [ObjectId(62ff26c349a3c47656765434), ObjectId(62ff26c349a3c47656765435)]
Now the array fields can also be empty in some cases like so :
arrayFieldOfObjectIds : []
I have defined the correct index mapping for this field.
My goal is to get all the documents that met the below conditions:
1. Either arrayFieldOfObjectIds doesn't exist.
2. If arrayFieldOfObjectIds does exist, it must be empty.
3. If arrayFieldOfObjectIds does exist, it must be equal to some specified value.
The query I have for arrayFieldOfObjectIds is :
{
"compound": {
"should": [
{
"compound": {
"mustNot": [
{
"exists": {
"path": "arrayFieldOfObjectIds1"
}
}
]
}
},
{
"equals": {
"value": ObjectId("62ff26c349a3c47656765434"),
"path": "arrayFieldOfObjectIds1"
}
}
],
"minimumShouldMatch": 1
}
}
This query doesn't give me those documents where arrayFieldOfObjectIds does exist and is empty.
Note: I need to use $search syntax and I also don't want to combine $match with $search as it kills the query performance altogether.
Thanks in advance.

Mongo query - Array of Objects - get field from element 0 only

Trying to query Mongo, and get 1 field of element 0, inside a document, namely emails[0].address:
Here's a sample (truncated) document structure:
{
"_id" : "dfadgfe266reh",
"emails" : [
{
"address" : "email#domain.com",
"verified" : false
}
]
}
And my query (truncated) is like this:
{
fields: {
'emails.0.address': {
address: 1
}
}
}
However, when I run this, I get an empty object array, namely emails:[{}]
But if I change the selector to 'emails.address' it will give me the actual email address -- the problem is I only want emails[0].address
What am I doing wrong?
To get the required document you need a project document with two attributes. First one, as #Veeram mentioned, slices the array and second to specify an attribute from the embedded document in the array. See code:
db.collection.find( {}, { "emails": { $slice: 1 } , "emails.address": 1} );
You can use $ in projection:
.find({"emails.address":{$exists:true}}, {"emails.$.address":1})

Inline/combine other collection into one collection

I want to combine two mongodb collections.
Basically I have a collection containing documents that reference one document from another collection. Now I want to have this as a inline / nested field instead of a separate document.
So just to provide an example:
Collection A:
[{
"_id":"90A26C2A-4976-4EDD-850D-2ED8BEA46F9E",
"someValue": "foo"
},
{
"_id":"5F0BB248-E628-4B8F-A2F6-FECD79B78354",
"someValue": "bar"
}]
Collection B:
[{
"_id":"169099A4-5EB9-4D55-8118-53D30B8A2E1A",
"collectionAID":"90A26C2A-4976-4EDD-850D-2ED8BEA46F9E",
"some":"foo",
"andOther":"stuff"
},
{
"_id":"83B14A8B-86A8-49FF-8394-0A7F9E709C13",
"collectionAID":"90A26C2A-4976-4EDD-850D-2ED8BEA46F9E",
"some":"bar",
"andOther":"random"
}]
This should result in Collection A looking like this:
[{
"_id":"90A26C2A-4976-4EDD-850D-2ED8BEA46F9E",
"someValue": "foo",
"collectionB":[{
"some":"foo",
"andOther":"stuff"
},{
"some":"bar",
"andOther":"random"
}]
},
{
"_id":"5F0BB248-E628-4B8F-A2F6-FECD79B78354",
"someValue": "bar"
}]
I'd suggest something simple like this from the console:
db.collB.find().forEach(function(doc) {
var aid = doc.collectionAID;
if (typeof aid === 'undefined') { return; } // nothing
delete doc["_id"]; // remove property
delete doc["collectionAID"]; // remove property
db.collA.update({_id: aid}, /* match the ID from B */
{ $push : { collectionB : doc }});
});
It loops through each document in collectionB and if there is a field collectionAID defined, it removes the unnecessary properties (_id and collectionAID). Finally, it updates a matching document in collectionA by using the $push operator to add the document from B to the field collectionB. If the field doesn't exist, it is automatically created as an array with the newly inserted document. If it does exist as an array, it will be appended. (If it exists, but isn't an array, it will fail). Because the update call isn't using upsert, if the _id in the collectionB document doesn't exist, nothing will happen.
You can extend it to delete other fields as necessary or possibly add more robust error handling if for example a document from B doesn't match anything in A.
Running the code above on your data produces this:
{ "_id" : "5F0BB248-E628-4B8F-A2F6-FECD79B78354", "someValue" : "bar" }
{ "_id" : "90A26C2A-4976-4EDD-850D-2ED8BEA46F9E",
"collectionB" : [
{
"some" : "foo",
"andOther" : "stuff"
},
{
"some" : "bar",
"andOther" : "random"
}
],
"someValue" : "foo"
}
Sadly mapreduce can't produce full documents.
https://jira.mongodb.org/browse/SERVER-2517
No idea why despite all the attention, whining and upvotes they haven't changed it. So you'll have to do this manually in the language of your choice.
Hopefully you've indexed 'collectionAID' which should improve the speed of your queries. Just write something that goes through your A collection one document at a time, loading the _id and then adding the array from Collection B.
There is a much faster way than https://stackoverflow.com/a/22676205/1578508
You can do it the other way round and run through the collection you want to insert your documents in. (Far less executions!)
db.collA.find().forEach(function (x) {
var collBs = db.collB.find({"collectionAID":x._id},{"_id":0,"collectionA":0});
x.collectionB = collBs.toArray();
db.collA.save(x);
})

How do I update Array Elements matching criteria in a MongoDB document?

I have a document with an array field, similar to this:
{
"_id" : "....",
"Statuses" : [
{ "Type" : 1, "Timestamp" : ISODate(...) },
{ "Type" : 2, "Timestamp" : ISODate(...) },
//Etc. etc.
]
}
How can I update a specific Status item's Timestamp, by specifying its Type value?
From mongodb shell you can do this by
db.your_collection.update(
{ _id: ObjectId("your_objectid"), "Statuses.Type": 1 },
{ $set: { "Statuses.$.Timestamp": "new timestamp" } }
)
so the c# equivalent
var query = Query.And(
Query.EQ("_id", "your_doc_id"),
Query.EQ("Statuses.Type", 1)
);
var result = your_collection.Update(
query,
Update.Set("Statuses.$.Timestamp", "new timestamp", UpdateFlags.Multi,SafeMode.True)
);
This will update the specific document, you can remove _id filter if you wanted to update the whole collection
Starting with MongoDB 3.6, the $[<identifier>] positional operator may be used. Unlike the $ positional operator — which updates at most one array element per document — the $[<identifier>] operator will update every matching array element. This is useful for scenarios where a given document may have multiple matching array elements that need to be updated.
db.yourCollection.update(
{ _id: "...." },
{ $set: {"Statuses.$[element].Timestamp": ISODate("2021-06-23T03:47:18.548Z")} },
{ arrayFilters: [{"element.Type": 1}] }
);
The arrayFilters option matches the array elements to update, and the $[element] is used within the $set update operator to indicate that only array elements that matched the arrayFilter should be updated.

Updating array of objects using mongoose and date [duplicate]

I have a document with an array field, similar to this:
{
"_id" : "....",
"Statuses" : [
{ "Type" : 1, "Timestamp" : ISODate(...) },
{ "Type" : 2, "Timestamp" : ISODate(...) },
//Etc. etc.
]
}
How can I update a specific Status item's Timestamp, by specifying its Type value?
From mongodb shell you can do this by
db.your_collection.update(
{ _id: ObjectId("your_objectid"), "Statuses.Type": 1 },
{ $set: { "Statuses.$.Timestamp": "new timestamp" } }
)
so the c# equivalent
var query = Query.And(
Query.EQ("_id", "your_doc_id"),
Query.EQ("Statuses.Type", 1)
);
var result = your_collection.Update(
query,
Update.Set("Statuses.$.Timestamp", "new timestamp", UpdateFlags.Multi,SafeMode.True)
);
This will update the specific document, you can remove _id filter if you wanted to update the whole collection
Starting with MongoDB 3.6, the $[<identifier>] positional operator may be used. Unlike the $ positional operator — which updates at most one array element per document — the $[<identifier>] operator will update every matching array element. This is useful for scenarios where a given document may have multiple matching array elements that need to be updated.
db.yourCollection.update(
{ _id: "...." },
{ $set: {"Statuses.$[element].Timestamp": ISODate("2021-06-23T03:47:18.548Z")} },
{ arrayFilters: [{"element.Type": 1}] }
);
The arrayFilters option matches the array elements to update, and the $[element] is used within the $set update operator to indicate that only array elements that matched the arrayFilter should be updated.