根据子文档日期查找即将发布的文档
Posted
技术标签:
【中文标题】根据子文档日期查找即将发布的文档【英文标题】:Find upcoming documents based on subdocument date 【发布时间】:2019-05-31 04:41:30 【问题描述】:假设我有一些 MongoDB 事件文档,每个文档都有许多在不同日期发生的会话。我们可以将其表示为:
db.events.insert([
_id: '5be9860fcb16d525543cafe1',
name: 'Past',
host: '5be9860fcb16d525543daff1',
sessions: [
date: new Date(Date.now() - 1e8 ) ,
date: new Date(Date.now() + 1e8 )
]
,
_id: '5be9860fcb16d525543cafe2',
name: 'Future',
host: '5be9860fcb16d525543daff2',
sessions: [
date: new Date(Date.now() + 2e8) ,
date: new Date(Date.now() + 3e8)
]
]);
我想查找所有尚未举行第一次会议的活动。所以我想找到“未来”而不是“过去”。
目前我正在使用 Mongoose 和 Express 来做:
Event.aggregate([
$unwind: '$sessions' ,
$group:
_id: '$_id',
startDate: $min: '$sessions.date'
,
$sort: startDate: 1 ,
$match: startDate: $gte: new Date()
])
.then(result => Event.find( _id: result.map(result => result._id) ))
.then(event => Event.populate(events, 'host'))
.then(events => res.json(events))
但我觉得我正在为此制造恶劣的天气。数据库中的两次命中(如果包含 populate
语句,则为三次)和一个大而复杂的 aggregate
语句。
有没有更简单的方法来做到这一点?理想情况下,只涉及一次访问数据库。
【问题讨论】:
【参考方案1】:您可以使用 $reduce
折叠数组并查找是否有任何元素具有过去的会话。
为了说明这一点,请考虑运行以下聚合管道:
db.events.aggregate([
"$match": "sessions.date": "$gte": new Date() ,
"$addFields":
"hasPastSession":
"$reduce":
"input": "$sessions.date",
"initialValue": false,
"in":
"$or" : [
"$$value",
"$lt": ["$$this", new Date()]
]
,
// "$match": "hasPastSession": false
])
基于上面的示例,这将产生以下带有额外字段的文档
/* 1 */
"_id" : "5be9860fcb16d525543cafe1",
"name" : "Past",
"host" : "5be9860fcb16d525543daff1",
"sessions" : [
"date" : ISODate("2019-01-03T12:04:36.174Z")
,
"date" : ISODate("2019-01-05T19:37:56.174Z")
],
"hasPastSession" : true
/* 2 */
"_id" : "5be9860fcb16d525543cafe2",
"name" : "Future",
"host" : "5be9860fcb16d525543daff2",
"sessions" : [
"date" : ISODate("2019-01-06T23:24:36.174Z")
,
"date" : ISODate("2019-01-08T03:11:16.174Z")
],
"hasPastSession" : false
有了这个聚合管道,您就可以利用 $expr
并将管道表达式用作 find()
方法中的查询(或使用聚合操作上面,但最后启用了$match
管道步骤)作为
db.events.find(
"$expr":
"$eq": [
false,
"$reduce":
"input": "$sessions.date",
"initialValue": false,
"in":
"$or" : [
"$$value",
"$lt": ["$$this", new Date()]
]
]
)
返回文档
"_id" : "5be9860fcb16d525543cafe2",
"name" : "Future",
"host" : "5be9860fcb16d525543daff2",
"sessions" : [
"date" : ISODate("2019-01-06T23:24:36.174Z")
,
"date" : ISODate("2019-01-08T03:11:16.174Z")
]
【讨论】:
不确定为什么这回答了问题,因为 OP 的查询有效,但 OP 使用了三个查询并且需要单个查询来简化它。 1) Op 的第一个聚合查询正在工作 2) Op 不知道组聚合中的$first
,这就是为什么他使用第二个查询再次找到 host
键的原因。 3) 使用填充,因为 Op 不知道 $lookup
。但我看不到任何与之相关的东西。我说的对吗?
@AnthonyWinzlet $unwind
会带来巨大的性能损失,尤其是在匹配您真正想要的特定文档之前展开集合中的所有文档。 $unwind
效率不高的原因是它会生成文档的笛卡尔积,即每个数组条目的每个文档的副本,这会使用更多内存(聚合管道的内存上限可能为 10% 总内存),因此需要在展平过程中生成和处理文档的时间。上面的答案只需一个查询就消除了这个瓶颈
好的,但仍然看不到任何连接 host
字段的连接。
好吧,OP 的主要问题是 我想找到 所有事件 还没有他们的第一次会议。所以我想找到“未来”而不是“过去”。,host
填充可以简单地通过链接find()
方法来完成【参考方案2】:
您无需使用$unwind
和$group
从数组中查找$min
日期。您可以直接使用$min
从session
数组中提取最小日期,然后使用$lookup
填充host
键
db.events.aggregate([
"$match": "sessions.date": "$gte": new Date() ,
"$addFields": "startDate": "$min": "$sessions.date" ,
"$match": "startDate": "$gte": new Date() ,
"$lookup":
"from": "host",
"localField": "host",
"foreignField": "_id",
"as": "host"
,
"$unwind": "$host"
])
【讨论】:
【参考方案3】:您是否有可能只进入每个活动的会议,然后撤回所有会议日期仅在未来的每个活动?像这样的东西?可能需要调整..
db.getCollection("events").aggregate(
[
$match:'$and':
[
'sessions.date':'$gt': new Date(),
'sessions.date': '$not': '$lt': new Date()
]
]
);
【讨论】:
以上是关于根据子文档日期查找即将发布的文档的主要内容,如果未能解决你的问题,请参考以下文章