MongoDB: Update array elements on multiple conditions - mongodb

Colleagues, good afternoon!
I'm struggling with some issue. I am using MongoDB 4.4.4. My assignment looks like this:
There is a set of elements methods[].subcategories[].actions[]. Please note that all objects are arrays and they may be absent. Elements of the actions[] array consists of the _id and title fields.
It is necessary to find the actual value of recordId by the field actions.title of the element and write it to the element of the actions array.
List of current values:
0af4cd2e-78cb-109b-8178-d5a7ba0e0012, Inspection
0af4cd2e-78cb-109b-8178-d5a7ba130014, Screening
0af4cd2e-78cb-109b-8178-d5a7ba170016, Poll
0af4cd2e-78cb-109b-8178-d5a7ba1b0018, Getting written explanations
0af4cd2e-78cb-109b-8178-d5a7ba1e001a, Request for documents
0af4cd2e-78cb-109b-8178-d5a7ba21001c, Sampling (samples)
0af4cd2e-78cb-109b-8178-d5a7ba23001e, Instrumental examination
0af4cd2e-78cb-109b-8178-d5a7ba260020, Test
0af4cd2e-78cb-109b-8178-d5a7ba2b0022, Expertise
0af4cd2e-78cb-109b-8178-d5a7ba2d0024, Experiment
3b7205c1-8282-4b63-8121-b82aacd7ca67, Request for documents that, in accordance with the mandatory requirements, must be located at the location (carrying out activities) of the controlled person (its branches, representative offices, separate structural divisions) or the object of control
I wrote the following code:
db.getSiblingDB("ervk_core").getCollection("supervision1").updateMany(
{},
{
"$set": {
"methods.subcategories.actions.$[elem1]._id": "0af4cd2e-78cb-109b-8178-d5a7ba0e0012",
"methods.subcategories.actions.$[elem2]._id": "0af4cd2e-78cb-109b-8178-d5a7ba0e0014",
"methods.subcategories.actions.$[elem3]._id": "0af4cd2e-78cb-109b-8178-d5a7ba0e0016",
"methods.subcategories.actions.$[elem4]._id": "0af4cd2e-78cb-109b-8178-d5a7ba0e0018",
"methods.subcategories.actions.$[elem5]._id": "0af4cd2e-78cb-109b-8178-d5a7ba0e001a",
"methods.subcategories.actions.$[elem6]._id": "0af4cd2e-78cb-109b-8178-d5a7ba0e001c",
"methods.subcategories.actions.$[elem7]._id": "0af4cd2e-78cb-109b-8178-d5a7ba0e001e",
"methods.subcategories.actions.$[elem8]._id": "0af4cd2e-78cb-109b-8178-d5a7ba260020",
"methods.subcategories.actions.$[elem9]._id": "0af4cd2e-78cb-109b-8178-d5a7ba260022",
"methods.subcategories.actions.$[elem10]._id": "0af4cd2e-78cb-109b-8178-d5a7ba260024",
"methods.subcategories.actions.$[elem11]._id": "3b7205c1-8282-4b63-8121-b82aacd7ca67",
}
},
{
"arrayFilters": [
{
"elem1.title": "Inspection",
},
{
"elem2.title": "Search",
},
{
"elem3.title": "Poll",
},
{
"elem4.title": "Receipt of Written Explanations",
},
{
"elem5.title": "Retrieval of Documents",
},
{
"elem6.title": "Sampling (samples)",
},
{
"elem7.title": "Instrumental examination",
},
{
"elem8.title": "Trial",
},
{
"elem9.title": "Expertise",
},
{
"elem10.title": "Experiment",
},
{
"elem11.title": "Request for documents that, in accordance with the mandatory requirements, must be located at the location (carrying out activities) of the controlled person (its branches, representative offices, separate structural divisions) or the object of control",
}
]
}
);
However, it gives the error "The path 'methods.subcategories.actions' must exist in the document in order to apply array updates.". I understand why it occurs - due to the absence of the actions[] array. But how can I account for the fact that methods[].subcategories[].actions[] arrays may be missing. And did I write the code correctly, otherwise I'm already a little confused. Thanks a lot in advance!

What you can do is add array filters checks to the method and subcategory object to see the nested array exists, this will solve your issue as Mongo will not continue checking the nested conditions in case they don't exist. Here's how you'd do it:
db.collection.updateMany(
{
"methods.subcategories.actions": {
$exists: true
}
},
{
"$set": {
"methods.$[methodElem].subcategories.$[subCatElem].actions.$[elem1]._id": "0af4cd2e-78cb-109b-8178-d5a7ba0e0012",
"methods.$[methodElem].subcategories.$[subCatElem].actions.$[elem2]._id": "0af4cd2e-78cb-109b-8178-d5a7ba0e0014",
"methods.$[methodElem].subcategories.$[subCatElem].actions.$[elem3]._id": "0af4cd2e-78cb-109b-8178-d5a7ba0e0016",
"methods.$[methodElem].subcategories.$[subCatElem].actions.$[elem4]._id": "0af4cd2e-78cb-109b-8178-d5a7ba0e0018",
"methods.$[methodElem].subcategories.$[subCatElem].actions.$[elem5]._id": "0af4cd2e-78cb-109b-8178-d5a7ba0e001a",
"methods.$[methodElem].subcategories.$[subCatElem].actions.$[elem6]._id": "0af4cd2e-78cb-109b-8178-d5a7ba0e001c",
"methods.$[methodElem].subcategories.$[subCatElem].actions.$[elem7]._id": "0af4cd2e-78cb-109b-8178-d5a7ba0e001e",
"methods.$[methodElem].subcategories.$[subCatElem].actions.$[elem8]._id": "0af4cd2e-78cb-109b-8178-d5a7ba260020",
"methods.$[methodElem].subcategories.$[subCatElem].actions.$[elem9]._id": "0af4cd2e-78cb-109b-8178-d5a7ba260022",
"methods.$[methodElem].subcategories.$[subCatElem].actions.$[elem10]._id": "0af4cd2e-78cb-109b-8178-d5a7ba260024",
"methods.$[methodElem].subcategories.$[subCatElem].actions.$[elem11]._id": "3b7205c1-8282-4b63-8121-b82aacd7ca67"
}
},
{
"arrayFilters": [
{
"methodElem.subcategories": {
$exists: true
}
},
{
"subCatElem.actions": {
$exists: true
}
},
{
"elem1.title": "Inspection"
},
{
"elem2.title": "Search"
},
{
"elem3.title": "Poll"
},
{
"elem4.title": "Receipt of Written Explanations"
},
{
"elem5.title": "Retrieval of Documents"
},
{
"elem6.title": "Sampling (samples)"
},
{
"elem7.title": "Instrumental examination"
},
{
"elem8.title": "Trial"
},
{
"elem9.title": "Expertise"
},
{
"elem10.title": "Experiment"
},
{
"elem11.title": "Request for documents that, in accordance with the mandatory requirements, must be located at the location (carrying out activities) of the controlled person (its branches, representative offices, separate structural divisions) or the object of control"
}
]
})
Mongo Playground
I also changed the updates query to just ignore documents with no nested actions, this is just to save time.

Related

Query finding a group whose members match exactly with a given string

First of all, my English is not good, I apologize if I am rude.
I need your help with a query. I tried many things but failed.
I’ve written a little database example below.
I want to find the group whose members match exactly with the given username array.
groups.findOne( { 'members.username': { $in: [ 'john22', 'david7' ] } } )
As a result, the first group is returning. But there is another user in the first group. Whereas the array matches exactly with the second group. How should I write the query for exact match. Please help me.
Thank you
{ groups: [
{
id:1,
name:"aliens",
members:[
{
username:"john22",
joineddate:222222222,
removed: false
},
{
username:"david7",
joineddate:3333333333,
removed: false
},
{
username:"william4",
joineddate:444444444444,
removed: false
},
]
},
{
id:2,
name:"stars",
members:[
{
username:"john22",
joineddate:111111111111,
removed: false
},
{
username:"david7",
joineddate:111111111111,
removed: false
},
]
}
] }

I need to extract data in the form of key-value pairs from a collection of records and merge them into their parent record in mongoDB

Below I have a structure for supporting custom picklist fields (in this example) within my sails.js application. The general idea is we support a collection of custom picklist values on any model within the app and the customer can have total control of the configuration of the custom field.
I went with this relationship model as using a simple json field lacks robustness when it comes to updating each individual custom picklist value. If I allow a customer to change "Internal" to "External" I need to update all records that have the value "Internal" recorded against that custom picklist with the new value.
This way - when I update the "value" field of CustomPicklistValue wherever that record is referenced via ID it will use the new value.
Now the problem comes when I need to integrate this model into my existing report engine...
rawCollection
.aggregate(
[
{
$match: {
createdAt: {
$gte: rangeEndDate,
$lte: rangeStartDate
},
...$match
}
},
{
$project: {
...$project,
total: $projectAggregation
}
},
{
$group: {
_id: {
...$groupKey
},
total: {
[`$${aggrAttrFunc}`]: "$total"
}
}
}
],
{
cursor: {
batchSize: 100
}
}
)
Here is the main part of a method for retrieving and aggregating any models stored in my mongodb instance. A user can specify a whole range of things including but not limited to the model, field specific date ranges and filters such as "where certificate status equals expired" etc.
So I'm now presented with this data structure:
{
id: '5e5fb732a9422a001146509f',
customPicklistValues: [
{
id: '5e4e904f16ab94bff1a324a0',
value: 'Internal',
fieldName: 'Business Group',
customPicklist: '109c7a1a9d00b664f2ee7827'
},
{
id: '5e4e904f16ab94bff1a324a4',
value: 'Slack',
fieldName: 'Application',
customPicklist: '109c5a1a9d00b664f2ee7827'
}
],
}
And for the life of me can't work out if there's any way I can essentially pull out fieldName and value for each of the populated records as key-value pairs and add each to the parent record before running my match clause...
I think I need to use lookup to initially populate the customPicklistValues and then merge them somehow?
Any help appreciated.
EDIT:
#whoami has suggested I use $addFields. There was a fair amount I needed to do before $addFields to populate the linked records (due to how Waterline via sails.js handles saving Mongo ObjectIDs in related collections as strings), you can see my steps in compass here:
Final step would be to edit this or add a stage to it to actually be able to support a key:value pair like Business Group: "Finance" in this example.
You can try these stages after your $lookup stage :
db.collection.aggregate([
{
$addFields: {
customPicklistValues:
{
$arrayToObject: {
$map: {
input: '$customPicklistValues',
in: { k: '$$this.fieldName', v: '$$this.value' }
}
}
}
}
},
{ $replaceRoot: { newRoot: { $mergeObjects: ['$customPicklistValues', '$$ROOT'] } } },
{ $project: { customPicklistValues: 0 } }
])
Test : MongoDB-Playground

How to query an object in an array embedded in mongodb?

I am currently working on an application that takes control of Projects, which have Meetings and that these meetings have Participants.
I want to consult a Participant by his nomina field.
Structure for a project document object:
{
"id":"5c1b0616a0441f27f022bfdc",
"name":"Project Test",
"area":"Area",
"date":"2019-01-01",
"meetings":[
{
"id":"5c1b073d445707834699ce97",
"objetive":"Objetive",
"fecha":"2019-01-01",
"participants":[
{
"nomina":1,
"name":"Person 1",
"role":"Rol1",
"area":"area1",
"signature":null
},
{
"nomina":2,
"name":"Person 2",
"role":"rol 2",
"area":"área 2",
"signature":null
}
]
}
]
}
Expected behavior
I want to consult a Participant by nomina field knowing the id of the Project and also knowing the id of the Meeting.
Expected output
Having:
id Project = 5c1b0616a0441f27f022bfdc
id Meeting = 5c1b073d445707834699ce97
nomina Participant = 1
It's expected that the query will return me:
{
"nomina":1,
"name":"Person 1",
"role":"Rol1",
"area":"area1",
"signature":null
}
For not so huge number of meetings in every document if you want to get the exact document stated, you can do this pipeline, it is straight forward:
db.collection.aggregate(
[
{
$match: {
id:"5c1b0616a0441f27f022bfdc"
}
}, {
$unwind: {
path : "$meetings"
}
},
{
$unwind: {
path : "$meetings.participants"
}
},
{
$match: {
"meetings.id":"5c1b073d445707834699ce97",
"meetings.participants.nomina":1
}
},
{
$replaceRoot: {
newRoot: "$meetings.participants"
}
}
]);
If you would have over thousands of elements in meetings then I'd suggest adding another match to meetings or grouping meetings and project IDs.
But if you just want to get the document containing what you want it is just a simple find query:
db.collection.find({id:"5c1b0616a0441f27f022bfdc","meetings.id":"5c1b073d445707834699ce97","meetings.participants.nomina":1 });

Mongo find value with unknown parent key

I am looking for a value in a Mongo table where its parent key might not have a descriptive or known name. Here is an example of what one of our documents looks like.
{
"assetsId": {
"0": "546cf2f8585ffa451bb68369"
},
"slotTypes": {
"0": { "usage": "json" },
"1": { "usage": "image" }
}
}
I am looking to see if this contains "usage": "json" in slotTypes, but I can't guarantee that the parent key for this usage will be "0".
I tried using the following query without any luck:
db.documents.find(
{
slotTypes:
{
$elemMatch:
{
"usage": "json"
}
}
}
)
Sorry in advance if this is a really basic question, but I'm not used to working in a nosql database.
I'm not sure you're going to be able to solve elegantly this with your current schema; slotTypes should be an array of sub-documents, which would allow your $elemMatch query to work. Right now, it's an object with numeric-ish keys.
That is, your document schema should be something like:
{
"assetsId": {
"0": "546cf2f8585ffa451bb68369"
},
"slotTypes": [
{ "usage": "json" },
{ "usage": "image" }
]
}
If changing the data layout isn't an option, then you're going to need to basically scan through every document to find matches with $where. This is slow, unindexable, and awkward.
db.objects.find({$where: function() {
for(var key in this.slotTypes) {
if (this.slotTypes[key].usage == "json") return true;
}
return false;
}})
You should read the documentation on $where to make sure you understand the caveats of it, and for the love of all that is holy, sanitize your inputs to the function; this is live code that is executing in the context of your database.

Complex MongoDB query with multiple OR

I'm using mongodb and I've built to make the following query.
1 AccessToken.where('resource_owner_id').equals(event.resource_owner_id)
2 .where('revoked_at').equals(undefined)
3 .or([ { scopes: /resources/i }, { scopes: new RegExp(event.resource,'i') } ])
4 .or([ { device_ids: [] }, { device_ids: event.body.id } ])
For this example I'm using Mongoose and coffescript.
Unluckily it doesn't work as I want, mainly for the or statement. What I want is the two or on row 3 and 4 being independent.
This means that the field scopes (row 3) must contain the string resources or the string stored in event.resource, meanwhile the field device_ids must be empty or contain the id event.body.id.
From the tests I've prepared I can see that the or command put all together. This means that when just one of the four or conditions is satisfied I get the document.
- { scopes: /resources/i }
- { scopes: new RegExp(event.resource,'i') }
- { device_ids: [] }
- { device_ids: event.body.id }
What I'm not able to reach is to split them in two groups so that both the conditions related to the field scopes and the conditions related to the field device_ids can be satisfied.
Hope the problem is well described.
Thanks.
Don't use $where unless you absolutely have to as performance is terrible. Use a combination of the $or and $size operators instead:
UPDATE There's no Mongoose and method so you have to use the $and operator directly.
AccessToken.find({
resource_owner_id: event.resource_owner_id,
revoked_at: undefined,
$and: [
{ $or: [{ scopes: /resources/i }, { scopes: new RegExp(event.resource,'i') }] },
{ $or: [{ device_ids: { $size: 0 } }, { device_ids: event.body.id }] }
]
}, callback);