根据子文档日期查找即将发布的文档

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 日期。您可以直接使用$minsession 数组中提取最小日期,然后使用$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() 
               ]
             
    ]
);

【讨论】:

以上是关于根据子文档日期查找即将发布的文档的主要内容,如果未能解决你的问题,请参考以下文章

Mongodb:按元素分组并根据条件显示子文档计数并按日期对文档进行排序

根据父项中的值按 Id 查找子文档

Mongodb根据日期查询

MongoDB 即将支持跨文档事务

根据条件查找第 N 个文档

如何根据查询从子文档数组中更新子文档字段?