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

时间:2019-01-04 15:27:06

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

假设我有一些MongoDB Event文档,每个文档都有许多在不同日期进行的会话。我们可以这样表示:

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语句,则有3个)和一个复杂的大型aggregate语句。

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

3 个答案:

答案 0 :(得分:3)

您可以使用 $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")
        }
    ]
}

答案 1 :(得分:1)

您无需使用IPL Data Set$unwind从数组中查找$min日期。您可以直接使用$groupsession数组中提取最小日期,然后使用$min来填充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" }
])

答案 2 :(得分:0)

您是否可以仅参加每个活动的会议,并撤回所有会议日期都在将来的每个活动?像这样吗可能需要调整。

db.getCollection("events").aggregate(
    [

        {$match:{'$and':
               [
                   {'sessions.date':{'$gt': new Date()}}, 
                   {'sessions.date':{ '$not': {'$lt': new Date()}}} 
               ]
             }}
    ]
);