Remove multiple objects from deeply nested array 2 - mongodb

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.

Related

Remove multiple objects from nested array 4

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

Remove multiple inconsistent objects from double nested array 5

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

Remove multiple objects from nested array 3

I try to clean my collection with single update query , need to remove some deeply nested objects , but without breaking other objects , here is a good solution provided by #rickhg12hs:
Remove multiple objects from deeply nested array 2
but it has small drawback , it is breaking the content of _a._p object when there is no _a._p.s object inside...
and original solution provided by #nimrod serok:
Remove multiple elements from deep nested array with single update query
but it has other issue , when there is missing "_a._p.s.c" , "_a._p.s.d" or "_a._p.s.a" object it add objects with null values instead which afcourse is not expected ...
Playground test
This are 2x example original documents:
[
{
"_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"
}
]
}
}
}
}
]
},
{
"_id": ObjectId("5c05984246a0201286d4b57b"),
f: "x",
"_a": [
{
"_onlineStore": {}
},
{
"_p": {
_t: "Some field",
_x: "Some other field"
}
}
]
}
]
Expected result after update:
[
{
"_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
}
]
}
}
}
}
],
"_id": ObjectId("5c05984246a0201286d4b57a"),
"f": "x"
},
{
"_a": [
{
"_onlineStore": {}
},
{
"_p": {
_t: "Some field",
_x: "Some other field"
}
}
],
"_id": ObjectId("5c05984246a0201286d4b57b"),
"f": "x"
}
]
The goal is with single update query to remove any objects under _a._p.s.[a|c|d].t where the fields did,dst and den are missing but without breaking other objects _a._p where _a._p.s do not exists ...
Looks like a small change to #rickhg12hs's answer can solve this:
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",
{
_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

$addfield with three $cond mongodb

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.

Mongoose find by array of ids, but with repetitive results

Consider we have this array of IDs, which contains repetitive values.
[
'A',
'B',
'C',
'C',
'C',
'D',
.
.
.
]
How can I select the match for each ID from the DB? I don't need unique rows. I mean the result array must contain three same rows for ID C. One to one pointing. thanks.
It's better to do it on the client-side since you may just create lots of duplicated data on server and return to client.
db.collection.aggregate([
{
"$match": {
_id: {
"$in": [ "A", "B", "C", "C", "C", "D" ]
}
}
},
{
"$group": {
"_id": null,
"fields": {
"$push": "$$ROOT"
}
}
},
{
"$set": {
"fields": {
"$map": {
"input": [ "A", "B", "C", "C", "C", "D" ],
"as": "id",
"in": {
$let: {
vars: {
"findOne": {
"$first": {
"$filter": {
"input": "$fields",
"as": "item",
"cond": {
"$eq": [ "$$item._id", "$$id" ]
}
}
}
}
},
in: {
"$cond": {
"if": "$$findOne",
"then": "$$findOne",
"else": { _id: "$$id" }
}
}
}
}
}
}
}
},
{
"$unwind": "$fields"
},
{
"$replaceRoot": { "newRoot": "$fields" }
}
])
mongoplayground