I'm toying with Mongo to make a SO-like pet project, and I want to implement post tags. Each tag has a name and a slug (string to be used as an id in the URL), and a post has multiple tags. I'd like to be able to create queries like "find posts, which have tag A, don't have tag B", and I'm wondering what's the mongo-way to do this.
One way is to store an array of tag ids with each post - this will make the said query easy, but will require an extra one for each post to get tag name and a slug. Another way is to store an array of [tag name, tag slug] with each post, but I'm not sure I'll be able to use that info in a find
.
Is there some other method, which will work better for mongo? I'm new to NoSQL, so I'd appreciate any advise on how this can be accomplished. Also, I'm using PHP binding, but that shouldn't matter probably.
If the tags you use and their respective slugs are unlikely to change, I think your second approach is the better one. However I would suggest a small change - rather than storing an array of [name, slug]
, make the fields explicit by creating a tag subdocument as in this example post
document:
{
"_id" : ObjectId("4ee33229d8854784468cda7e"),
"title" : "My Post",
"content" : "This is a post with some tags",
"tags" : [
{
"name" : "meta",
"slug" : "34589734"
},
{
"name" : "post",
"slug" : "34asd97x"
},
]
}
You can then query for posts with a particular tag using dot notation like this:
db.test.find({ "tags.name" : "meta"})
Because tags
is an array, mongo is clever enough to match the query against any element of the array rather than the array as a whole, and dot-notation allows you to match against a particular field.
To query for posts not containing a specific tag, use $ne
:
db.test.find({ "tags.name" : { $ne : "fish" }})
And to query for posts containing one tag but not the other, use $and
:
db.test.find({ $and : [{ "tags.name" : { $ne : "fish"}}, {"tags.name" : "meta"}]})
Hope this helps!