Though this sort of "self join" type of query might not seem immediately apparent to how you would do this with MongoDB, it can be done with the aggregation framework but just requires a little change in your thinking.
With your data in MongoDB in this form, which is still very much like the original SQL source:
{
"source" : "server1",
"s_port" : 446,
"dest" : "server2",
"d_port" : 555,
"transferMB" : 10
},
{
"source" : "server3",
"s_port" : 226,
"dest" : "server1",
"d_port" : 666,
"transferMB" : 2
},
{
"source" : "server1",
"s_port" : 446,
"dest" : "server3",
"d_port" : 226,
"transferMB" : 5
}
Working with a pre 2.6 version of MongoDB your query will look like this:
db.logs.aggregate([
// Project a "type" tag in order to transform, then unwind
{ "$project": {
"source": 1,
"dest": 1,
"transferMB": 1,
"type": { "$cond": [ 1,[ "source", "dest" ],0] }
}},
{ "$unwind": "$type" },
// Map the "source" and "dest" servers onto the type, keep the source
{ "$project": {
"type": 1,
"tag": { "$cond": [
{ "$eq": [ "$type", "source" ] },
"$source",
"$dest"
]},
"mbytes": "$transferMB",
"source": 1
}},
// Group for totals, keep an array of the "source" for each
{ "$group": {
"_id": "$tag",
"mbytes": { "$sum": "$mbytes" },
"source": { "$addToSet": "$source" }
}},
// Unwind that array
{ "$unwind": "$source" },
// Is our grouped tag one on the sources? Inner join simulate
{ "$project": {
"mbytes": 1,
"matched": { "$eq": [ "$source", "$_id" ] }
}},
// Filter the results that did not match
{ "$match": { "matched": true }},
// Discard duplicates for each server tag
{ "$group": {
"_id": "$_id",
"mbytes": { "$first": "$mbytes" }
}}
])
For versions 2.6 and above, you get a few additional operators to streamline this, or a least makes use of different operators:
db.logs.aggregate([
// Project a "type" tag in order to transform, then unwind
{ "$project": {
"source": 1,
"dest": 1,
"transferMB": 1,
"type": { "$literal": [ "source", "dest" ] }
}},
{ "$unwind": "$type" },
// Map the "source" and "dest" servers onto the type, keep the source
{ "$project": {
"type": 1,
"tag": { "$cond": [
{ "$eq": [ "$type", "source" ] },
"$source",
"$dest"
]},
"mbytes": "$transferMB",
"source": 1
}},
// Group for totals, keep an array of the "source" for each
{ "$group": {
"_id": "$tag",
"mbytes": { "$sum": "$mbytes" },
"source": { "$addToSet": "$source" }
}},
// Co-erce the server tag into an array ( of one element )
{ "$group": {
"_id": "$_id",
"mbytes": { "$first": "$mbytes" },
"source": { "$first": "$source" },
"tags": { "$push": "$_id" }
}},
// User set intersection to find common element count of arrays
{ "$project": {
"mbytes": 1,
"matched": { "$size": {
"$setIntersection": [
"$source",
"$tags"
]
}}
}},
// Filter those that had nothing in common
{ "$match": { "matched": { "$gt": 0 } }},
// Remove the un-required field
{ "$project": { "mbytes": 1 }}
])
Both forms produce the results:
{ "_id" : "server1", "mbytes" : 17 }
{ "_id" : "server3", "mbytes" : 7 }
The general principle in both is that by keeping a list of the valid "source" servers you can then "filter" the combined results so that only those that were listed as a source will have their total transfer recorded.
So there are a couple of techniques you can use to "re-shape", "combine" and "filter" your documents to get your desired result.
Read up more on the aggregation operators and also worth looking at for an introduction is the SQL to Aggregation mapping chart within the documentation to give you some idea of converting common operations.
Even browse aggregation-framework tags here on Stack Overflow to find some interesting transformation operations.