【问题标题】: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 语句。

有没有更简单的方法来做到这一点?理想情况下,只涉及一次访问数据库。

【问题讨论】:

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


    【解决方案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()}}} 
                     ]
                   }}
          ]
      );
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-03-12
        • 2021-11-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-16
        • 1970-01-01
        • 2020-07-05
        相关资源
        最近更新 更多