Search code examples
mongodbmongodb-query

Mongo query: find document where each embeded element equals to given value


I have a collection with embedded array of objects. I need a query to select only those documents where each of embedded objects prop equals to some value:

    {
     {
      _id: 1,
      objects: [{prop_1: ..., foo: true}, {prop_1: ..., foo: false}, {prop_1: ...}]
     },
     {
      _id: 2,
      objects: [{prop_1: ..., foo: false}, {prop_1: ..., foo: false}, {prop_1: ...}]
     },
     {
      _id: 3,
      objects: [
         {prop_1: ..., foo: true}, {prop_1: ..., foo: true}, {prop_1: ..., foo: true}
      ]
     }
    }

I need a query to get all the documents where each element of objects has foo === true. If one of objects.foo is not exists or not equals true do not return this document in result. In this case only record with _id : 3 meets this condition.


Solution

  • 1. you can use $allElementsTrue directly if checking explicitly true

    edit
    as pointed out by @aneroid this one's shorter if directly checking true

    db.collection.find({ $expr: { $allElementsTrue: "$objects.foo" } })
    

    playground

    db.collection.aggregate([
      {
        $match: {
          $expr: {
            $allElementsTrue: {
              $map: {
                input: "$objects",
                in: "$$this.foo"
              }
            }
          }
        }
      }
    ])
    

    playground

    or with a small addition if you want to check other values too

    db.collection.aggregate([
      {
        $match: {
          $expr: {
            $allElementsTrue: {
              $map: {
                input: "$objects",
                in: { $eq: [ "$$this.foo", yourValue ] }
              }
            }
          }
        }
      }
    ])
    

    playground

    2. or by comparing the filtered objects array size with the actual object array size

    db.collection.aggregate([
      {
        $match: {
          $expr: {
            $eq: [
              {
                $size: "$objects"
              },
              {
                $size: {
                  $filter: {
                    input: "$objects",
                    cond: { $eq: [ "$$this.foo", yourValue ] }
                  }
                }
              }
            ]
          }
        }
      }
    ])
    

    playground

    3. or using $reduce by keeping a boolean accumulator and by checking if it finally ends up being true

    db.collection.aggregate([
      {
        $match: {
          $expr: {
            $eq: [
              true,
              {
                $reduce: {
                  input: "$objects",
                  initialValue: true,
                  in: { $and: ["$$value", { $eq: ["$$this.foo", yourValue ] }] }
                }
              }
            ]
          }
        }
      }
    ]);
    
    

    playground

    4. or using $setIsSubset since now the first array (without dupes) is a subset(equal) of the second

    db.collection.aggregate([
      {
        $match: {
          $expr: {
            $setIsSubset: [
              { $map: { input: "$objects", in: "$$this.foo" } },
              [ yourValue ]
            ]
          }
        }
      }
    ])
    

    playground

    5. or with a .find by getting the negation of all the docs that do not have foo as yourValue

    db.collection.find({
      objects: {
        $not: {
          $elemMatch: { foo: { $ne: yourValue } }
        }
      }
    })
    

    playground