MongoDB update fields of subarrays that meet criteria - mongodb

I am having a problem where I need to update a specific field found in arrays contained in a bigger array that match certain criteria as of MongoDB v2.2.3.
I have the following mongodb sample document.
{
_id: ObjectId("50be30b64983e5100a000009"),
user_id: 0
occupied: {
Greece: [
{
user_id: 3,
deadline: ISODate("2013-02-08T19:19:28Z"),
fulfilled: false
},
{
user_id: 4,
deadline: ISODate("2013-02-16T19:19:28Z"),
fulfilled: false
}
],
Italy: [
{
user_id: 2,
deadline: ISODate("2013-02-15T19:19:28Z"),
fulfilled: false
}
]
}
}
Each country in the occupied array has its own set of arrays.
What I am trying to do is find the document where user_id is 0, search through the occupied.Greece array only for elements that have "deadline": {$gt: ISODate(current-date)} and change their individual "fulfilled" fields to true.
I have tried the $ and $elemMatch operators but they match only one, the first, array element in a query whereas I need it to match all eligible elements by the given criteria and make the update without running the same query multiple times or having to process the arrays client-side.
Is there no server-side solution for generic updates in a single document? I am developing using PHP though a solution to this should be universal.

I'm afraid this is not possible. From the documentation:
Remember that the positional $ operator acts as a placeholder for the first match of the update query selector. [emphasis not mine]
This is tracked in the MongoDB Jira under SERVER-1243.
There's quite a number of related feature requests in the jira, mostly under the topic 'virtual collections'.

Related

how can I make the "updated" of mongodb stop when updating a field of a nested array?

I have a database like this:
{
"universe":"comics",
"saga":[
{
"name":"x-men",
"characters":[
{
"character":"wolverine",
"picture":"618035022351.png"
},
{
"character":"wolverine",
"picture":"618035022352.png"
}
]
}
]
},
{
"universe":"dc",
"saga":[
{
"name":"spiderman",
"characters":[
{
"character":"venom",
"picture":"618035022353.png"
}
]
}
]
}
And with this code, I update the field where name: wolverine:
db.getCollection('collection').findOneAndUpdate(
{
"universe": "comics"
},
{
$set: {
"saga.$[outer].characters.$[inner].character": "lobezno",
"saga.$[outer].characters.$[inner].picture": "618035022354.png"
}
},
/*{
"saga.characters": 1
},*/
{
"arrayFilters": [
{
"outer.name": "x-men"
},
{
"inner.character": "wolverine"
}
],
"multi":false
}
)
I want to just update the first object where there is a match, and stop it.
For example, if I have an array of 100,000 elements and the object where the match is, is in the tenth position, he will update that record, but he will continue going through the entire array and this seems ineffective to me even though he already did the update.
Note: if I did the update using an _id inside of universe.saga.characters instead of doing the update using the name, it would still loop through the rest of the elements.
How can I do it?
Update using arrayFilters conditions
I don't think it will find and update through loop, and It does not matter if collection have 100,000 sub documents, because here is nice explanation in $[<identifier>] and has mentioned:
The $[<identifier>] to define an identifier to update only those array elements that match the corresponding filter document in the arrayFilters
In the update document, use the $[<identifier>] filtered positional operator to define an identifier, which you then reference in the array filter documents. But make sure you cannot have an array filter document for an identifier if the identifier is not included in the update document.
Update using _id
Your point,
Note: if I did the update using an _id inside of universe.saga.characters instead of doing the update using the name, it would still loop through the rest of the elements.
MongoDB will certainly use the _id index. Here is the nice answer on question MongoDB Update Query Performance, from this you will get an better idea on above point
Update using indexed fields
You can create index according to your query section of update command, Here MongoDB Indexes and Indexing Strategies has explained why index is important,
In your example, lets see with examples:
Example 1: If document have 2 sub documents and when you update and check with explain("executionStats"), assume it will take 1 second to update,
quick use Mongo Playground (this platform will not support update query)
Example 2: If document have 1000 sub documents and when you update and check with explain("executionStats"), might be it will take more then 1 second,
If provide index on fields (universe, saga.characters.character and saga.characters.picture) then definitely it will take less time then usual without index, main benefit of index it will direct point to indexed fields.
quick use Mongo Playground (this platform will not support update query)
Create Index for your fields
db.maxData.createIndex({
"universe": 1,
"saga.characters.character": 1,
"saga.characters.picture": 1
})
For more experiment use above 2 examples data with index and without index and check executionStats you will get more clarity.

How to filter minimongo collection with more parameters in meteor

I need help with filtering reactively minimongo collection with 3+ parameters. Firs, I have loaded minimongo from server's mongo collection with publish/subscribe. Now, I want to filter that collection and let user see only objects from collection that are same like filters. First of all I have search query filter that checks if input string is same as some filed in collection:
Job.find({ $or: [ { Name: regex }, { Description: regex }, ]});
This part is doing his job well. And now second filter: I have field in object that is true if that particular job is remote friendly and false if it is not, I wanted to if user enables that filter he only see just remote friendly job positions, and if he disables that, he can see all jobs available ( + search query of course):
if (remoteQuery === true ) {
return Job.find({ $or: [ { Name: regex }, { Description: regex }, ] , Remote: true});
}
This is working, but it is bad way for doing this. And now biggest problem comes with last filter: I have one more field that is storing "job" (collection object) type. Type can be 1, 2 , 3 or 4. So how could I say to minimongo e.g. "Show only jobs that have "Front-end" inside (search query), that are remote friendly, and have third filter 2 and 3 inside"
Job.find({ $or: [ { Name: "Front-end"}, { Description: "Front-end"}, ] , Remote: true, positionType: [2,3],});
Something like this? Thanks!
Sounds like you are looking for the MongoDB query $in operator:
The $in operator selects the documents where the value of a field equals any value in the specified array.
Therefore your 3rd query could look like:
Job.find({
positionType: {
$in: [2, 3] // Matches documents where `positionType` is either 2 or 3
},
// Other conditions…
});

How to force MongoDB pullAll to disregard document order

I have a mongoDB document that has the following structure:
{
user:user_name,
streams:[
{user:user_a, name:name_a},
{user:user_b, name:name_b},
{user:user_c, name:name_c}
]
}
I want to use $pullAll to remove from the streams array, passing it an array of streams (the size of the array varies from 1 to N):
var streamsA = [{user:"user_a", name:"name_a"},{user:"user_b", name:"name_b"}]
var streamsB = [{name:"name_a", user:"user_a"},{name:"name_b", user:"user_b"}]
I use the following mongoDB command to perform the update operation:
db.streams.update({name:"user_name", {"$pullAll:{streams:streamsA}})
db.streams.update({name:"user_name", {"$pullAll:{streams:streamsB}})
Removing streamsA succeeds, whereas removing streamsB fails. After digging through the mongoDB manuals, I saw that the order of fields in streamsA and streamsB records has to match the order of fields in the database. For streamsB the order does not match, that's why it was not removed.
I can reorder the streams to the database document order prior to performing an update operation, but is there an easier and cleaner way to do this? Is there some flag that can be set to update and/or pullAll to ignore the order?
Thank You,
Gary
The $pullAll operator is really a "special case" that was mostly intended for single "scalar" array elements and not for sub-documents in the way you are using it.
Instead use $pull which will inspect each element and use an $or condition for the document lists:
db.streams.update(
{ "user": "user_name" },
{ "$pull": { "streams": { "$or": streamsB } }}
)
That way it does not matter which order the fields are in or indeed look for an "exact match" as the current $pullAll operation is actually doing.

Matching an array field which contains any combination of the provided array in MongoDB

I would like to query with a specified list of array elements such that documents returned can only contain the elements I pass, but need not contain all of them.
Given documents like:
{
name: "Article 1",
tags: ["Funny", "Rad"]
}
{
name: "Article 2",
tags: ["Cool", "Rad"]
}
{
name: "Article 3",
tags: ["Rad"]
}
Here are some example arrays and their respective results.
["Rad"] should return Article 3
["Rad", "Cool"] should return Article 2 and Article 3
["Funny", "Cool"] should return nothing, since there are no articles with only one of those tags or both
I'm sure I can pull this off with $where but I'd like to avoid that for obvious reasons.
You can do this by combining multiple operators:
db.test.find({tags: {$not: {$elemMatch: {$nin: ['Rad', 'Cool']}}}})
The $elemMatch with the $nin is finding the docs where a single tags element is neither 'Rad' nor 'Cool', and then the parent $not inverts the match to return all the docs where that didn't match any elements.
However, this will also return docs where tags is either missing or has no elements. To exclude those you need to add a qualifier that ensures tags has at least one element:
db.test.find({
tags: {$not: {$elemMatch: {$nin: ['Rad', 'Cool']}}},
'tags.0': {$exists: true}
})
The accepted answer works, but isn't optimised. Since this is the top result on Google, here's a better solution.
I went all the way back to version 2.2 in the docs, which is the oldest version available, and all of them state:
If the field holds an array, then the $in operator selects the documents whose field holds an array that contains at least one element that matches a value in the specified array (e.g. <value1>, <value2>, etc.)
Source
So you can just do
db.test.find({tags: {$in: ['Rad', 'Cool']}})
which will return any entries where the tags contain either 'Rad', 'Cool', or both and use an index if available.

How do I implement this mongodb query & update operation (CSharp driver)?

I have this collection:
Books
[
{
title: Book1,
References: [ObjectId(1), ObjectId(3), ObjectId(5)] <- These are object ids of another collection
Main-Reference: ObjectId(5)
},
{
title: Book2,
References: [ObjectId(2), ObjectId(5), ObjectId(7)]
Main-Reference: ObjectId(5)
}
{
title: Book3,
References: [ObjectId(5), ObjectId(7), ObjectId(9)]
Main-Reference: ObjectId(7)
},
]
I have an operation where I delete a Reference from book collection
Example: Assume I have to delete Reference ObjectId(5) from my collection
So my new collection become this:
Books
[
{
title: Book1,
References: [ObjectId(1), ObjectId(3)] <- ObjectId(5) is pulled
Main-Reference: ObjectId(1) <- ObjectId(1) is new value as ObjectId(5) is deleted
},
{
title: Book2,
References: [ObjectId(2), ObjectId(7)] <- ObjectId(5) is pulled
Main-Reference: ObjectId(2) <- ObjectId(2) is now main reference
}
{
title: Book3,
References: [ObjectId(7), ObjectId(9)] <- ObjectId(5) is pulled
Main-Reference: ObjectId(7) <- no changes here as ObjectId(7) still exists in References
},
]
Currently this is how I am doing:
Step 1: Pull ObjectId(5) from all Books where References[] has ObjectId(5)
Step 2: Query Books collection where Main-Reference=ObjectId(5) & use References: {$slice:1} slice to get the first array element from References array
Step 3: Update all of the books found in Step 2 & replace Main-Reference with the first array element I get from slice
This seems clumsy to me and trying to see if there is a better way to do this.
If I essentially get your gist you basically want to
Pull the item that is not required from your references array
Set the value of your main-reference field to the first element of the altered array
And get that done all in one update without moving documents across the wire.
But this sadly cannot be done. The main problem with this is that there is no way to refer to the value of another field within the document being updated. Even so, to do this without iterating you would also need to access the changed array in order to get the new first element.
Perhaps one approach is to re-think your schema in order to accomplish what you want. My option here would expanding on your references documents a little and removing the need for the main-reference field.
It seems that the assumption you are willing to live with on the updates is that if the removed reference was the main-reference then you can just set the new main-reference to the first element in the array. With that in mind consider the following structure:
refs: [ { oid: "object1" }, { oid: "object2" }, { oid: "object5", main: true } ]
By changing these to documents with an oid property that would be set to the ObjectId it gives the option to have an additional property on the document that specifies which is the default. This can easily be queried determine which Id is the main reference.
Now also consider what would happen if the document matching "object5" in the oid field was pulled from the array:
refs: [ { oid: "object1" }, { oid: "object2" } ]
So when you query for which is the main-reference as per the earlier logic you accept the first document in the array. Now of course, to your application requirements, if you want to set a different main-reference you just alter the document
refs: [ { oid: "object1" }, { oid: "object2", main: true } ]
And now the logic remains to choose the array element that has the main property as true would occur in preference, and as shown above that if that property does not exist on any elements document then fall back to the first element.
With all of that digested, your operation to pull all references to an object out of that array in all documents becomes quite simple, as done in the shell ( same format should basically apply to whatever driver ):
db.books.update(
{ "refs.oid": "object5" },
{ $pull: { refs: {oid: "object5"} } }, false, true )
The two extra arguments to the query and update operation being upsert and multi respectively. In this case, upsert does not make much sense as we only want to modify documents that exist, and multi means that we want to update everything that matched. The default is to change just the first document.
Naturally I shortened all the notation but of course the values can be actual ObjectId's as per your intent. It seemed also reasonable to presume that your main usage of the main-reference is once you have retrieved the document. Defining a query that returns the main-reference by following the logic that was outlined should be possible, but as it stands I have typed a lot out here and need to break for dinner :)
I think this presents a worthwhile case for re-thinking your schema to avoid over the wire iterations for what you want to achieve.