按日期时间筛选子文档



我有以下模型

var messageSchema   = new Schema({
    creationDate:   { type: Date, default: Date.now },
    comment:        { type: String },
    author:         { type: Schema.Types.ObjectId }
});
var conversationSchema = new Schema({
    title:          { type: String },
    author:         { type : Schema.Types.ObjectId },
    members:        [ { type: Schema.Types.ObjectId } ],
    creationDate:   { type: Date, default: Date.now },
    lastUpdate:     { type: Date, default: Date.now },
    comments:       [ messageSchema ]
});

我想创建两个方法来获取user或conversationId在日期之后生成的评论。

由用户

我尝试了以下方法

var query = { 
    members : { $all : [ userId, otherUserId ], "$size" : 2 }
    , comments : { $elemMatch : { creationDate : { $gte: from } } } 
};

当指定日期(at from)之后没有注释时,该方法返回[]或null

由conversationId

当我尝试通过用户id

获取时也会发生同样的情况
var query = { _id : conversationId
    , comments : { $elemMatch : { creationDate : { $gte: from } } } 
};

是否有任何方法可以使该方法返回带有空注释的对话信息?

谢谢!

听起来这里有几个问题,但是逐步解决它们

为了从数组中获得多个匹配"或"none需要mapReduce的聚合框架来完成此操作。您可以尝试使用 $elemMatch "投影",但这只能返回"第一个"匹配。即:

{ "a": [1,2,3] }
db.collection.find({ },{ "$elemMatch": { "$gte": 2 } })
{ "a": [2] }

所以标准投影在这里不起作用。它可以返回一个"空"数组,但也只能返回匹配的"第一个"数组。

继续,在你的代码中也有:

{ $all : [ userId, otherUserId ], "$site" : 2 }

其中$site不是一个有效的运算符。我想你的意思是$size,但实际上有"两个"操作符与该名称,你的意图可能不清楚这里。

如果您的意思是要测试的数组必须"只有两个"元素,那么这就是适合您的操作符。如果你的意思是两个人之间匹配的对话必须等于匹配中的两个人,那么$all无论如何都会这样做,所以$size在任何情况下都是多余的,除非你不想让任何人参与对话。

关于聚合问题。您需要以"非破坏性方式"过滤"数组的内容,以便获得多个匹配项或空数组。

最好的方法是使用2.6版本提供的现代MongoDB功能,它允许在不处理$unwind的情况下过滤数组内容:

Model.aggregate(
    [
        { "$match": {
            "members": { "$all": [userId,otherUserId] }
        }},
        { "$project": {
            "title": 1,
            "author": 1,
            "members": 1,
            "creationDate": 1,
            "lastUpdate": 1,
            "comments": {
                "$setDifference": [
                    { "$map": {
                        "input": "$comments",
                        "as": "c",
                        "in": { "$cond": [
                            { "$gte": [ "$$c.creationDate", from ] },
                            "$$c",
                            false
                        ]}
                    }},
                    [false]
                ]
            }
        }}
    ],
    function(err,result) {
    }
);

使用 $map ,可以针对每个数组元素处理表达式。在这种情况下,在 $cond 三进制下测试值,返回条件为true的数组元素,否则返回false作为元素。

然后由 $setDifference 运算符"过滤",该运算符本质上是将$map的结果数组与另一个数组[false]进行比较。这将从结果数组中删除任何false值,只留下匹配的元素或根本没有元素。

另一种可能是 $redact ,但由于您的文档在多个级别包含"creationDate",因此这会与$$DESCEND操作符使用的逻辑混淆。

在早期版本中,"不销毁"数组需要小心处理。因此,您需要对结果进行相同的"筛选",以获得您想要的"空"数组:

Model.aggregate(
    [
        { "$match": {
            "$and": [ 
                { "members": userId },
                { "members": otherUserId }
        }},
        { "$unwind": "$comments" },
        { "$group": {
            "_id": "$_id",
            "title": { "$first": "$title" },
            "author": { "$first": "$author" },
            "members": { "$first": "$members" },
            "creationDate": { "$first": "$creationDate" },
            "lastUpdate": { "$first": "$lastUpdate" },
            "comments": {
                "$addToSet": {
                    "$cond": [
                        { "$gte": [ "$comments.creationDate", from ] },
                        "$comments",
                        false
                    ]
                }
            },
            "matchedSize": { 
                "$sum": {
                    "$cond": [
                        { "$gte": [ "$comments.creationDate", from ] },
                        1,
                        0
                    ]
                }
            }            
        }},
        { "$unwind": "$comments" },
        { "$match": {
            "$or": [
                { "comments": { "$ne": false } },
                { "matchedSize": 0 }
            ]
        }},
        { "$group": {
            "_id": "$_id",
            "title": { "$first": "$title" },
            "author": { "$first": "$author" },
            "members": { "$first": "$members" },
            "creationDate": { "$first": "$creationDate" },
            "lastUpdate": { "$first": "$lastUpdate" },
            "comments": { "$push": "$comments" }
        }},
        { "$project": {
            "title": 1,
            "author": 1,
            "members": 1,
            "creationDate": 1,
            "lastUpdate": 1,
            "comments": { 
                "$cond": [
                    { "$eq": [ "$comments", [false] ] },
                    { "$const": [] },
                    "$comments"
                ]
            }
        }}
    ],
    function(err,result) {
    }
)

做了很多相同的事情,但时间更长。为了查看数组内容,您需要 $unwind 内容。当您返回 $group 时,您将查看每个元素,看它是否符合决定返回内容的条件,并记录匹配的计数。

这将把一些($addToSet) false结果放在数组中,或者只放在一个没有匹配的条目false的数组中。所以你用 $match 过滤掉这些,但也测试匹配的"计数",看看是否没有找到匹配。如果没有找到匹配项,则不要丢弃该物品。

在final $project 中将[false]数组替换为空数组。

所以根据你的MongoDB版本,这是"快速/容易"或"慢/难"处理。更新已有多年历史的版本的令人信服的理由。


工作示例

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/aggtest');
var memberSchema = new Schema({
  name:         { type: String }
});
var messageSchema = new Schema({
  creationDate: { type: Date, default: Date.now },
  comment:      { type: String },
});
var conversationSchema = new Schema({
  members:      [ { type: Schema.Types.ObjectId } ],
  comments:     [messageSchema]
});
var Member = mongoose.model( 'Member', memberSchema );
var Conversation = mongoose.model( 'Conversation', conversationSchema );
async.waterfall(
  [
    // Clean
    function(callback) {
      async.each([Member,Conversation],function(model,callback) {
        model.remove({},callback);
      },
      function(err) {
        callback(err);
      });
    },
    // add some people
    function(callback) {
      async.map(["bill","ted","fred"],function(name,callback) {
        Member.create({ "name": name },callback);
      },callback);
    },
    // Create a conversation
    function(names,callback) {
      var conv = new Conversation();
      names.forEach(function(el) {
        conv.members.push(el._id);
      });
      conv.save(function(err,conv) {
        callback(err,conv,names)
      });
    },
    // add some comments
    function(conv,names,callback) {
      async.eachSeries(names,function(name,callback) {
        Conversation.update(
          { "_id": conv._id },
          { "$push": { "comments": { "comment": name.name } } },
          callback
        );
      },function(err) {
        callback(err,names);
      });
    },
    function(names,callback) {
      Conversation.findOne({},function(err,conv) {
        callback(err,names,conv.comments[1].creationDate);
      });
    },
    function(names,from,callback) {
      var ids = names.map(function(el) {
        return el._id
      });
      var pipeline = [
        { "$match": {
          "$and": [
            { "members": ids[0] },
            { "members": ids[1] }
          ]
        }},
        { "$project": {
          "members": 1,
          "comments": {
            "$setDifference": [
              { "$map": {
                "input": "$comments",
                "as": "c",
                "in": { "$cond": [
                  { "$gte": [ "$$c.creationDate", from ] },
                  "$$c",
                  false
                ]}
              }},
              [false]
            ]
          }
        }}
      ];
      //console.log(JSON.stringify(pipeline, undefined, 2 ));
      Conversation.aggregate(
        pipeline,
        function(err,result) {
          if(err) throw err;
          console.log(JSON.stringify(result, undefined, 2 ));
          callback(err);
        }
      )
    }

  ],
  function(err) {
    if (err) throw err;
    process.exit();
  }
);

产生如下输出:

[
  {
    "_id": "55a63133dcbf671918b51a93",
    "comments": [
      {
        "comment": "ted",
        "_id": "55a63133dcbf671918b51a95",
        "creationDate": "2015-07-15T10:08:51.217Z"
      },
      {
        "comment": "fred",
        "_id": "55a63133dcbf671918b51a96",
        "creationDate": "2015-07-15T10:08:51.220Z"
      }
    ],
    "members": [
      "55a63133dcbf671918b51a90",
      "55a63133dcbf671918b51a91",
      "55a63133dcbf671918b51a92"
    ]
  }
]

注意"comments"只包含最后两个大于或等于作为输入的日期(即第二个注释的日期)的条目。

相关内容

  • 没有找到相关文章

最新更新