5

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

Community
  • 1
  • 1
Orion Chang
  • 77
  • 1
  • 5
  • 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? – Sammaye Mar 26 '13 at 11:17
  • Either {post1.tags = ['tag1']} or {post2.tags = ['tag1', 'tag3']} is a part of array ['tag1','tag3']. – Orion Chang Mar 26 '13 at 11:19
  • Provided you can garauntee the order you could do: `db.col.find({tags:[tag1,tag2]})` but it requires a good order – Sammaye Mar 26 '13 at 11:20
  • 1
    Ok I see your edit, it seems like: `db.col.find({tags: {$in:[tag1,tag2]}})` should work – Sammaye Mar 26 '13 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"] – Orion Chang Mar 26 '13 at 11:45
  • hmmm there is no good way to automate that currently, you could split the search criteria up into the segments and do an $or on exact matching as I showed above but thats all that comes to mind – Sammaye Mar 26 '13 at 12:00

3 Answers3

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"
        ]
      }
    }
  }
})
Rodrigo Torres
  • 386
  • 2
  • 13
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
}
3

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