结合MongoDB查询和文档ID数组作为排序首选项


Combining MongoDB Query with Document ID array as sort preference

好的,那么情况是我有一个产品类别的集合,每个文档类似于:

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

和product集合:

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的序列中的第二部分来偏移/限制。

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

缺少从Product中拆分一些字段并添加到Category->products[],我正在努力寻找一种方法来做到这一点。现在是简单地使用MapReduce/aggregation来处理它,还是有更简单的替代方案?

谢谢!

如果您需要以这种方式维持"顺序",那么您最好的选择是为您所选择的项目分配一个"权重"。这给了你排序的东西。

您可以使用.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字段的"值",并分配"权重",如增加数字。它维持输入数组中值的顺序。

实际上,您可以在代码中这样做,以"生成"管道部分,特别是嵌套条件,这些条件与这里所看到的内容一致。这个例子使用了"特定"权重,但大部分原则是相同的。

但是允许您保持输入数组的顺序


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,这只是为了显示形成的结构。


地图减少

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

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

也许还有一个finalize函数来清理,"a little":

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"类型的输出。