将MySQL查询转换为MongoDB

3
我已经开始学习MongoDB,但遇到了一个问题。我有一个名为server_logs的集合。
它包含以下列(SOURCE_SERVER、SOURCE_PORT、DESTINATION_PORT、DESTINATION_SERVER、MBYTES)。
我需要获取每个SOURCE_SERVER传输的MBYTES总量。(但还有一个要点是,如果任何源服务器也存在于目标服务器中,则它们的MBYTES也将添加到每个SOURCE_SERVER中)。
例如:我有以下表结构。
  SOURCE   S_PORT   DEST    D_PORT  MBYTES
1)server1   446    server2   555     10MB
2)server3   226    server1   666     2MB
3)server1   446    server3   226     5MB

我需要以下结果:

Server1  17MB
Server3  7MB

我在MySQL中创建了一个查询,用于计算数据传输到该SOURCE的MBYTES的顶部SOURCE。 它工作正常,并且通过此查询在MYSQL中获得所需的结果。

SELECT SOURCE, DEST, sum( logs.MBYTES )+(
    SELECT SUM(log.MBYTES) as sum
    from logs as log
    where logs.DEST=log.SOURCE
) AS MBYTES

我希望在MongoDB中执行此查询,请帮忙。谢谢。

那么,当你将数据导入MongoDB后,它是什么样子的呢? - Neil Lunn
你看过聚合框架吗? - John Powell
是的,我已经检查了聚合框架,但我没有找到任何通过聚合添加子查询的示例。 - Shiv Aggarwal
没有子查询,但是请您在问题中展示您所提出的数据结构和期望的结果。 - Neil Lunn
您可以编辑您的问题。请勿尝试在评论中发布详细信息。编辑您的问题对每个人都更好,特别是对您自己。 - Neil Lunn
谢谢,我已经编辑了细节。现在它会更清晰明了。 - Shiv Aggarwal
2个回答

1
虽然这种“自连接”类型的查询可能不会立即显而易见地告诉你如何在MongoDB中执行,但可以通过聚合框架来完成,只需要稍微改变一下思路即可。
当您的数据以这种形式存储在MongoDB中时,它仍然非常类似于原始的SQL源:
{ 
    "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
}

使用MongoDB 2.6之前的版本,您的查询将如下所示:
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" }
    }}
])

对于版本2.6及以上,您将获得一些额外的运算符来简化此过程,或者至少利用不同的运算符:
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 }}
])

两种形式都会产生以下结果:
{ "_id" : "server1", "mbytes" : 17 }
{ "_id" : "server3", "mbytes" : 7 }

在这两种方法中,一般的原则是通过保持一个有效的“源”服务器列表,您可以“过滤”合并结果,以便只有那些被列为源的结果才会记录它们的总传输量。
因此,您可以使用一些技术来“重新塑造”、“组合”和“过滤”您的文档,以获得所需的结果。
请阅读更多关于聚合操作符,同时值得一提的是,SQL到聚合映射图表文档中的介绍可以让您了解如何转换常见操作。
甚至可以浏览Stack Overflow上的标签,以找到一些有趣的转换操作。

0
你可以使用聚合框架来完成这个任务:
db.logs.aggregate([
    {$group:{_id:"$SOURCE",MBYTES:{$sum:"$MBYTES"}}}
])

假设您的MBYTES字段中只有数字值。因此,您将得到以下结果:
{
    _id: server1,
    MBYTES: 17
},
{
    _id: server3,
    MBYTES: 7
}

如果您需要对出现在DEST字段中的服务器进行计数,您应该使用Map-Reduce方法:
var mapF = function(){
    emit(this.SOURCE,this.MBYTES);
    emit(this.DEST,this.MBYTES);
}

var reduceF = function(serverId,mbytesValues){
    var reduced = {
        server: serverId,
        mbytes: 0
    };

    mbytesValues.forEach(function(value) {
        reduced.mbytes += value;
    });

    return reduced;
}

db.logs.mapReduce(mapF,reduceF,{out:"server_stats"});

之后您可以在server_stats集合中找到结果。


它只会对SOURCE进行分组,并对MBYTES的值进行求和。上述聚合函数给出以下输出: { _id: server1, MBYTES: 15 }, { _id: server3, MBYTES: 2 } 但如果在任何目标服务器中存在源,则它们的MBYTES也需要被加上。 - Shiv Aggarwal
好的,所以你必须使用MapReduce来完成这个任务。我会在几分钟内编辑上面的答案。 - Marcin Twardowski
是的,你正在“合并”“SOURCE”和“DEST”的结果,就像问题中所要求的那样,但这并没有提供仅基于“SOURCE”值的“join”,就像在SQL中所示。因此,这将给出不需要的“server2”结果,因为它不是一个“SOURCE”。实际上,可以使用聚合框架来完成这个任务,就像我发布的那样。 - Neil Lunn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接