Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB: mixing $all and $elemMatch

Tags:

mongodb

I have a collection "items" of 5 items, each of which has an id and an array

{ _id: ObjectId("51c21bec162c138d9d0001a7"), 
    tags: [ { name: "a", type: "tag" }, { name: "b", type: "tag" }, { name: "c", type: "note" } ] 
}
{ _id: ObjectId("51c21ca22c69904840000178"), 
    tags: [ { name: "a", type: "tag" }, { name: "d", type: "tag" }, { name: "c", type: "note" } ] 
}
{ _id: ObjectId("51c21cc1478cf6691a0001b4"), 
    tags: [ { name: "d", type: "tag" }, { name: "b", type: "tag" }, { name: "c", type: "note" } ] 
}
{ _id: ObjectId("51c22292162c13b1ff000222"), 
    tags: [ { name: "a", type: "tag" }, { name: "d", type: "tag" }, { name: "e", type: "note" } ] 
}
{ _id: ObjectId("51c222e926d602a57d0001d8"), 
    tags: [ { name: "a", type: "tag" }, { name: "d", type: "note" }, { name: "c", type: "note" } ] 
}

The goal here is to return all items which have tags 'a' and 'd', where the tags are of the type 'tag'. You might have thought this would do it:

find({"tags.name":{"$all":["a","d"]}, "tags.type":'tag'})

returns 3 docs, which is wrong, but I have learnt that this query does an or. So then I try to use '$elemMatch' to do this, in what I thought was the most intuitive way, but

find({"tags":{"$elemMatch":{'name':{'$all':["a","d"]}, "type":'tag'}}})

returns no docs.

The same query, if I only want the items tagged 'a' works:

find({"tags":{"$elemMatch":{'name':{'$all':["a"]}, "type":'tag'}}})

presumably because $all gets mapped to something like $eq.

Eventually I found that the following is what I need to do for my original GOAL:

find({"tags":{"$all":[{'$elemMatch':{'name':"a", "type":'tag'}}, {'$elemMatch':{'name':"d", "type":'tag'}} ]}})

returns the correct two docs.

But this syntax is horrible! I have to expand the array ["a", "d"] on my own into the query. What if I am writing a generic query engine and I want to say that multiple fields of the embedded doc are arrays,and I want a certain subset of values from each array?

Is there a better way to do this? A better syntax?

like image 917
rahuldave Avatar asked Jun 19 '13 22:06

rahuldave


1 Answers

What you eventually arrived at is the correct solution. It may be an improvement to programatically construct the array of $elemMatch rules.

Consider the following python:

match_rules = []
for tag in query_tags:
  match_rules.append({
    '$elemMatch': {
      'name': tag
      'type': 'tag'
    }
  })

collection.find({"tags":{"$all": match_rules}})
like image 107
Nat Avatar answered Nov 15 '22 10:11

Nat