【问题标题】:MongoDB aggregation: any value of $existsMongoDB 聚合:$exists 的任何值
【发布时间】:2017-02-22 14:50:32
【问题描述】:

我正在尝试将 MongoDB 聚合用于 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 类型的值进行比较,但它似乎太老套了。

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

非常感谢任何帮助。

【问题讨论】:

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


    【解决方案1】:

    我将重组 $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()
        // ...
    });
    

    【讨论】:

    • 谢谢。不知道基于毫秒的日期查询订阅是否有用:D 顺便说一下,您可以使用点语法,即match.$match.cancelled.$exists = true
    【解决方案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: { $or: [ true, false ] } ,如果您没有得到 true 或 false ,则不要添加任何要查询的内容。

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

    【讨论】:

    • 解决方案没问题,但为了简洁起见,我尽量避免使用if-then-else 的东西。谢谢。
    • 我已将您的答案与@chridam 的答案结合起来:var match = { user: req.user._id }; if ([ 'true', 'false' ].indexOf(req.query.cancelled) !== -1) match.cancelled = { $exists: JSON.parse(req.query.cancelled) };
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-20
    • 1970-01-01
    • 1970-01-01
    • 2017-09-03
    • 1970-01-01
    • 2017-05-23
    相关资源
    最近更新 更多