conditional addFields to embedded objects in MongoDb - mongodb

I am trying to add in values to a list of embedded objects based on another value within the object. A sample document looks like:
{
"array" : [{
"val1" : "a"
}, {
"val1" : "b"
}]
}
What I am trying to achieve is
{
"array" : [{
"val1" : "a",
"isVal1A": true
}, {
"val1" : "b",
"isVal1A": false
}]
}
How do I go about doing this using an aggregate pipeline? Thanks!

Try
Live version
db.collection.aggregate({
$addFields: {
array: {
$map: {
input: "$array",
as: "a",
in: {
$cond: [
{
$eq: [
"$$a.val1",
"a"
]
},
{
"$mergeObjects": [
"$$a",
{
"Isval1A": true
}
]
},
{
"$mergeObjects": [
"$$a",
{
"Isval1A": false
}
]
}
]
}
}
}
}
})

You can try,
$map to iterate loop of array, check condition if val1 is 'a' then returnb true otherwise false, $mergeObjects to merge current object and status field
db.collection.aggregate([
{
$addFields: {
array: {
$map: {
input: "$array",
in: {
$mergeObjects: [
"$$this",
{
isVal1A: {
$cond: [{ $eq: ["$$this.val1", "a"] }, true, false]
}
}
]
}
}
}
}
}
])
Playground

Related

How do I fetch only the first element from the array?

How do I fetch only the first element from the "topicsName" array?
Data I have input:
{
"_id" : ObjectId("606b7046a0ccf72222c00c2f"),
"groupId" : ObjectId("5f06cca74e51ba15f5167b86"),
"insertedAt" : "2021-04-05T20:17:10.144521Z",
"isActive" : true,
"staffId" : [
"606b6c34a0ccf72222c5a4df",
"606b6c48a0ccf722228aa035"
],
"subjectName" : "Maths",
"teamId" : ObjectId("6069a6a9a0ccf704e7f4b537"),
"updatedAt" : "2022-04-29T07:57:31.072067Z",
"syllabus" : [
{
"chapterId" : "626b9b94ae6cd2092024f3ee",
"chapterName" : "chap1",
"topicsName" : [
{
"topicId" : "626b9b94ae6cd2092024f3ef",
"topicName" : "1.1"
},
{
"topicId" : "626b9b94ae6cd2092024f3f0",
"topicName" : "1.2"
}
]
},
{
"chapterId" : "626b9b94ae6cd2092024f3f1",
"chapterName" : "chap2",
"topicsName" : [
{
"topicId" : "626b9b94ae6cd2092024f3f2",
"topicName" : "2.1"
},
{
"topicId" : "626b9b94ae6cd2092024f3f3",
"topicName" : "2.2"
}
]
}
]
}
The Query I used to try to fetch the element:- "topicId" : "626b9b94ae6cd2092024f3ef" from the
"topicsName" array.
db.subject_staff_database
.find(
{ _id: ObjectId("606b7046a0ccf72222c00c2f") },
{
syllabus: {
$elemMatch: {
chapterId: "626b9b94ae6cd2092024f3f1",
topicsName: { $elemMatch: { topicId: "626b9b94ae6cd2092024f3f2" } },
},
},
}
)
.pretty();
I was trying to fetch only the first element from the "topicsName" array, but it fetched both the elements in that array.
You can do the followings in an aggregation pipeline.
$match with your given id locate documents
$reduce to flatten the syllabus and topicsName arrays
$filter to get the expected element
db.collection.aggregate([
{
$match: {
"syllabus.topicsName.topicId": "626b9b94ae6cd2092024f3ef"
}
},
{
"$project": {
result: {
"$reduce": {
"input": "$syllabus.topicsName",
"initialValue": [],
"in": {
"$concatArrays": [
"$$value",
"$$this"
]
}
}
}
}
},
{
"$project": {
result: {
"$filter": {
"input": "$result",
"as": "r",
"cond": {
$eq: [
"$$r.topicId",
"626b9b94ae6cd2092024f3ef"
]
}
}
}
}
}
])
Here is the Mongo playground for your reference.
Welcome Ganesh Sowdepalli,
You are not only asking to "fetch only the first element from the array", but to fetch only the matching element of a nested array property of an object item in array.
Edit: (according to #ray's comment)
One way to do it is using an aggregation pipeline:
db.subject_staff_database.aggregate([
{
$match: {"_id": ObjectId("606b7046a0ccf72222c00c2f")}
},
{
$project: {
syllabus: {
$filter: {
input: "$syllabus",
as: "item",
cond: {$eq: ["$$item.chapterId", "626b9b94ae6cd2092024f3f1"
]
}
}
}
}
},
{
$unwind: "$syllabus"
},
{
$project: {
"syllabus.topicsName": {
$filter: {
input: "$syllabus.topicsName",
as: "item",
cond: {$eq: ["$$item.topicId", "626b9b94ae6cd2092024f3f2"]}
}
},
"syllabus.chapterId": 1,
"syllabus.chapterName": 1,
_id: 0
}
}
])
As you can see on this playground example.
If you want the actual first element, not by _id, look here on my first understanding to your question.
The aggregation pipeline allows us to do several operation on the results.
Since syllabus is an array that may contain more than one matching chapterId, we need to $filter it for the items we want.

How to remove field conditionally mongoodb

I have a collection and its documents look like:
{
_id: ObjectId('111111111122222222223333'),
my_array: [
{
id: ObjectId('777777777788888888889999')
name: 'foo'
},
{
id: ObjectId('77777777778888888888555')
name: 'foo2'
}
//...
]
//more attributes
}
However, some documents have my_array: [{}] (with one element which is an empty array).
How can I add conditionally a projection or remove it?
I have to add it to a mongo pipeline at the end of the query, and I want to get my_array only when it has at least one element which is not an empty object. If there's an empty object remove it.
I tried with $cond and $eq in a projection stage but it is not supported. Any suggestion to solve this?
Suppose you have documents like this with my_array field:
{ "my_array" : [ ] }
{ "my_array" : [ { "a" : 1 } ] } // #(1)
{ "my_array" : null }
{ "some_fld" : "some value" }
{ "my_array" : [ { } ] }
{ "my_array" : [ { "a" : 2 }, { "a" : 3 } ] } // #(2)
And, the following aggregation will filter and the result will have the two documents (1) and (2):
db.collection.aggregate([
{
$match: {
$expr: {
$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]
}
}
}
])
This also works with a find method:
db.collection.find({
$expr: {
$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]
}
})
To remove the my_array field, from a document when its empty, then you try this aggregation:
db.collection.aggregate([
{
$addFields: {
my_array: {
$cond: [
{$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]},
"$my_array",
"$$REMOVE"
]
}
}
}
])
The result:
{ }
{ "my_array" : [ { "a" : 1 } ] }
{ }
{ "a" : 1 }
{ }
{ "my_array" : [ { "a" : 2 }, { "a" : 3 } ] }
You can't do that in a query, however in an aggregations you can add $filter to you pipeline, like so:
db.collection.aggregate([
{
$project: {
my_array: {
$filter: {
input: "$my_array",
as: "elem",
cond: {
$ne: [
{},
"$$elem"
]
}
}
}
}
}
])
Mongo Playground
However unless this is "correct" behavior I suggest you clean up your database, it's much simpler to maintain "proper" structure than to update all your queries everywhere.
You can use this update to remove these objects:
db.collection.update({
"myarray": {}
},
[
{
"$set": {
"my_array": {
$filter: {
input: "$my_array",
as: "elem",
cond: {
$ne: [
{},
"$$elem"
]
}
}
}
}
},
],
{
"multi": false,
"upsert": false
})
Mongo Playground

mongo: how to project array elements excluding some values?

I'm using Mongo 3.6. How do I return array items which are NOT in a certain list of values?
Here's the doc:
{
"_id" : "myId",
"myList" : [
"a",
"b",
"c"
]
}
here's the query, which returns only the FIRST match:
db.getCollection('Item').find({_id:"myId"}, {
myList:{ $elemMatch:{
$not: { $in:["a"] }
}}
})
result:
{
"_id" : "myId",
"myList" : [ "b" ]
}
Expected result - how do I change the query above to return ALL matches for 'not in ["a"]'? Something that yields:
{
"_id" : "myId",
"myList" : [ "b", "c" ]
}
Any idea why items are missing?
User aggregate with
$filter
as below to get expected output:
db.getCollection("Item").aggregate([
{
$project: {
list: {
$filter: {
input: "$myList",
as: "item",
cond: { $ne: ["$$item", "a"] },
},
},
},
},
]);
It can be done with an aggregate
[
{
$match: {
_id: "myId"
}
},
{
$project: {
myList: {
$filter: {
input: "$myList",
as: "item",
cond: {
$not: {
$in: [
"$$item",
[
"a"
]
]
}
}
}
}
}
}
]
Try it on mongoplayground
You can get the expected result using Aggregation Pipeline...
db.getCollection("Item").aggregate([
{
$project: {
list: {
$filter: {
input: "$myList",
as: "item",
cond: {
$ne: [
"$$item",
"a"
]
},
},
},
},
},
])
this seems to be the only thing that works on Mongo 3.6 for me, although #AlexisG's solution works in the Mongo v5 playground as well but not on my Mongo 3.6 :-/
db.getCollection('Item').aggregate([
{$match:{_id:"myId"}},
{$project:{myList:1}},
{$unwind:{path:"$myList"}},
{$match:{myList:{$not:{$in:["a"]}}}}
])

how to project field in array with mongodb

my collection in mongo db like this:
{
name:"mehdi",
grades:
[
{
a:1,
b:[2,3,4],
c:3,
d:4,
e:5
},
{
a:11,
b:[22,33,44],
c:33,
d:44,
e:55
}
]
}
I want to get a result with project op to give me a specific field in an array like this:
{
name:"mehdi",
grades:
[
{
a:1,
b:2
},
{
a:11,
b:22
}
]
}
how can I do this?
You can use $map to select a,b fields using $type to determine whether it's an array or number:
db.collection.aggregate([
{
$project: {
grades: {
$map: {
input: "$grades",
in: {
a: { $cond: [ { $eq: [ { $type: "$$this.a" }, "array" ] }, { $arrayElemAt: [ "$$this.a", 0 ] }, "$$this.a" ] },
b: { $cond: [ { $eq: [ { $type: "$$this.b" }, "array" ] }, { $arrayElemAt: [ "$$this.b", 0 ] }, "$$this.b" ] },
}
}
}
}
}
])
Mongo Playground

Document transformation based type

Lets say I have a mongodb document
{
"id" : 1,
"types" : ["online" ,"offline"],
"applications" : [ ["online", "online"], ["offline"] ]
}
I would like to transformed into
{
"id" : 1,
"types" : [
{
"type" : "online",
"count" : 2,
"applications" : [ "online", "online"]
},
{
"type" : "offline",
"count" : 1,
"applications" : [ "offline" ]
}
]
}
here types and applications arrays can be of any size.
You can do that in two steps, use $map with $filter to match type with applications and then run $reduce to get count:
db.collection.aggregate([
{
$project: {
id: 1,
types: {
$map: {
input: "$types",
as: "type",
in: {
type: "$$type",
applications: {
$filter: {
input: "$applications",
as: "application",
cond: {
$allElementsTrue: {
$map: { input: "$$application", in: { $eq: [ "$$this", "$$type" ] } }
}
}
}
}
}
}
}
}
},
{
$addFields: {
types: {
$map: {
input: "$types",
in: {
$mergeObjects: [
"$$this",
{
count: {
$reduce: {
input: "$$this.applications",
initialValue: 0,
in: { $add: [ "$$value", { $size: "$$this" } ] }
}
}
}
]
}
}
}
}
}
])
You can decide whether it's better to use $allElementsTrue or $anyElementTrue when building your filtering criteria.
Mongo Playground