按日期时间过滤子文档
Filter subdocument by datetime
我有以下型号
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 ]
});
我想创建两种方法来获取某个日期后由用户或 conversationId 生成的评论。
按用户
我试过下面的方法
var query = {
members : { $all : [ userId, otherUserId ], "$size" : 2 }
, comments : { $elemMatch : { creationDate : { $gte: from } } }
};
如果在指定日期(从)之后没有评论,方法 returns [] 或 null
通过会话 ID
当我尝试通过用户 ID 获取时,同样的情况发生了
var query = { _id : conversationId
, comments : { $elemMatch : { creationDate : { $gte: from } } }
};
有没有办法让方法returns的对话信息空评论?
谢谢!
这里听起来像是几个问题,但逐步解决了所有问题
为了从一个数组中得到多个匹配"or" none需要mapReduce的聚合框架来做到这一点。您可以尝试 "projecting" 与 $elemMatch
但这只能 return 与 "first" 匹配。即:
{ "a": [1,2,3] }
db.collection.find({ },{ "$elemMatch": { "$gte": 2 } })
{ "a": [2] }
因此标准投影不适用于此。它可以 return 一个 "empty" 数组,但它也只能 return 匹配的 "first"。
继续前进,您的代码中也有这个:
{ $all : [ userId, otherUserId ], "$site" : 2 }
其中 $site
不是有效的运算符。我认为你的意思是 $size
但实际上有 "two" 个运算符使用该名称,你的意图在这里可能不清楚。
如果您的意思是要测试的数组必须有 "only two" 个元素,那么这就是适合您的运算符。如果你的意思是两个人之间的匹配对话必须等于匹配中的两个人,那么 $all
无论如何都会这样做,所以 $size
在任何一种情况下都会变得多余,除非你不想要其他人在对话中。
关于聚合问题。您需要 "filter" 中的数组内容 "non-destructive way" 才能获得多个匹配项或一个空数组。
最好的方法是使用 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
which can process an expression against each array element. In this case the vallues are tested under the $cond
三进制来 return 条件为 true
的数组元素,否则 return false
作为元素。
然后 "filtered" 由 $setDifference
运算符进行 "filtered",该运算符本质上是将 $map
的结果数组与另一个数组 [false]
进行比较。这将从结果数组中删除任何 false
值,只留下匹配的元素或根本没有元素。
替代方法可能是 $redact
,但由于您的文档在多个级别包含 "creationDate",因此这会扰乱其 $$DESCEND
运算符所使用的逻辑。这排除了该操作。
在早期版本中 "not destroying" 数组需要小心处理。所以你需要做很多相同的 "filter" 结果才能得到你想要的 "empty" 数组:
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
the content. When you $group
返回的数组内容,您查看每个元素以查看它是否符合条件以决定要 return 的内容,同时记录匹配项的数量。
这将把一些(一个带有 $addToSet
)false
结果放在数组中,或者只放在一个没有匹配项的 false
数组中。所以你用 $match
过滤掉这些,但也在匹配的 "count" 上进行测试,看看是否没有找到匹配项。如果未找到匹配项,则您不会丢弃该项目。
而是在最终 $project
.
中将 [false]
数组替换为空数组
因此,根据您的 MongoDB 版本,这是要处理的 "fast/easy" 或 "slow/hard"。更新已经存在多年的版本的令人信服的理由。
工作示例
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" 仅包含最后两个条目,即 "greater than or equal" 到用作输入的日期(即第二条评论中的日期)。
我有以下型号
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 ]
});
我想创建两种方法来获取某个日期后由用户或 conversationId 生成的评论。
按用户
我试过下面的方法
var query = {
members : { $all : [ userId, otherUserId ], "$size" : 2 }
, comments : { $elemMatch : { creationDate : { $gte: from } } }
};
如果在指定日期(从)之后没有评论,方法 returns [] 或 null
通过会话 ID
当我尝试通过用户 ID 获取时,同样的情况发生了
var query = { _id : conversationId
, comments : { $elemMatch : { creationDate : { $gte: from } } }
};
有没有办法让方法returns的对话信息空评论?
谢谢!
这里听起来像是几个问题,但逐步解决了所有问题
为了从一个数组中得到多个匹配"or" none需要mapReduce的聚合框架来做到这一点。您可以尝试 "projecting" 与 $elemMatch
但这只能 return 与 "first" 匹配。即:
{ "a": [1,2,3] }
db.collection.find({ },{ "$elemMatch": { "$gte": 2 } })
{ "a": [2] }
因此标准投影不适用于此。它可以 return 一个 "empty" 数组,但它也只能 return 匹配的 "first"。
继续前进,您的代码中也有这个:
{ $all : [ userId, otherUserId ], "$site" : 2 }
其中 $site
不是有效的运算符。我认为你的意思是 $size
但实际上有 "two" 个运算符使用该名称,你的意图在这里可能不清楚。
如果您的意思是要测试的数组必须有 "only two" 个元素,那么这就是适合您的运算符。如果你的意思是两个人之间的匹配对话必须等于匹配中的两个人,那么 $all
无论如何都会这样做,所以 $size
在任何一种情况下都会变得多余,除非你不想要其他人在对话中。
关于聚合问题。您需要 "filter" 中的数组内容 "non-destructive way" 才能获得多个匹配项或一个空数组。
最好的方法是使用 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
which can process an expression against each array element. In this case the vallues are tested under the $cond
三进制来 return 条件为 true
的数组元素,否则 return false
作为元素。
然后 "filtered" 由 $setDifference
运算符进行 "filtered",该运算符本质上是将 $map
的结果数组与另一个数组 [false]
进行比较。这将从结果数组中删除任何 false
值,只留下匹配的元素或根本没有元素。
替代方法可能是 $redact
,但由于您的文档在多个级别包含 "creationDate",因此这会扰乱其 $$DESCEND
运算符所使用的逻辑。这排除了该操作。
在早期版本中 "not destroying" 数组需要小心处理。所以你需要做很多相同的 "filter" 结果才能得到你想要的 "empty" 数组:
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
the content. When you $group
返回的数组内容,您查看每个元素以查看它是否符合条件以决定要 return 的内容,同时记录匹配项的数量。
这将把一些(一个带有 $addToSet
)false
结果放在数组中,或者只放在一个没有匹配项的 false
数组中。所以你用 $match
过滤掉这些,但也在匹配的 "count" 上进行测试,看看是否没有找到匹配项。如果未找到匹配项,则您不会丢弃该项目。
而是在最终 $project
.
[false]
数组替换为空数组
因此,根据您的 MongoDB 版本,这是要处理的 "fast/easy" 或 "slow/hard"。更新已经存在多年的版本的令人信服的理由。
工作示例
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" 仅包含最后两个条目,即 "greater than or equal" 到用作输入的日期(即第二条评论中的日期)。