mongodb聚合子查询:Mongodb php适配器

时间:2014-05-15 06:35:44

标签: php mongodb aggregation-framework mongodb-php

我有以下收藏:

**S_SERVER**  –  **S_PORT**  –  **D_PORT**  –  **D_SERVER**  –  **MBYTES**
L0T410R84LDYL – 2481 – 139 – MRMCRUNCHAPP – 10
MRMCASTLE – 1904 – 445 – MRMCRUNCHAPP – 25
MRMXPSCRUNCH01 – 54769 – 445 – MRMCRUNCHAPP - 2
MRMCASTLE – 2254 – 139 – MRMCRUNCHAPP - 4
MRMCASTLE – 2253 – 445 – MRMCRUNCHAPP -35
MRMCASTLE – 987 – 445 – MRMCRUNCHAPP – 100
MRMCASTLE – 2447 – 445 – MRMCRUNCHAPP – 12
L0T410R84LDYL – 2481 – 139 – MRMCRUNCHAPP - 90
MRMCRUNCHAPP – 61191 – 1640 – OEMGCPDB – 10

首先,根据从每个S_SERVER传输的总MBYTES,我需要前30个S_SERVER。这是我能够得到以下查询:

$sourcePipeline = array(
        array(
            '$group' => array(
                '_id' => array('sourceServer' => '$S_SERVER'),
                'MBYTES' => array('$sum' => '$MBYTES')
            ),
        ),
        array(
            '$sort' => array("MBYTES" => -1),
        ),
        array(
            '$limit' => 30
        )
    );
$sourceServers = $collection->aggregate($sourcePipeline);

根据从每个D_PORT传输的个人S_SERVER的总MBYTES,我还需要前30个D_PORT。我这样做是通过从上面的服务器结果运行循环并逐个为每个S_SERVER获取它们。

$targetPortPipeline = array(
                array(
                    '$project' => array('S_SERVER' => '$S_SERVER', 'D_PORT' => '$D_PORT', 'MBYTES' => '$MBYTES')
                ),
                array(
                    '$match' => array('S_SERVER' => S_SERVER(find from above query, passed one by one in for loop)),
                ),
                array(
                    '$group' => array(
                        '_id' => array('D_PORT' => '$D_PORT'),
                        'MBYTES' => array('$sum' => '$MBYTES')
                    ),
                ),
                array(
                    '$sort' => array("MBYTES" => -1),
                ),
                array(
                    '$limit' => $limit
                )
            );
$targetPorts = $collection->aggregate($targetPortPipeline);

但这个过程花费了太多时间。我需要一种有效的方法来获得相同查询中的所需结果。我知道我使用Mongodb php适配器来实现这一目标。你也可以让我知道javascript格式的聚合函数。我会把它转换成php。

1 个答案:

答案 0 :(得分:1)

这里的问题基本上似乎是您要为最初的30个结果发出30多个查询。没有简单的解决方案,目前似乎没有一个查询,但您可以考虑一些事项。

作为补充说明,你并不孤单,因为这是我之前看到过的一个问题,我们可以将其称为"前N个结果问题"。基本上你真正想要的是组合两个结果集的某种方式,这样每个分组边界(源服务器)本身只有最大N个结果,而在那个顶级你也将这些结果再次限制在最顶层N个结果值。

您的第一个聚合查询前30个"源服务器的结果"你想要的,那很好。但是,您可以尝试使用"源服务器"创建一个数组,而不是从中循环其他查询。此结果中的值,并使用 $in 运算符将其传递给您的第二个查询:

db.collection.aggregate([
    // Match should be first
    { "$match": { "S_SERVER": { "$in": sourceServers } } },

    // Group both keys
    { "$group": {
        "_id": {
            "S_SERVER": "$S_SERVER",
            "D_SERVER": "$D_SERVER"
        },
        "value": { "$sum": "$MBYTES" }
    }},

    // Sort in order of key and largest "MBYTES" 
    { "$sort": { "S_SERVER": 1, "value": -1 } }
])

注意到你不能限制"这里包含每个"源服务器"从最初的比赛。你也不能限制"在分组边界上,这实际上是聚合框架中缺少的,否则会使其成为两个查询结果。

因为它包含每个" dest服务器"结果,并可能超过"前30"您将在代码中处理结果并在"前30"之后跳过返回的结果。在每个分组(源服务器)级别检索。根据您拥有的结果数量,这可能是也可能不是最实用的解决方案。

继续前进不太实际的地方,你很可能会把这个输出作为临时步骤转移到另一个集合中。如果你有MongoDB 2.6或更高版本,这可以像在语句末尾添加 $out 管道阶段一样简单。对于早期版本,您可以使用mapReduce执行等效语句:

db.collection.mapReduce(
    function() {
        emit(
            {
                "S_SERVER": this["S_SERVER"],
                "D_SERVER": this["D_SERVER"]
            },
            this.MBYTES
        );
    },
    function(key,values) {
        return Array.sum( values );
    },
    {
        "query": { "S_SERVER": { "$in": sourceServers } },
        "out": { "replace": "output" }
    }
)

这与前一个聚合语句基本上是相同的过程,同时还注意到 mapReduce 不对输出进行排序。这是对结果集合的其他 mapReduce 操作所涵盖的内容:

db.output.mapReduce(
    function() {

        if ( cServer != this._id["S_SERVER"] ) {
            cServer = this._id["S_SERVER"];
            counter = 0;
        }

        if ( counter < 30 )
            emit( this._id, this.value );

        counter++;
    },
    function(){},  // reducer is not actually called
    { 
        "sort": { "_id.S_SERVER": 1, "value": -1 },
        "scope": { "cServer": "", "counter": 0 }
    }
)

这里的实施是&#34;服务器端&#34; &#34;光标跳过的版本&#34;那是前面提到的。因此,您仍在处理每个结果,但通过网络返回的结果仅限于每个&#34;源服务器&#34;下的前30个结果。

如上所述,仍然非常可怕,因为它必须以编程方式扫描&#34;通过结果丢弃您不想要的结果,并再次依赖于您的音量,您可能更好的只是为每个&#34;源服务器&#34;发出 .find() 。排序和限制结果时这些结果中的值

sourceServers.forEach(function(source) {
    var cur = db.output.find({ "_id.S_SERVER": source })
        .sort({ "value": -1 }).limit(30);
    // do something with those results
);

这仍然是30个额外的查询,但至少你不是&#34;聚合&#34;每次这项工作已经完成。

作为一个实际上太详细的最后一点,您可以使用显示的初始聚合查询的修改形式来处理此问题。这更像是一个脚注,如果你已经读过这篇文章而没有其他看似合理的方法,那么由于可能会遇到的内存限制,这可能是最糟糕的。

引入这个的最好方法是使用&#34;理想&#34;结果&#34;前N个结果&#34;聚合,当然实际上并不存在,但理想情况下,管道的末端看起来像这样:

    { "$group": {
        "_id": "$S_SERVER",
        "results": { 
            "$push": {
                "D_SERVER": "$_id.D_SERVER",
                "MBYTES": "$value"
            }, 
            "$limit": 30
        }
    }}

所以&#34;不存在&#34;这里的因素是能够限制&#34;推送的结果数量&#34;进入每个&#34;源服务器的结果数组&#34;值。如果实现这个或类似的功能,世界肯定会是一个更好的地方,因为它可以很容易地解决这个问题。

由于它不存在,你可以使用其他方法来获得相同的结果并最终得到像this example这样的列表,除非在这种情况下实际上要复杂得多。

考虑到代码,您将按照以下方式执行某些操作:

  1. 将所有结果分组回每个服务器的数组
  2. 将所有这些归为一个单独的文档
  3. 展开第一个服务器结果
  4. 获取第一个条目并重新组合。
  5. 再次展开结果
  6. 投放并匹配找到的条目
  7. 放弃匹配结果
  8. 冲洗并重复步骤 4 - 7 30次
  9. 存储30个结果的第一个服务器文档
  10. 为每台服务器冲洗并重复 3 - 9 ,所以30次
  11. 您永远不会直接对此进行编码,并且必须编写代码来生成管道阶段。它很可能会破坏16MB的限制,可能不会在管道文档本身上,但很可能在实际的结果集上这样做,因为你将所有内容都推到了数组中。

    您可能还会注意到,如果您的实际结果不包含每个服务器至少30个最高值,那么完全爆炸是多么容易。

    整个方案归结为权衡哪种方法最适合您的数据和性能考虑因素:

    1. 从初始结果中获得30个聚合查询。
    2. 减少2个查询并在客户端代码中丢弃不需要的结果。
    3. 输出到临时集合并使用服务器光标跳过以丢弃结果。
    4. 从预先汇总的集合发出30个查询。
    5. 实际上要解决实现产生结果的管道的问题。
    6. 在任何情况下,由于一般聚合的复杂性,我会定期生成您的结果集并将其存储在自己的集合中,而不是试图实时执行此操作。

      数据不会是&#34;最新的&#34;结果,只有你更新它的频率一样新鲜。但实际上检索那些显示的结果变成了一个简单的查询,最多返回900个相当紧凑的结果,而没有为每个请求聚合的开销。