MongoDB聚合:$的任何值都存在

时间:2016-10-13 12:57:28

标签: node.js mongodb express mongoose aggregation-framework

我试图将RESToDB聚合用于RESTful api,但是遇到了以下情况。假设我有Subscriptions模型,看起来像这样:

var mongoose = require('mongoose'),
  Schema = mongoose.Schema;

var SubscriptionSchema = new Schema({
  // ...
  cancelled: Date
});

如果订阅处于有效状态,则此cancelled属性可以是undefined,也可以是用户取消操作的Date

现在,我有一个路由GET /me/subscriptions,它聚合订阅并有一个可选的查询参数:cancelled=true(仅显示已取消)或cancelled=false(仅显示有效)。如果未指定,则应返回任何订阅(活动或取消)。

var express = require('express'),
  router = express.Router(),
  Subscription = require('../../models/subscription');

router.get('/me/subscriptions', function(req, res, next) {
  var cancelled = req.query.cancelled === 'true' ? { $exists: true } :
    req.query.cancelled === 'false' ? { $exists: false } :
    { $exists: { $or: [ true, false ] } }; // wrong logic here

  return Subscription.aggregate([
      { $match: { user: req.user._id, cancelled: cancelled }},
      { $project: {
        // ...
      }}
    ])
    .exec()
    // ...
});

module.exports = router;

如果我传递上面提到的查询参数,它将完美地工作,但如果未指定参数(或者它不等于truefalse)则无法找到模型。我尝试了很多东西而不是错误的行(在$match管道中):

cancelled: {}
cancelled: void 0
cancelled: { $exists: { $or: [ true, false ] } }
cancelled: { $exists: { $in: [ true, false ] } }
cancelled: { $exists: [ true, false ] } // obviously wrong, but hey
cancelled: null // obviously wrong, too
cancelled: { $or: [ { $exists: false }, { $exists: true } ] } // can't use $or here, but still, hey

现在看到的唯一解决方案就是这样,与不是undefined而不是Date类型的值进行比较,但它似乎太过于hacky。

cancelled: { $ne: 'some-impossible-value' }

非常感谢任何帮助。

2 个答案:

答案 0 :(得分:2)

我认为稍微调整就能满足你想要的条件。

var express = require('express'),
  router = express.Router(),
  Subscription = require('../../models/subscription');

router.get('/me/subscriptions', function(req, res, next) {
  var match_query = {user: req.user._id};

  if (req.query.cancelled === 'true') {
      match_query.cancelled = {$exists:true};
  } else if(req.query.cancelled === 'false') {
      match_query.cancelled = {$exists:false};
  }

  return Subscription.aggregate([
      { $match: match_query},
      { $project: {
        // ...
      }}
    ])
    .exec()
    // ...
});

module.exports = router;

你不需要添加{$ exists:{$或:[true,false]},如果你没有得到真或假,就不要添加任何东西来查询。

我没有检查代码是否存在语法错误,但从逻辑上讲它会起作用。

答案 1 :(得分:0)

我会重新构建 $match 管道,如下所示(需要安装momentjs库):

router.get('/me/subscriptions', function(req, res, next) {
    var match = {
        "$match": {
            "user": req.user._id,
            "cancelled": {}
        }
    };

    if (req.query.cancelled === 'true' || req.query.cancelled === 'false') {
        match["$match"]["cancelled"]["$exists"] = JSON.parse(req.query.cancelled);
    } else if(moment(req.query.cancelled, moment.ISO_8601, true).isValid()){
        match["$match"]["cancelled"] = moment(req.query.cancelled).toDate();
    }
    else {
        match["$match"]["cancelled"]["$exists"] = false;
    }       

    return Subscription.aggregate([match,
        { "$project": {
          // ...
        }}
    ]).exec()
    // ...
});