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?
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}})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With