pull value from object -> array -> object -> object mongo - mongodb

I have this following strtucture
{
_id: ".....",
a: {
a1:".......",
b: [
b1: {
b11: "......",
b12: "......",
},
b2: {
b21: "......",
b22: "......",
d: {},
},
c:{
c1: {
......
},
d: {
}
}
]
}
}
here I want to check if property d exists or not inside b, it may exists in multiple objects inside b, if exists pull the d object from record.
Note: There might be a chance that property d exists multiple times inside b1 and b2, In this case I want to remove it from all objects
I tried like
Coll.find({ 'a.b': { $elemMatch: { 'c': { d: { $exists: true } } } } })
but it is not returning anything although there are record, any help appreciated.
I want to pull that data from record too.
Thanks.
UPDATE
Coll.find({ 'a.b.c.d'{ $exists: true } })
that worked for me but still no idea how to use positional operator to pull the value from record

Please take a little more time forming your questions to make them easier to answer: your example data has key-values inside a list (invalid), your question mentions $elemMatch which is a list operator, you talk about removing things but never follow up that thought, and then your UPDATE switches gears and implies it is an object hierarchy.
Taking a hint from your UPDATE, I created some valid data - paste this into mongo shell (perhaps in the test database):
db.test.insert({
a: {
a1: "a1",
b: {
b1: {
b11: "b11",
b12: "b12",
},
b2: {
b21: "b21",
b22: "b22",
d: {},
},
c: {
c1: {
c1: "......"
},
d: {
d1: "woo hoo, struck gold"
}
}
}
}
})
Inside the mongo shell, everything is javascript so "positional operators" are the dot and array subscript operator. A mongo find() returns an array of documents. If you want the the d document (object) from the first doc returned from the query
db.test.find({'a.b.c.d': {$exists: true}})[0].a.b.c.d
produces
{
"d1": "woo hoo, struck gold"
}
UPDATE: Adding detail in response to comment.
If you want to remove the a.b.c.d sub-document, use $unset
// remove sub-document a.b.c.d
db.test.update({}, {$unset: {'a.b.c.d': ''}});
// look at the document to verify that a.b.c.d is removed
db.test.find();

Related

Find a value in multiple nested fields with the same name in MongoDB

In some documents I have a property with a complex structure like:
{
content: {
foo: {
id: 1,
name: 'First',
active: true
},
bar: {
id: 2,
name: 'Second',
active: false
},
baz: {
id: 3,
name: 'Third',
active: true
},
}
I'm trying to make a query that can find all documents with a given value in the field name across the different second level objects foo, bar, baz
I guess that a solution could be:
db.getCollection('mycollection').find({ $or: [
{'content.foo.name': 'First'},
{'content.bar.name': 'First'},
{'content.baz.name': 'First'}
]})
But a I want to do it dynamic, with no need to specify key names of nested fields, nether repeat the value to find in every line.
If some Regexp on field name were available , a solution could be:
db.getCollection('mycollection').find({'content.*.name': 'First'}) // Match
db.getCollection('mycollection').find({'content.*.name': 'Third'}) // Match
db.getCollection('mycollection').find({'content.*.name': 'Fourth'}) // Doesn't match
Is there any way to do it?
I would say this is a bad schema if you don't know your keys in advance. Personally I'd recommend to change this to an array structure.
Regardless what you can do is use the aggregation $objectToArray operator, then query that newly created object. Mind you this approach requires a collection scan each time you execute a query.
db.collection.aggregate([
{
$addFields: {
arr: {
"$objectToArray": "$content"
}
}
},
{
$match: {
"arr.v.name": "First"
}
},
{
$project: {
arr: 0
}
}
])
Mongo Playground
Another hacky approach you can take is potentially creating a wildcard text index and then you could execute a $text query to search for the name, obviously this comes with the text index/query limitations and might not be right for your usecase.

Is it possible to write a Mongo query that validates a condition in each element of an array?

Consider the following documents:
{
_id: 1,
a: [{b:true},{b:true}]
},
{
_id: 2,
a: [{b:false},{b:true}]
},
{
_id: 3,
a: [{b:true}]
}
I'd like to write a query that will return all of the top level documents that have an array ("a") that contain only elements matching {b : true}. In this example, I'm expecting the first and third document to be returned, but not the second.
When writing the query like this, all 3 documents are returned..
{a : {b : true}}
Is there an operator that I'm missing? I've reviewed quite a few of them ($all) and I'm not sure which would match best for this use case
Thanks so much
Simply use $allElementsTrue on the array a.b.
db.collection.find({
$expr: {
"$allElementsTrue": "$a.b"
}
})
Here is the Mongo Playground for your reference.

Most efficient way to put fields of an embedded document in its parent for an entire MongoDB collection?

I am looking for the most efficient way to modify all the documents of a collection from this structure:
{
[...]
myValues:
{
a: "any",
b: "content",
c: "can be found here"
}
[...]
}
so it becomes this:
{
[...]
a: "any",
b: "content",
c: "can be found here"
[...]
}
Basically, I want everything under the field myValues to be put in its parent document for all the documents of a collection.
I have been looking for a way to do this in a single query using dbCollection.updateMany(), put it does not seem possible to do such thing, unless the content of myValues is the same for all documents. But in my case the content of myValues changes from one document to the other. For example, I tried:
db.getCollection('myCollection').updateMany({ myValues: { $exists: true } }, { $set: '$myValues' });
thinking it would perhaps resolve the myValues object and use that object to set it in the document. But it returns an error saying it's illegal to assign a string to the $set field.
So what would be the most efficient approach for what I am trying to do? Is there a way to update all the documents of the collection as I need in a single command?
Or do I need to iterate on each document of the collection, and update them one by one?
For now, I iterate on all documents with the following code:
var documents = await myCollection.find({ myValues: { $exists: true } });
for (var document = await documents.next(); document != null; document = await documents.next())
{
await myCollection.updateOne({ _id: document._id }, { $set: document.myValues, $unset: { myValues: 1} });
}
Since my collection is very large, it takes really long to execute.
You can consider using $out as an alternative, single-command solution. It can be used to replace existing collection with the output of an aggregation. Knowing that you can write following aggregation pipeline:
db.myCollection.aggregate([
{
$replaceRoot: {
newRoot: {
$mergeObjects: [ "$$ROOT", "$myValues" ]
}
}
},
{
$project: {
myValues: 0
}
},
{
$out: "myCollection"
}
])
$replaceRoot allows you to promote an object which merges the old $$ROOT and myValues into root level.

Remove an element from an array by index

According to this answer, removing an element from an array by index implies two distinct updates:
db.lists.update({}, {$unset : {"interests.0" : 1 }})
db.lists.update({}, {$pull : {"interests" : null}})
This works... but it introduces some complexity to make the operation atomic and prevent race conditions. Is there any update on that topic?
Yes and No, really. Consider the following array:
{ array: [ { a: "a", b: "b" }, { a:"a", b:"c" } ] }
So you could $pull the second element from here with the following statement:
db.collection.update({}, {$pull: { array: { a: "a", b: "b"} } })
And that would remove the matching document. But if did the same thing on this array:
{ array: [ { a: "a", b: "b" }, { a: "a", b:"c", c: "c" }, { a:"a", b:"c" } ] }
Then this would be the result:
{ array: [ { a: "a", b: "b" }] }
So the query matched two of the documents in the array, and pulled them both.
Unfortunately the seemingly obvious:
db.collection.update({}, {$pull: { array: { $elemMatch:{ a: "a", b: "b" } }} })
Just wont work. Which is a pity. In the too hard basket to parse at this time.
So the moral of the story is, if you really concerned about concurrency and race conditions for this type of update, .find() the matching element first, then update() with a statement that matches the whole document, as long as it will not also partially match another document in the array.
If you cannot do that, then the method you describe in the question is currently the only way.
There is a JIRA issue on this, but it is not exactly terribly popular.

Mongodb match empty object in nested document

I'm just wondering if this is possible to do in a single request?
Given
{
_id: 1,
foo: {
fred: {}, // <- I want to remove empty keys like this
barney: { bar: 1 } // <- But keep these keys
}
}
Expected
{
_id: 1,
foo: {
barney: { bar: 1 }
}
}
I know how to do it in several requests, but I'm trying to understand MongoDB better.
Note. fred becomes empty in update command like { $unset: { "fred.baz": 1 } } when baz is the last key in fred.
Maybe it is possible to remove it with its contents? But the command sender does not know, is there any other keys, except baz at the moment.
You can search for empty embedded docs ({ }) and $unset them .. here's an example in the JS shell:
db.mycoll.update(
{'foo.fred':{ }},
{ $unset: {'foo.fred':1} },
false, // upsert: no
true // multi: find all matches
)