Updating a nested Array in using UpdateOne() - mongodb

I'm having an issue updating a nested Array in a document. Reading around the topic i've come across various method, one that i've tweaked below, however nothing seems to work for me!
I'm trying to update the field systemUpdate_DT which is in a parent Array called List and a child array called customData. I'm referring to the object in the child array using the key _id of the parent array and key field_id in the child array.
How do I update the systemUpdate_DT of the respective object?
Live Example: https://mongoplayground.net/p/453OFPOQqBp
A document in the collection looks like:
[
{
"_id": "6032a5ad80443334a35f2232",
"List": [
{
"_id": "6032a5af80443334a35f2234",
"customData": [
{
"_id": "6032a5bc80443334a35f223c",
"systemUpdate_DT": null,
"field_id": "6032a5bc80443334a35f223b"
},
{
"_id": "6032a5c280443334a35f223e",
"systemUpdate_DT": null,
"field_id": "6032a5c280443334a35f223d"
}
]
},
{
"_id": "6032a5b080443334a35f2236",
"customData": [
{
"_id": "6032a5bc80443334a35f223c",
"systemUpdate_DT": null,
"field_id": "6032a5bc80443334a35f223b"
},
{
"_id": "6032a5c280443334a35f223e",
"systemUpdate_DT": null,
"field_id": "6032a5c280443334a35f223d"
}
]
}
]
}
]
My Update Query looks like:
db.collection.updateOne({
{
"List._id": mongodb.ObjectId("6032a5af80443334a35f2234"),
"List.customData.field_id": mongodb.ObjectId("6032a5bc80443334a35f223b")
},
{
$set: {
"List.$.customData.systemUpdate_DT": 'updatedDTTM'
}
})

As there's two nested arrays in your document, you can't set the field with classic positional operator '$'.
Instead, you should use the arrayFilters option like this:
db.collection.update({
"_id": ObjectId("6032a5ad80443334a35f2232")
},
{
$set: {
"List.$[list].customData.$[customData].systemUpdate_DT": "updatedDTTM"
}
},
{
"multi": false,
"upsert": false,
arrayFilters: [
{
"list._id": {
"$eq": ObjectId("6032a5af80443334a35f2234")
}
},
{
"customData._id": {
"$eq": ObjectId("6032a5bc80443334a35f223c")
}
}
]
})
try it online: mongoplayground.net/p/fb_86rNUKvt

Related

MongoDB use array field's element to $set a new field of the document

In the database, I have documents like the following
Ticket {
"eventHistory": [
{
"event": "CREATED",
"timestamp": "aa-bb-cccc"
},
{
"event": "ASSIGNED",
"timestamp": "ii-jj-kkkk"
},
...
{
"event": "CLOSED",
"timestamp": "xx-yy-zzzz"
}
]
}
I would like to add a closedAt field to the relevant Tickets, getting the value from the eventHistory array's last element. The resultant document would look like the following
Ticket {
"eventHistory": [
{
"event": "CREATED",
"timestamp": "aa-bb-cccc"
},
{
"event": "ASSIGNED",
"timestamp": "ii-jj-kkkk"
},
...
{
"event": "CLOSED",
"timestamp": "xx-yy-zzzz"
}
],
"closedAt": "xx-yy-zzzz"
}
The following pipeline allows me to use the entire object that's present as the eventHistory array's last element.
db.collection.updateMany(
<query>,
[
"$set": {
"closedAt": {
"$arrayElemAt": [
"$eventHistory",
-1
]
}
}
]
...
)
But I want to use only the timestamp field; not the entire object.
Please help me adjust (and/or improve) the pipeline.
One option to fix your query is:
db.collection.updateMany(
<query>,
[
{
$set: {
"Ticket.closedAt": {
$last: "$Ticket.eventHistory.timestamp"
}
}
}
])
See how it works on the playground example
But note that you assume that last item is a closing one. Is this necessarily the case? Otherwise you can validate it.

Mongo DB aggregate match not returning value

I have the following mongo db schema and I am trying to build an aggregate query that searches under github_open_issues under the repo key and can return me a match for all the values with repoA as the value. I have tried the following as my query however its not returning any result. Im a bit confused why this is not working as I have another db with a schema similar to this and this type of query works there but here something seems to be different and is not working. I have also put together this interactive example mongoplayground
query
db.collection.aggregate([
{
"$unwind": "$github_open_issues"
},
{
"$match": {
"github_open_issues.repo": {
"$in": [
"repoA"
]
}
}
},
])
schema
[
{
"github_open_issues": {
"0": {
"git_url": "https://github.com/",
"git_assignees": "None",
"git_open_date": "2019-09-26",
"git_id": 253113,
"repo": "repoA",
"git_user": "userA",
"state": "open"
},
"1": {
"git_url": "https://github.com/",
"git_assignees": "None",
"git_open_date": "2019-11-15",
"git_id": 294398,
"repo": "repoB",
"git_user": "userB",
"state": "open"
},
"2": {
"git_url": "https://github.com/",
"git_assignees": "None",
"git_open_date": "2021-04-12",
"git_id": 661208,
"repo": "repoA",
"state": "open"
}
},
"unique_label_seen": {
"568": {
"label_name": "some label",
"times_seen": 12,
"535": {
"label_name": "another label",
"times_seen": 1
}
}
}
}
]
$objectToArray convert github_open_issues object to array in key-value format
$filter to iterate loop of above converted array and filter your search condition
$match to filter github_open_issues not empty
$arrayToObject convert github_open_issues array to object
db.collection.aggregate([
{
$addFields: {
github_open_issues: {
$filter: {
input: { $objectToArray: "$github_open_issues" },
cond: { $in: ["$$this.v.repo", ["repoA"]] }
}
}
}
},
{ $match: { github_open_issues: { $ne: [] } } },
{ $addFields: { github_open_issues: { $arrayToObject: "$github_open_issues" } } }
])
Playground
You query is correct but you data in schema placed wrong inside github_open_issues.repo your objects are place by numbers like {"0": {values... }, "1":{values... }} which cannot get your desired value. You can check the playground now playground

If condition in MongoDB for Nested JSON to retrieve a particular value

I've nested JSON like this. I want to retrieve the value of "_value" in second level. i,e. "Living Organisms" This is my JSON document.
{
"name": "Biology Book",
"data": {
"toc": {
"_version": "1",
"ge": [
{
"_name": "The Fundamental Unit of Life",
"_id": "5a",
"ge": [
{
"_value": "Living Organisms",
"_id": "5b"
}
]
}
]
}
}
}
This is what I've tried, using the "_id", I want to retrieve it's "_value"
db.products.aggregate([{"$match":{ "data.toc.ge.ge._id": "5b"}}])
This is the closest I could get to the output you mentioned in the comment above. Hope it helps.
db.collection.aggregate([
{
$match: {
"data.toc.ge.ge._id": "5b"
}
},
{
$unwind: "$data.toc.ge"
},
{
$unwind: "$data.toc.ge.ge"
},
{
$group: {
_id: null,
book: {
$push: "$data.toc.ge.ge._value"
}
}
},
{
$project: {
_id: 0,
first: {
$arrayElemAt: [
"$book",
0
]
},
}
}
])
Output:
[
{
"first": "Living Organisms"
}
]
You can check what I tried here
If you are using Mongoid:
(1..6).inject(Model.where('data.toc.ge.ge._id' => '5b').pluck('data.toc.ge.ge._value').first) { |v| v.values.first rescue v.first rescue v }
# => "Living Organisms"
6 is the number of containers to trim from the output (4 hashes and 2 arrays).
If I understand your question correctly, you only care about _value, so it sounds like you might want to use a projection:
db.products.aggregate([{"$match":{ "data.toc.ge.ge._id": "5b"}}, { "$project": {"data.toc.ge.ge._value": 1}}])

MongoDb aggregation project onto collection

I've a problem with a huge MongoDb aggregation pipeline. I've many constraint and I've simplified the problem a lot. Hence, don't discuss the goal for this query.
I've a mongo aggregation that gives something similar to this:
[
{
"content": {
"processes": [
{
"id": "101a",
"title": "delivery"
},
{
"id": "101b",
"title": "feedback"
}
]
}
}
]
To this intermediate result I'm forced to apply a project operation in order to obtain something similar to this:
[
{
"results":
{
"titles": [
{
"id": "101a",
"value": "delivery"
},
{
"id": "101b",
"value": "feedback"
}
]
}
}
]
enter code here
But applying this projections:
"results.titles.id": "$content.processes.id",
"results.titles.value": "$content.processes.title"
I obtain this:
[
{
"results":
{
"titles": {
"id": ["101a", "101b"]
"value": ["delivery", "feedback"]
}
}
}
}
]
Collection are created but not in the proper position.
Is it possible to exploit some operator inside the project operation in order to tell mongo to create an array in a parent position?
Something like this:
"results.titles.$[x].value" : "$content.processes.value"
You can use the dot notation to project entire array:
db.col.aggregate([
{
$project: {
"results.titles": "$content.processes"
}
}
])
and if you need to rename title to value then you have to apply $map operator:
db.col.aggregate([
{
$project: {
"results.titles": {
$map: {
input: "$content.processes",
as: "process",
in: {
id: "$$process.id",
value: "$$process.title"
}
}
}
}
}
])

Aggregate documents where objects in array matches multiple conditions

I have a collection with documents similar to such:
{
"_id": ObjectId("xxxxx"),
"item": [
{ "property": ["attr1", "+1"] },
{ "property": ["attr2", "-1"] }
]
}
{
"_id": ObjectId("xxxxy"),
"item": [
{ "property": ["attr1", "-1"] },
{ "property": ["attr2", "0"] }
]
}
{
"_id": ObjectId("xxxxz"),
"item": [
{ "property": ["attr1", "0"] },
{ "property": ["attr2", "+1"] }
]
}
Preferably using an aggregation pipeline, is there any way to match the document if and only if any one of the properties match more than one condition?
For example, I want a query where one object in the array matches both of these conditions:
("item.property": "attr1") AND ("item.property": /^\+/)
That is, a single property where it contains "attr1" and an element that starts with "+".
However, using my current query that looks like this:
collection.aggregate(
{ $match:
{ $and:
[
{ "item.property": "attr1" },
{ "item.property": /^\+/ }
]
}
}
This would match both the first and last document because both contain a property with "attr1" and an element that stats with "+". However, I do not want this query to match the last document, since the element that starts with "+" does not belong to the same object in the array.
Is there any way to achieve this behavior using the aggregation framework?
You can use the below query with $elemMatch to match the array's both values.
Something like
db.collection_name.aggregate({
"$match": {
"item": {
"$elemMatch": {
"property.0": "attr1",
"property.1": /^\+/
}
}
}
});
Also, you can use $all operator if you don't want to reference array index.
db.collection_name.aggregate({
"$match": {
"item": {
"$elemMatch": {
"property": {
"$all": [
"attr1",
/^\+/
]
}
}
}
}
});