mongo: how to project array elements excluding some values? - mongodb

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"]}}}}
])

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

conditional addFields to embedded objects in 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

Mongodb: $march, $project and $filter array to get only the document back

I cannot reconstruct this: Retrieve only the queried element in an object array in MongoDB collection.
Remember, there are two id which should match and I want the Image: [] back.
This is my structur.
{
"_id" : ObjectId("5ee4e57a6e5a926bdeb1e406"),
"Display" : [
{
"_id" : ObjectId("5ee5b7db9245084840dc624f"),
"Image" : [
{Document I want},
{Document I want}]
}
]
}
My best try:
db.User.aggregate([
{"$match" :
{"_id": ObjectId("5ee4e57a6e5a926bdeb1e406")}
},{
"$project" :{
"Display" : {
$filter: {
input: ObjectId("5ee5b7db9245084840dc624f"),
as: "id",
cond: {
"$Display._id": "$$id"}
}
}
}
}]);
In your aggregation, you are missing $eq operator.
Aggregation approach:
db.collection.aggregate([
{
$match: {
_id: 1
}
},
{
$project: {
Display: {
$filter: {
input: "$Display",
as: "d",
cond: {
$eq: [
"$$d._id",
100
]
}
}
}
}
}
])
MongoPLayGroundLink
Find approach:
db.collection.find({
_id: 1,
"Display._id": 100
},
{
"Display.$": 1
})
MongoPLayGroundLink

MongoDB aggregation filter based on max value

Suppose I have a document structure where one of the fields, X, is an array av objects as shown below.
"X" : [
{
"A" : "abc",
"B" : 123
},
{
"A" : "wer",
"B" : 124
},
{
"A" : "fgh",
"B" : 124
}
]
How can I project only the document where field B has the highest values? And if the maximum value is shared by several documents, I just want to return one of them (not important which one). In this case the result could look like:
"X" : [
{
"A" : "wer",
"B" : 124
}
]
What about this one:
db.collection.aggregate([
{
$set: {
X: {
$filter: {
input: "$X",
cond: { $eq: ["$$this.B", { $max: "$X.B" }] }
}
}
}
},
{ $set: { X: { $arrayElemAt: ["$X", 0] } } }
])
You can use $reduce
db.collection.aggregate([
{
"$project": {
"X": {
$reduce: {
input: "$X",
initialValue: {},
in: {
$cond: [ { "$gt": [ "$$this.B", "$$value.B" ]}, // Condition Check
"$$this", // If condition true ($$this - Current Object)
"$$value" // If condition false $$value - Previous Returned Object
]
}
}
}
}
}
])
Mongo Playground
Updated answer:
Another option that results in the full object being returned at the end:
[
{$unwind: {
path: "$X"
}},
{$sort: {
"X.B": -1
}},
{$group: {
_id: { _id: "$_id"},
X: {
$first: "$X"
}
}}]
Original answer:
You can use the $max operator (https://docs.mongodb.com/manual/reference/operator/aggregation/max/).
[{$project: {
X: {$max: "$X.B"}
}}]