霸气显露无疑!看例子了解Mongodb聚合框架
One of the most common questions we get is:
I have a collection of blog posts and each post has an array of comments. How do I get…
…all comments by a given author
…the most recent comments
…the most popular commenters?
And so on. The answer to this has always been “Well, you can’t do that on the server side…” You can either do it on the client side or store comments in their own collection. What you really want is the ability to treat embedded documents like a “real” collection.
The aggregation pipeline gives you this ability by letting you “unwind” arrays into separate documents, then doing whatever else you need to do in subsequent pipeline operators.
For example…
Getting all comments by Serious Cat
Serious Cat’s comments are scattered between post documents, so there wasn’t a good way of querying for just those embedded documents. Now there is.
Let’s assume we want each comment by Serious Cat, along with the title and url of the post Serious Cat was commenting on. So, the steps we need to take are:
- Extract the fields we want (title, url, comments)
- Unwind the comments field: make each comment into a “real” document
- Query our new “comments collection” for “Serious Cat”
Using the aggregation pipeline, this looks like:
> db.runCommand({aggregate: "posts", pipeline: [
{
// extract the fields
$project: {
title : 1,
url : 1,
comments : 1
}
},
{
// explode the "comments" array into separate documents
$unwind: "$comments"
},
{
// query like a boss
$match: {comments.author : "Serious Cat"}
}]})
Now, this works well for something like a blog, where you have human-generated (small) data. If you’ve got gigs of comments to go through, you probably want to filter out as many as possible (e.g., with $match
or $limit
) before sending it to the “everything-in-memory” parts of the pipeline.
Getting the most recent comments
Let’s assume our site lists the 10 most recent comments across all posts, with links back to the posts they appeared on, e.g.,
- Great post! -Jerry (February 2nd, 2012) from This is a Great Post
- What does batrachophagous mean? -Fred (February 2nd, 2012) from Fun with Crosswords
- Where can I get discount Prada shoes? -Tom (February 1st, 2012) from Rant about Spam
…
To extract these comments from a collection of posts, you could do something like:
> db.runCommand({aggregate: "posts", pipeline: [
{
// extract the fields
$project: {
title : 1,
url : 1,
comments : 1
}
{
// explode "comments" array into separate documents
$unwind: "$comments"
},
{
// sort newest first
$sort: {
"comments.date" : -1
}
},
{
// get the 10 newest
$limit: 10
}]})
Let’s take a moment to look at what $unwind
does to a sample document.
Suppose you have a document that looks like this after the $project
:
{
"url" : "/blog/spam",
"title" : "Rant about Spam",
"comments" : [
{text : "Where can I get discount Prada shoes?", ...},
{text : "First!", ...},
{text : "I hate spam, too!", ...},
{text : "I love spam.", ...}
]
}
Then, after unwinding the comments
field, you’d have:
{
"url" : "/blog/spam",
"title" : "Rant about Spam",
"comments" : [
{text : "Where can I get discount Prada shoes?", ...},
]
}
{
"url" : "/blog/spam",
"title" : "Rant about Spam",
"comments" : [
{text : "First!", ...}
]
}
{
"url" : "/blog/spam",
"title" : "Rant about Spam",
"comments" : [
{text : "I hate spam, too!", ...}
]
},
{
"url" : "/blog/spam",
"title" : "Rant about Spam",
"comments" : [
{text : "I love spam.", ...}
]
}
Then we $sort
, $limit
, and Bob’s your uncle.
Rank commenters by popularity
Suppose we allow users to upvote comments and we want to see who the most popular commenters are.
The steps we want to take are:
- Project out the fields we need (similar to above)
- Unwind the comments array (similar to above)
- Group by author, taking a count of votes (this will sum up all of the votes for each comment)
- Sort authors to find the most popular commenters
Using the pipeline, this would look like:
> db.runCommand({aggregate: "posts", pipeline: [
{
// extract the fields we'll need
$project: {
title : 1,
url : 1,
comments : 1
}
},
{
// explode "comments" array into separate documents
$unwind: "$comments"
},
{
// count up votes by author
$group : {
_id : "$comments.author",
popularity : {$sum : "$comments.votes"}
}
},
{
// sort by the new popular field
$sort: {
"popularity" : -1
}
}]})
As I mentioned before, there are a couple downsides to using the aggregation pipeline: a lot of the pipeline is done in-memory and can be very CPU- and memory-intensive. However, used judiciously, it give you a lot more freedom to mush around your embedded documents.