40

I have a collection that I'm performing an aggregation on and I've basically gotten it down to

{array:[1,2,3], value: 1},
{array:[1,2,3], value: 4}

How would I perform an aggregation match to check if the value is in the array? I tried using {$match: {"array: {$in: ["$value"]}}} but it doesn't find anything.

I would want the output (if using the above as an example) to be:

{array:[1,2,3], value:1}
2
  • 1
    I haven't tried this, but I think you probably want $elemMatch (see docs) rather than $in - you want to test whether a value is an the array, not whether the array is in the value! Commented May 29, 2015 at 19:19
  • 2
    You really need to come back and look at this question, as anyone who suggested $unwind gave you the worst possible performance answer you could implement in your code. There are much better alternatives submitted. Commented Jul 25, 2015 at 18:32

10 Answers 10

27

You can use aggregation expression in regular query in 3.6 version.

db.collection_name.find({"$expr": {"$in": ["$value", "$array"]}})

Using Aggregation:

You can use $match + $expr in current 3.6 version.

db.collection_name.aggregate({"$match": {"$expr": {"$in": ["$value", "$array"]}}})

You can try $redact + $in expression in 3.4 version.

db.collection_name.aggregate({
  "$redact": {
    "$cond": [
      {
        "$in": [
          "$value",
          "$array"
        ]
      },
      "$$KEEP",
      "$$PRUNE"
    ]
  }
})
Sign up to request clarification or add additional context in comments.

Comments

16

As stated, $where is a good option where you do not need to continue the logic in the aggregation pipeline.

But if you do then use $redact, with $map to transform the "value" into an array and use of $setIsSubSet to compare. It is the fastest way to do this since you do not need to duplicate documents using $unwind:

db.collection.aggregate([
   { "$redact": {
       "$cond": {
           "if": { "$setIsSubset": [
                { "$map": {
                    "input": { "$literal": ["A"] },
                    "as": "a",
                    "in": "$value"
                }},
                "$array"
           ]},
           "then": "$$KEEP",
           "else": "$$PRUNE"
       }
   }}
])

The $redact pipeline operator allows the proccessing of a logical condition within $cond and uses the special operations $$KEEP to "keep" the document where the logical condition is true or $$PRUNE to "remove" the document where the condition was false.

This allows it to work like $project with a subsequent $match, but in a single pipeline stage which is more efficient.

Considering these are native coded operators and not JavaScript then it is likely "the" fastest way to perform your match. So provided you are using a MongoDB 2.6 version or above, then this is the way you should be doing it to compare these elements in your document.

1 Comment

thumbs up for $redact. I didn't know about this one and it does exactly what I need: look in each document for a specific value in an array. Thanks!
11

A slight variation based on @chridam's answer:

db.test.aggregate([
    { "$unwind": "$array" },
    { "$group": {
                  _id: { "_id": "$_id", "value": "$value" },
                  array: { $push: "$array" },
                  mcount: { $sum: {$cond: [{$eq: ["$value","$array"]},1,0]}}
                }
    },
    { $match: {mcount: {$gt: 0}}},
    { "$project": { "value": "$_id.value", "array": 1, "_id": 0 }}
])

The idea is to $unwind and $group back the array, counting in mcount the number of items matching the value. After that, a simple $match on mcount > 0 will filter out unwanted documents.

Comments

8

A more efficient approach would involve a single pipeline that uses the $redact operator as follows:

db.collection.aggregate([
    { 
        "$redact": {
            "$cond": [
                { 
                    "$setIsSubset": [ 
                        ["$value"],
                        "$array"  
                    ] 
                },
                "$$KEEP",
                "$$PRUNE"
            ]
        }
    }
])

For earlier versions of MongoDB that do not support $redact (versions < 2.6) then consider this aggregation pipeline that uses the $unwind operator:

db.collection.aggregate([
    { "$unwind": "$array" },
    {
        "$project": {
            "isInArray": {
                "$cond": [
                    { "$eq": [ "$array", "$value" ] },
                    1,
                    0
                ]
            },
            "value": 1,
            "array": 1
        }
    },
    { "$sort": { "isInArray": -1 } },
    {
        "$group": {
            "_id": {
                "_id": "$_id",
                "value": "$value"
            },
            "array": { "$push": "$array" },
            "isInArray": { "$first": "$isInArray" }
        }
    },
    { "$match": { "isInArray": 1 } },
    { "$project": { "value": "$_id.value", "array": 1, "_id": 0 } }
])

Comments

5

A little late to answer but this presents another solution:

By using addFields and match separately, this gives more flexibility than the redact. You can expose several fields and then use other matching logic together based on the results.

db.applications.aggregate([
    {$addFields: {"containsValueInArray": {$cond:[{$setIsSubset: [["valueToMatch"], "$arrayToMatchIn"]},true,false]}}},
    {$match: {"containsValueInArray":true}}
]);

Comments

1

Try the combination of $eq and $setIntersection

{$group :{
  _id: "$id",
  yourName :  { $sum:
  { $cond :[
       {$and : [
          {$eq:[{$setIntersection : ["$someArrayField", ["$value"]]  },["$value"]]}
         ]
      },1,0]
  }

} }

Comments

1

i prefer without grouping, there's an easy approach since v.3.2

...aggregate([
      {
        $addFields: {
          arrayFilter: {
            $filter: {
              input: '$array',
              as: 'item',
              cond: ['$$item', '$value']
            }
          }
        }
      },
      {
        $unwind: '$arrayFilter'
      },
      {
        $project: {
          arrayFilter: 0
        }
      }
    ]);
  1. Add a temporary filter field
  2. $unwind on the resulting array (pipeline results with empty arrays get removed)
  3. (optional) remove filter field from result via project

Comments

0

$match:{ empID:true profession:[]// array have multiple values like hr, team-lead, sales-manager etc }

how to implement above logic in pipelines

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
-1

You can do it with simple $project & $match

db.test.aggregate([{
$project: {
              arrayValue: 1,
              value: 1,
              "has_same_value" : { $in: ["$value", "$arrayValue"] }
          }
},
{
   $match: {has_same_value: true}
},
{
  $project: {has_same_value: 0}
}])

Comments

-1
      "$match": { "name": { "$in":["Rio","Raja"] }} }])

2 Comments

This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review
Here's the basic examples of $Match and $in using array of name in String

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.