将MongoDB Query与Document ID数组组合作为排序首选项

时间:2014-03-25 22:52:43

标签: php mongodb

好的,所以案例场景是我有一个产品类别的集合,每个文档类似于:

Category {
    _id => ...,
    name => ...,
    products => array(
        0 => new MongoID(PID...1),
        1 => new MongoID(PID...2),
        2 => new MongoID(PID...3),
        ....
    )
}

以及一系列产品:

Product {
    _id => ...,
    name => ...,
    active => true,
    status => 'published',
    categories => array(
        0 => new MongoID(CATID...1),
        1 => new MongoID(CATID...2),
        2 => new MongoID(CATID...3)
    )
}

我正在重构我的代码,因为此时因为当我进行任何限制/偏移时,我必须将类别中的所有产品作为对象返回,如果它们不活动则取消设置它们(还有更多内容匹配,但我为了简洁而将它们排除在外。显然,我们现阶段尚未完全优化。

我已经构建了一个如下所示的条件查询:

array(
    '$and' => array(
        array(
            '$or' => array(
                    0 => array(
                            '_id' => new MongoID(PID...1)
                    ),
                    1 => array(
                            '_id' => new MongoID(PID...2)
                    ),
                    2 => array(
                            '_id' => new MongoID(PID...3)
                    ),
                    ...

            )
        ),
        array(
            '$and' => array(
                array(
                    'active' => true
                ),
                array(
                    'status' => 'published'
                )
            )
        )
    )
)

我需要做的是尊重查询第一部分中_id的顺序和偏移/限制,基于哪些文档与_id序列中的第二部分匹配。< / p>

查询最终以find()命令结束,但设置排序顺序显然不会起作用,因为我们没有排序。

如果没有从产品中拆分某些字段并添加到Category-&gt;产品[],我很难找到一种方法来做到这一点。它只是现在用MapReduce / aggregation处理它的情况还是有更简单的替代方案?

谢谢!

1 个答案:

答案 0 :(得分:2)

如果您需要以这种方式维护“订单”,那么您最好的选择是为您选择的项目指定“权重”。这给你排序的东西。

您可以使用.aggregate()执行此操作,并且可以说mapReuce可以做同样的事情,但的“聚合”方式应该运行得更快。你在这里的语法似乎有点偏。而不是在同一字段上使用$or,而您可能需要$in

一般JavaScript / JSON格式:

var idArray = [ 5, 2, 8 ];

db.collection.aggregate([

    // Match the selected documents by "_id"
    { "$match": {
        "_id": { "$in": [ 5, 2, 8 ] },
        "active": true,
        "status": "published"
    },

    // Project a "weight" to each document
    { "$project": {
        "name": 1,
        "active": 1,
        "status": 1,
        "weight": { "$cond": [
            { "$eq": [ "_id", 5  ] },
            1,
            { "$cond": [
                { "$eq": [ "_id", 2 ] },
                2,
                3
            ]}
        ]}
    }},

    // Sort the results
    { "$sort": { "weight": 1 } }

])

所以为了阅读本文,我确实在那里“展开”了数组,但是你想要引用$in子句的实际代码。

$cond的嵌套使用评估了逻辑条件以匹配_id字段的“值”,并在增加数字中指定“权重”。这样可以保持输入数组中值的顺序。

实际上,您可以在代码中执行此操作以“生成”管道部件,尤其是嵌套条件,其中包含与here一致的内容。该示例使用“特定”权重,但大部分原则都是相同的。

允许您维护输入数组的顺序


PHP代码

例如,像这样生成所需的管道:

$list = array( 5, 2, 8 );

$stack = array();

for( $i = count($list)-1; $i > 0; $i-- ) {

  $rec = array(
    '$cond' => array(
      array( '$eq' =>
        array( '$_id', $list[$i-1] )
      ),
      $i
    )
  );

  if ( count($stack) == 0 ) {
    $rec['$cond'][] = $i+1;
  } else {
    $last = array_pop($stack);
    $rec['$cond'][] = $last;
  }

  $stack[] = $rec;

}

$pipeline = array(
  array(
    '$match' => array(
      '_id' => array( '$in' => $list ),
      'active' => true,
      'status' => "published"
    )
  ),
  array(
    '$project' => array(
      'name'    => 1,
      'active'  => 1,
      'status'  => 1,
      'weight'  => $stack[0]
    )
  ),
  array(
    '$sort' => array( 'weight' => 1 )
  )
);

echo json_encode( $pipeline, JSON_PRETTY_PRINT ) . "\n";

当然,你实际上并没有“编码”到JSON,这只是为了展示所形成的结构。


Map Reduce

只是为了表明你也可以用mapReduce做同样的事情。首先设置映射器

var mapper = function () {
    var order = inputs.indexOf(this._id);
    emit( order, { doc: this } );
}

也许是一个最终化的功能来清理,“一点点”:

var finalize = function (key, value) {
    return value.doc;
}

运行mapReduce。不需要减速器:

db.test.mapReduce(
    mapper,
    function(){},
    { 
        "out": { "inline": 1 },
        "query": { "_id": { "$in": idArray } },
        "scope": { "inputs": idArray } ,
        "finalize": finalize
    }
)

哪个“看起来”更干净,但可能的运行速度不会很快,并且结果会有一个非常“mapReduce”类型的输出。