Consider the following collection, where the parent document has a amount
field with the value 100000
and there's an embedded array of documents with the same field amount
and the same value.
{
"_id" : ObjectId("5975ce5f05563b6303924914"),
"amount" : 100000,
"offers" : [
{
"amount": 100000
}
]
}
Is there any way to match all objects that has at least one embedded document offer
with the same amount as the parent?
If I for example query this, it works just fine:
find({ offers: { $elemMatch: { loan_amount: 100000 } } })
But I don't know the actual value 100000
in the real query I'm trying to assemble, I would need to use a variable for the parent documents amount field. Something like this.
find({ offers: { $elemMatch: { loan_amount: "parent.loan_amount" } } })
Thankful for any suggestions. I was hoping to do this with $eq
or $elemMatch
, and to avoid aggregates, but maybe it's not possible.
Thanks!
Accessing embedded/nested documents – In MongoDB, you can access the fields of nested/embedded documents of the collection using dot notation and when you are using dot notation, then the field and the nested field must be inside the quotation marks.
Use $match With $eq to Find Matching Documents in an Array in MongoDB. Use $match With $all to Find Matching Documents in an Array in MongoDB.
When should we use embedded documents in MongoDB? Without knowing exactly how your applications will interact with the data, the following answer is only a general guidelines or approach : Favour the embedding, unless there is a reason not to. When the relationship is one to few (not many, not unlimited).
To query if the array field contains at least one element with the specified value, use the filter { <field>: <value> } where <value> is the element value. To specify conditions on the elements in the array field, use query operators in the query filter document: { <array field>: { <operator1>: <value1>, ... } }
Standard queries cannot "compare" values in documents. This is actually something you do using .aggregate()
and $redact
:
db.collection.aggregate([
{ "$redact": {
"$cond": {
"if": {
"$gt": [
{ "$size": {
"$filter": {
"input": "$offers",
"as": "o",
"cond": { "$eq": [ "$$o.amount", "$amount" ] }
}
}},
0
]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
Here we use $filter
to compare the values of "amount"
in the parent document to those within the array. If at least one is "equal" then we "$$KEEP"
the document, otherwise we "$$PRUNE"
In most recent versions, we can shorten that using $indexOfArray
.
db.collection.aggregate([
{ "$redact": {
"$cond": {
"if": {
"$ne": [
{ "$indexOfArray": [ "$offers.amount", "$amount" ] },
-1
]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
If you actually only wanted the "matching array element(s)" as well, then you would add a $filter
in projection:
db.collection.aggregate([
{ "$redact": {
"$cond": {
"if": {
"$gt": [
{ "$size": {
"$filter": {
"input": "$offers",
"as": "o",
"cond": { "$eq": [ "$$o.amount", "$amount" ] }
}
}},
0
]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}},
{ "$project": {
"amount": 1,
"offers": {
"$filter": {
"input": "$offers",
"as": "o",
"cond": { "$eq": [ "$$o.amount", "$amount" ] }
}
}
}}
])
But the main principle is of course to "reduce" the number of documents returned to only those that actually match the condition as a "first" priority. Otherwise you are just doing unnecessary calculations and work that is taking time and resources, for results that you later would discard.
So "filter" first, and "reshape" second as a priority.
I think since MongoDB version 3.6 you can actually do this with a simple filter using the expr operator.
Something along those lines:
find({
$expr: {
$in: [
"$amount",
"$offers.amount"
]
}
})
See a live example on mongoplayground.net
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With