here is another challenge:
I need to clean my data from incorrect objects , objects under the array "t" that contain did , dst and den fields are considered correct , #nimrok serok / #rickhg12hs helped with a working solution , but still there is some edge cases where none of objects are valid and stay empty array after the update , so I am wondering if those can be cleared in same update query?
example document:
{
"_id": ObjectId("5c05984246a0201286d4b57a"),
f: "x",
"_a": [
{
"_onlineStore": {}
},
{
"_p": {
"pid": 1,
"s": {
"a": {
"t": [
{
id: 1,
"dateP": "20200-09-20",
did: "x",
dst: "y",
den: "z"
},
{
id: 2,
"dateP": "20200-09-20"
}
]
},
"c": {
"t": [
{
id: 3,
"dateP": "20300-09-22"
},
{
id: 4,
"dateP": "20300-09-23",
}
]
}
},
h: "This must stay"
}
},
{
"_p": {
"pid": 2,
"s": {
"a": {
"t": [
{
id: 1,
"dateP": "20200-09-20",
}
]
},
"c": {
"t": [
{
id: 3,
"dateP": "20300-09-22"
},
{
id: 4,
"dateP": "20300-09-23",
}
]
}
},
h: "This must stay"
}
},
{
x: "This must stay"
}
]
}
Expected output:
{
"_a": [
{
"_onlineStore": {}
},
{
"_p": {
"h": "This must stay",
"pid": 1,
"s": {
"a": {
"t": [
{
"dateP": "20200-09-20",
"den": "z",
"did": "x",
"dst": "y",
"id": 1
}
]
}
}
}
},
{
"_p": {
"h": "This must stay",
"pid": 2,
}
},
{
"x": "This must stay"
}
],
"_id": ObjectId("5c05984246a0201286d4b57a"),
"f": "x"
}
Playground
(As you can see in the playground example , job is almost done , just for cases where all array elements are wrong the array stay empty , so it need to be removed as well ...)
mongodb version 4.4
It touk me some time , but here is the solution for those who face similar problem:
db.collection.update({},
[
{
"$set": {
_a2: {
$filter: {
input: "$_a",
as: "elem",
cond: {
"$eq": [
{
"$type": "$$elem._p.s"
},
"missing"
]
}
}
},
_a: {
$filter: {
input: "$_a",
as: "elem",
cond: {
"$ne": [
{
"$type": "$$elem._p.s"
},
"missing"
]
}
}
}
}
},
{
"$set": {
"_a": {
"$map": {
"input": "$_a",
"as": "elem",
"in": {
"$mergeObjects": [
"$$elem",
{
"_p": {
"$mergeObjects": [
"$$elem._p",
{
s: {
"$arrayToObject": {
"$map": {
"input": {
"$objectToArray": "$$elem._p.s"
},
"as": "anyKey",
"in": {
"k": "$$anyKey.k",
"v": {
"t": {
"$filter": {
"input": "$$anyKey.v.t",
"as": "t",
"cond": {
"$setIsSubset": [
[
"did",
"dst",
"den"
],
{
"$map": {
"input": {
"$objectToArray": "$$t"
},
"in": "$$this.k"
}
}
]
}
}
}
}
}
}
}
}
}
]
}
}
]
}
}
}
}
},
{
"$set": {
"_a": {
"$map": {
"input": "$_a",
"as": "elem",
"in": {
"$mergeObjects": [
"$$elem",
{
"_p": {
"$mergeObjects": [
"$$elem._p",
{
s: {
"$arrayToObject": {
"$filter": {
"input": {
"$objectToArray": "$$elem._p.s"
},
"as": "anyKey",
"cond": {
$ne: [
"$$anyKey.v.t",
[]
]
}
}
}
}
}
]
}
}
]
}
}
}
}
},
{
"$set": {
"_a": {
"$map": {
"input": "$_a",
"as": "elem",
"in": {
"$mergeObjects": [
"$$elem",
{
"_p": {
"$arrayToObject": {
"$filter": {
"input": {
"$objectToArray": "$$elem._p"
},
"as": "anyKey",
cond: {
$not: {
$in: [
"$$anyKey.v",
[
{}
]
]
}
}
}
}
}
}
]
}
}
}
}
},
{
$set: {
_a: {
"$concatArrays": [
"$_a2",
"$_a"
]
}
}
},
{
$unset: "_a2"
}
])
Explained:
Split the array in two arays via $set/$filter , _a2 (contain elements that will not be changed ) and _a ( contain the affected inconsistent )
$map/$mergeObjects/$mergeObjects/$map/$arrayToObject to remove the inconsistent objects inside _a[]._p.s.k.t[]
$map/$mergeObjects/$mergeObjects/$map/$arrayToObject/$filter to remove the empty _a[]._p.s.k.t[] arrays t with theyr keys k.
$map/$mergeObjects/$mergeObjects/$map/$arrayToObject/$filter to remove the empty _a[]._p.s:{} elements.
$concat on _a and _a2 to concatenete the fixed _a[] array elements with the ones that are correct and preserved in _a2[].
$unset the temporary array _a2[] since it has been already concatenated with _a[] in previous stage.
Special thanks to #nimrod serok & #rickhg12hs for the initial ideas!
Playground
Related
please, help in my attempt to clean my documents from corrupted sub-objects , few solutions proposed in previous questions work for most of the cases , but there is specific cases where there is more objects at same nested level that shall not be cleaned:
Example document:
{
"_id": ObjectId("5c05984246a0201286d4b57a"),
f: "x",
"_a": [
{
"_onlineStore": {}
},
{
"_p": {
"s": {
"a": {
"t": [
{
id: 1,
"dateP": "20200-09-20",
did: "x",
dst: "y",
den: "z"
},
{
id: 2,
"dateP": "20200-09-20"
}
]
},
"c": {
"t": [
{
id: 3,
"dateP": "20300-09-22"
},
{
id: 4,
"dateP": "20300-09-23",
did: "x",
dst: "y",
den: "z"
},
{
id: 5,
"dateP": "20300-09-23"
}
]
}
},
h: "This is cleaned but it shauld not"
}
}
]
}
All objects where did,dst,den are missing from _a._p.s.[a|c|d].t need to be removed ,
expected result:
[
{
"_a": [
{
"_onlineStore": {}
},
{
"_p": {
"s": {
"a": {
"t": [
{
"dateP": "20200-09-20",
"den": "z",
"did": "x",
"dst": "y",
"id": 1
}
]
},
"c": {
"t": [
{
"dateP": "20300-09-23",
"den": "z",
"did": "x",
"dst": "y",
"id": 4
}
]
}
},
h: "This is cleaned but it shauld not"
}
}
],
"_id": ObjectId("5c05984246a0201286d4b57a"),
"f": "x"
}
]
Very good solutions provided by #nimrod serok & #rickhg12hs here: , but unfortunatelly not working for all cases , for example for cases where there is more key/values at the level "_a._p" beside "s" the other key/values beside "s" are cleaned like _a._p.h:"..." in the example , please, advice if there is any easy option to be solved with mongo update query?
Playground example
One option is to add $mergeObjects to the party:
db.collection.update({},
[
{
"$set": {
"_a": {
"$map": {
"input": "$_a",
"as": "elem",
"in": {
"$cond": [
{
$or: [
{
"$eq": [
{
"$type": "$$elem._p"
},
"missing"
]
},
{
"$eq": [
{
"$type": "$$elem._p.s"
},
"missing"
]
}
]
},
"$$elem",
{
$mergeObjects: [
"$$elem._p",
{
"s": {
"$arrayToObject": {
"$map": {
"input": {
"$objectToArray": "$$elem._p.s"
},
"as": "anyKey",
"in": {
"k": "$$anyKey.k",
"v": {
"t": {
"$filter": {
"input": "$$anyKey.v.t",
"as": "t",
"cond": {
"$setIsSubset": [
[
"did",
"dst",
"den"
],
{
"$map": {
"input": {
"$objectToArray": "$$t"
},
"in": "$$this.k"
}
}
]
}
}
}
}
}
}
}
}
}
]
}
]
}
}
}
}
}
],
{
"multi": true
})
See how it works on the playground example
I need to remove some inconsistent objects not having did,dst and den from deeply nested array , please, advice if this can be done with single update query for all documents in the collection ?
This is example of my original document:
[
{
"_id": ObjectId("5c05984246a0201286d4b57a"),
f: "x",
"_a": [
{
"_onlineStore": {}
},
{
"_p": {
"s": {
"a": {
"t": [
{
id: 1,
"dateP": "20200-09-20",
did: "x",
dst: "y",
den: "z"
},
{
id: 2,
"dateP": "20200-09-20"
}
]
},
"c": {
"t": [
{
id: 3,
"dateP": "20300-09-22",
},
{
id: 4,
"dateP": "20300-09-23",
did: "x",
dst: "y",
den: "z"
},
{
id: 5,
"dateP": "20300-09-23",
}
]
}
}
}
}
]
}
]
After the update , the document need to look as follow:
[
{
"_id": ObjectId("5c05984246a0201286d4b57a"),
f: "x",
"_a": [
{
"_onlineStore": {}
},
{
"_p": {
"s": {
"a": {
"t": [
{
id: 1,
"dateP": "20200-09-20",
did: "x",
dst: "y",
den: "z"
}
]
},
"c": {
"t": [
{
id: 4,
"dateP": "20300-09-23",
did: "x",
dst: "y",
den: "z"
}
]
}
}
}
}
]
}
]
Please, note a.t , c.t and d.t are all possible objects inside s object , but they are not compulsory in all documents so in some documents they can be missing , in other documents there can be only a.t and c.t ,but not d.t ...
#nimrod serok helped with a partial solution here:
Remove multiple elements from deep nested array with single update query
, but there is a small drawback , missing a,c, or d objects in original document do not need to appear in the resulting document as null since they do not exist and not expected:
playground
( d.t:null and c.t:null shall not appear after the update )
Here's one way you could do it where the field name after _p.s could be anything. It feels a bit fragile though since all the other field names and depths need to be constant.
db.collection.update({},
[
{
"$set": {
"_a": {
"$map": {
"input": "$_a",
"as": "elem",
"in": {
"$cond": [
{"$eq": [{"$type": "$$elem._p"}, "missing"]},
"$$elem",
{
"_p": {
"s": {
"$arrayToObject": {
"$map": {
"input": {"$objectToArray": "$$elem._p.s"},
"as": "anyKey",
"in": {
"k": "$$anyKey.k",
"v": {
"t": {
"$filter": {
"input": "$$anyKey.v.t",
"as": "t",
"cond": {
"$setIsSubset": [
["did", "dst", "den"],
{
"$map": {
"input": {"$objectToArray": "$$t"},
"in": "$$this.k"
}
}
]
}
}
}
}
}
}
}
}
}
}
]
}
}
}
}
}
],
{"multi": true}
)
Try it on mongoplayground.net.
I have this output data from aggregation $lookup
[
{
_id: 1,
name: "Abraham",
class: "V",
question_answered: [
{
id: "quest1",
answer: "A",
score: 10,
question: {
soal: "apa judul lagu?",
correct_answer: "A",
type_question: "Essay"
}
},
{
id: "quest2",
answer: "C",
score: null,
question: {
soal: "apa judul lagu B?",
correct_answer: "B",
type_question: "Essay"
}
},
{
id: "quest3",
answer: "C",
score: 10,
question: {
soal: "apa judul lagu C?",
correct_answer: "C",
type_question: "essay_pg"
}
},
]
},
{
_id: 2,
name: "Brenda",
class: "V",
question_answered: [
{
id: "quest1",
answer: "A",
score: 10,
question: {
soal: "apa judul lagu A?",
correct_answer: "A",
type_question: "Essay"
}
},
{
id: "quest2",
answer: "C",
score: 0,
question: {
soal: "apa judul lagu B?",
correct_answer: "B",
type_question: "Essay"
}
}
]
}
]
I need to add additional field formated_status_evaluation_essay and formated_status_evaluation_essay_pg in each data that i get with some few condition if,elseif, else. i'll give one of example addfield condition, more or less like this one:
IF(question_answered.question.type_question == 'Essay' and no score is
null in every essay type question) then,
formated_status_evaluation_essay = "complete scoring".
ELSEIF(there's essay type question and have at least one null score)
then, formated_status_evaluation_essay = "Incomplete scoring"
ELSEIF(if theres no essay type question) then,
formated_status_evaluation_essay = "no question"
Same goes to formated_status_evaluation_essay_pg. The output that i expected is like this.
[
{
_id: 1,
name: "Abraham",
class: "V",
question_answered: [....],
formated_status_evaluation_essay: incomplete scoring,
formated_status_evaluation_essay_pg: complete scoring,
},
{
_id: 2,
name: "Brenda",
class: "V",
question_answered: [....],
formated_status_evaluation_essay: complete scoring,
formated_status_evaluation_essay_pg: no question,
}
]
The explanation about the output.
_id:1, get evaluation_essay incomplete because it has one object that contain null score. But the evaluation_essay_pg contain complete
scoring because essay_pg type all of it have a score.
_id:2, evaluation_essay is complete because all question with type essay have a score. But essay_pg contain no question because theres no essay_pg type in question_answer.question.type_question.
I've tried this and still confuse to code three condition like i've explained before. I put code like this in the end of $lookup aggregation.
{
'$addFields': {
'formated_status_evaluation_essay': {
'$cond': [
{
'$and': [
{'$$question_answer.question.type_soal ':
'essay'},
{'$$question_answer.nilai':{$ne:null}},
]
},
'already scoring',
'havent scoring'
]
}
}
}
i almost get what i expected but, seems still have a wrong syntax i wrote. I would be very thankfull if you guys can help me. Been working for two days still got no answer.
Try to make the code a little bit more readable by using $switch to handle the branching.
db.collection.aggregate([
{
"$addFields": {
"formated_status_evaluation_essay": {
"$filter": {
"input": "$question_answered",
"as": "q",
"cond": {
$eq: [
"$$q.question.type_question",
"Essay"
]
}
}
},
"formated_status_evaluation_essay_pg": {
"$filter": {
"input": "$question_answered",
"as": "q",
"cond": {
$eq: [
"$$q.question.type_question",
"essay_pg"
]
}
}
}
}
},
{
"$addFields": {
"formated_status_evaluation_essay": {
"$switch": {
"branches": [
{
"case": {
$and: [
{
"$allElementsTrue": [
{
"$map": {
"input": "$formated_status_evaluation_essay.score",
"as": "s",
"in": {
$ne: [
"$$s",
null
]
}
}
}
]
},
{
$ne: [
{
$size: "$formated_status_evaluation_essay"
},
0
]
}
]
},
"then": "complete scoring"
},
{
"case": {
"$anyElementTrue": [
{
"$map": {
"input": "$formated_status_evaluation_essay.score",
"as": "s",
"in": {
$eq: [
"$$s",
null
]
}
}
}
]
},
"then": "incomplete scoring"
}
],
default: "no question"
}
},
"formated_status_evaluation_essay_pg": {
"$switch": {
"branches": [
{
"case": {
$and: [
{
"$allElementsTrue": [
{
"$map": {
"input": "$formated_status_evaluation_essay_pg.score",
"as": "s",
"in": {
$ne: [
"$$s",
null
]
}
}
}
]
},
{
$ne: [
{
$size: "$formated_status_evaluation_essay_pg"
},
0
]
}
]
},
"then": "complete scoring"
},
{
"case": {
"$anyElementTrue": [
{
"$map": {
"input": "$formated_status_evaluation_essay_pg.score",
"as": "s",
"in": {
$eq: [
"$$s",
null
]
}
}
}
]
},
"then": "incomplete scoring"
}
],
default: "no question"
}
}
}
}
])
Here is the Mongo playground for your reference.
I've got a document in MongoDB like this:
{
"name": "wine",
"foodstuffSelectedPortions": {
"id_1": [
{
"foodstuffId": "f1",
"portion": {
"portionName": "portion name 1",
"portionWeight": {
"value": 1,
"unit": "KG"
}
}
},
{
"foodstuffId": "f2",
"portion": {
"portionName": "portion name 2",
"portionWeight": {
"value": 100,
"unit": "ML"
}
}
}
],
"id_2": [
{
"foodstuffId": "f3",
"portion": {
"portionName": "portion name 3",
"portionWeight": {
"value": 15,
"unit": "ML"
}
}
}
]
}
}
and I want to update foodstuffSelectedPortions.portion object into array that contains this object. So the expected result should look like this:
{
"name": "wine",
"foodstuffSelectedPortions": {
"id_1": [
{
"foodstuffId": "f1",
"portion": [
{
"portionName": "portion name 1",
"portionWeight": {
"value": 1,
"unit": "KG"
}
}
]
},
{
"foodstuffId": "f2",
"portion": [
{
"portionName": "portion name 2",
"portionWeight": {
"value": 100,
"unit": "ML"
}
}
]
}
],
"id_2": [
{
"foodstuffId": "f3",
"portion": [
{
"portionName": "portion name 3",
"portionWeight": {
"value": 15,
"unit": "ML"
}
}
]
}
]
}
}
I've tried this query:
db.foodstuff.update(
{ },
{ $set: { "foodstuffSelectedPortions.$[].portion": ["$foodstuffSelectedPortions.$[].portion"] } }
)
but it gives me an error: Cannot apply array updates to non-array element foodstuffSelectedPortions: which looks fine because the foodstuffSelectedPortions is an object not array.
How to write this query correctly? I use MongoDB 4.4.4 and Mongo Shell.
Query (the one you asked)
(do updateMany to update all to the new schema)
converts to array
map on array
map on nested array, replacing portion object with array
Test code here
db.collection.update({},
[
{
"$set": {
"foodstuffSelectedPortions": {
"$arrayToObject": {
"$map": {
"input": {
"$objectToArray": "$foodstuffSelectedPortions"
},
"in": {
"k": "$$f.k",
"v": {
"$map": {
"input": "$$f.v",
"in": {
"$mergeObjects": [
"$$f1",
{
"portion": [
"$$f1.portion"
]
}
]
},
"as": "f1"
}
}
},
"as": "f"
}
}
}
}
}
])
Query
(alternative schema with 2 levels of nesting instead of 3, and without data on keys)
An example why data on keys is bad(i guess there are exceptions), is why you are stuck, you wanted to update all, but you didn't know their names so you couldn't select them. If you had them in array you could do a map on all or you could use update operator to do update in all members of an array (like the way you tried to do it, and complained that is object)
*But dont use this schema unless it fits your data and your queries better.
Test code here
db.collection.update({},
[
{
"$set": {
"foodstuffSelectedPortions": {
"$reduce": {
"input": {
"$objectToArray": "$foodstuffSelectedPortions"
},
"initialValue": [],
"in": {
"$let": {
"vars": {
"f": "$$this"
},
"in": {
"$concatArrays": [
"$$value",
{
"$map": {
"input": "$$f.v",
"in": {
"$mergeObjects": [
"$$f1",
{
"name": "$$f.k",
"portion": [
"$$f1.portion"
]
}
]
},
"as": "f1"
}
}
]
}
}
}
}
}
}
}
])
Pipeline updates require MongoDB >=4.2
I have below data. I want to find value=v2 (remove others value which not equals to v2) in the inner array which belongs to name=name2. How to write aggregation for this? The hard part for me is filtering the nestedArray which only belongs to name=name2.
{
"_id": 1,
"array": [
{
"name": "name1",
"nestedArray": [
{
"value": "v1"
},
{
"value": "v2"
}
]
},
{
"name": "name2",
"nestedArray": [
{
"value": "v1"
},
{
"value": "v2"
}
]
}
]
}
And the desired output is below. Please note the value=v1 remains under name=name1 while value=v1 under name=name2 is removed.
{
"_id": 1,
"array": [
{
"name": "name1",
"nestedArray": [
{
"value": "v1"
},
{
"value": "v2"
}
]
},
{
"name": "name2",
"nestedArray": [
{
"value": "v2"
}
]
}
]
}
You can try,
$set to update array field, $map to iterate loop of array field, check condition if name is name2 then $filter to get matching value v2 documents from nestedArray field and $mergeObject merge objects with available objects
let name = "name2", value = "v2";
db.collection.aggregate([
{
$set: {
array: {
$map: {
input: "$array",
in: {
$mergeObjects: [
"$$this",
{
$cond: [
{ $eq: ["$$this.name", name] }, //name add here
{
nestedArray: {
$filter: {
input: "$$this.nestedArray",
cond: { $eq: ["$$this.value", value] } //value add here
}
}
},
{}
]
}
]
}
}
}
}
}
])
Playground
You can use the following aggregation query:
db.collection.aggregate([
{
$project: {
"array": {
"$concatArrays": [
{
"$filter": {
"input": "$array",
"as": "array",
"cond": {
"$ne": [
"$$array.name",
"name2"
]
}
}
},
{
"$filter": {
"input": {
"$map": {
"input": "$array",
"as": "array",
"in": {
"name": "$$array.name",
"nestedArray": {
"$filter": {
"input": "$$array.nestedArray",
"as": "nestedArray",
"cond": {
"$eq": [
"$$nestedArray.value",
"v2"
]
}
}
}
}
}
},
"as": "array",
"cond": {
"$eq": [
"$$array.name",
"name2"
]
}
}
}
]
}
}
}
])
MongoDB Playground