7

In this question, Jeff the Bear explained how to search documents with an array

contains 'tag1'
contains ['tag1','tag2'],
contains any of ['tag3', 'tag4']

But how should I do if I want to search documents with an array which is part of another array?

post1.tags = ['tag1']
post2.tags = ['tag1','tag3']
post3.tags = ['tag2','tag4']
post4.tags = ['tag1','tag2','tag3','tag4']

I want to get post1 and post3 because they have tags

contained in ['tag1', 'tag2', 'tag4']

I don't want to get post2 and post4, because tag3 doesn't exist in ['tag1', 'tag2', 'tag4']

In other words, select posts that all elements in its tags array can be found in another conditional array

6
  • What do you mean by "part of another array" should tags contain all of what your looking for or just some of what your looking for? Commented Mar 26, 2013 at 11:17
  • Either {post1.tags = ['tag1']} or {post2.tags = ['tag1', 'tag3']} is a part of array ['tag1','tag3']. Commented Mar 26, 2013 at 11:19
  • Provided you can garauntee the order you could do: db.col.find({tags:[tag1,tag2]}) but it requires a good order Commented Mar 26, 2013 at 11:20
  • 1
    Ok I see your edit, it seems like: db.col.find({tags: {$in:[tag1,tag2]}}) should work Commented Mar 26, 2013 at 11:33
  • db.col.find({tags: {$in:[tag1,tag2]}}) will search any posts with tags contain "tag1" or "tag2". I may get some posts with tags ["tag1","tag3"] or ["tag2","tag4"], and they aren't the posts I'm looking for. In this case, I only want posts with tags["tag1"],["tag2"],or ["tag1","tag2"] Commented Mar 26, 2013 at 11:45

3 Answers 3

6

7 years later... I found this solution, based on this topic.

I am not sure if it is a good solution, performance-wise, but it looks cleaner than the accepted solution.

db.collection.find({
  tags: {
    "$not": {
      "$elemMatch": {
        "$nin": [
          "tag1",
          "tag2",
          "tag4"
        ]
      }
    }
  }
})
Sign up to request clarification or add additional context in comments.

Comments

5

You can use aggregation framework to do this. Given the following data

> db.post.find()
{ "_id" : 1, "tags" : [ "tag1" ] }
{ "_id" : 2, "tags" : [ "tag1", "tag3" ] }
{ "_id" : 3, "tags" : [ "tag2", "tag4" ] }
{ "_id" : 4, "tags" : [ "tag1", "tag2", "tag3", "tag4" ] }

the aggregation query

db.post.aggregate({
  $project: {
    _id: 1,
    tags: 1,
    killFlag: {
      $const: [true, false]
    }
  }
}, {
  $unwind: "$tags"
}, {
  $unwind: "$killFlag"
}, {
  $match: {
    $nor: [{
        tags: {
          $in: ['tag1', 'tag2', 'tag4']
        },
        killFlag: true
      }
    ]
  }
}, {
  $group: {
    _id: "$_id",
    tags: {
      $addToSet: "$tags"
    },
    killFlag: {
      $max: "$killFlag"
    }
  }
}, {
  $match: {
    killFlag: false
  }
}, {
  $project: {
    _id: 1,
    tags: 1
  }
})

would give you

{
  "result": [{
      "_id": 3,
      "tags": [
          "tag4",
          "tag2"
      ]
    }, {
      "_id": 1,
      "tags": [
          "tag1"
      ]
    }
  ],
  "ok": 1
}

Comments

4

I had a similar task, and what is working for me:

db.collection.find({ tags: { "$all":[ "tag1", "tag2", "tag4"] }})

maybe for someone, this will be a helpful solution founded in docs https://docs.mongodb.com/manual/tutorial/query-arrays/#specify-multiple-criteria-for-array-elements

1 Comment

This is great! But note that if the array provided to $all is empty, it will possibly not match any documents (whereas I expected it to always result in a match). So if the array provided to $all is empty, instead just omit this condition all together. For an example, see this SO answer: stackoverflow.com/a/29856671/12218938

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.