Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compare embedded document to parent field with mongoDB

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!

like image 439
Stefan Konno Avatar asked Jul 31 '17 09:07

Stefan Konno


People also ask

How do I access an embedded document in MongoDB?

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.

How do you find documents with a matching item in an embedded array?

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 be embedded in document with another 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).

How do I query an array field in MongoDB?

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>, ... } }


2 Answers

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.

like image 70
Neil Lunn Avatar answered Oct 25 '22 08:10

Neil Lunn


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

like image 1
oae Avatar answered Oct 25 '22 06:10

oae