In my collection, I have documents which contains an array (called values
) of objects, which has id
and val
fields. The data looks like this
[
{
values: [
{
"id": "123",
"val": true
},
{
"id": "456",
"val": true
},
{
"id": "789",
"val": false
},
]
},
{
values: [
{
"id": "123",
"val": true
},
{
"id": "123",
"val": true
},
{
"id": "123",
"val": false
},
]
},
{
values: [
{
"id": "234",
"val": false
},
{
"id": "567",
"val": false
}
]
}
]
I want to query this data by val
to ensure it is true
, and there may be cases where I want to ensure that an array
has 2 instances of val
‘s that is true
. I am able to achieve this with the following query:
db.collection.find({
values: {
"$elemMatch": {
val: true
}
},
$expr: {
$gte: [
{
$reduce: {
input: "$values",
initialValue: 0,
in: {
$sum: [
"$$value",
{
$cond: [
{
$eq: [
"$$this.val",
true
]
},
1,
0
]
}
]
}
}
},
2
]
}
})
The results of the query above give me the following result:
[
{
"values": [
{
"id": "123",
"val": true
},
{
"id": "456",
"val": true
},
{
"id": "789",
"val": false
}
]
},
{
"values": [
{
"id": "123",
"val": true
},
{
"id": "123",
"val": true
},
{
"id": "123",
"val": false
}
]
}
]
The issue with this now, is that I want to ensure that there are no duplicate id
‘s in the results of the $reduce
‘d list. I have researched the docs and $group
looks promising, however I am unsure on how to integrate this into the query.
I want the result to look like the following:
[
{
"values": [
{
"id": "123",
"val": true
},
{
"id": "456",
"val": true
},
{
"id": "789",
"val": false
}
]
}
]
Here is a MongoPlayground link with all of the above in it.
Please Note
I have simplified the code examples here to get to the core of the problem. In my actual use case, there are many values of different data types, which means that using an $elemMatch is the best way to go for me.
2
Answers
After looking at Takis's answer, it made me realise that using
$setUnion
is an alternative way to find the distinct values of a field. Using this, I was able to rework my query to achieve what I want.What I have done to achieve this is to have a
$cond
operator within the$expr
operator. I pass in the original$reduce
as the condition to see ifx
amount ofval
's appear within the document. If it succeeds, then I am ensuring that the size of the union ofvalues.id
's (i.e. all the unique id's within the current documentvalues
array) is at leastx
amount. If this condition is satisfied, the document will be returned. If not, then it falls back to the valuefalse
, i.e. it will not return the current document.The query is as follows:
Here is a MongoPlayground link showing it in action.
Query
true count >=2
values.id
length, with the length of the setvalues.id
(keep it only if same size else => duplicate), it works usingpaths on arrays = arrays like values.id is array of ids
*if this is slow for your use case maybe it can be faster
Playmongo