查找日期范围内和点距内的所有文档

时间:2018-07-11 19:21:13

标签: mongodb

我正在使用一个系统,允许用户注册以接收有关在特定日期范围内某个位置的一定距离内发布的新事件的警报。

当用户注册警报时,我将创建一个如下所示的文档:

{
    "_id":"5b43ab8eb0a638000e188615",
    "confirmed":true,
    "confirmed_at":"2018-07-10T14:20:51.078Z",
    "created_at":"2018-07-09T18:38:06.874Z",
    "date_start":"2018-07-30T00:00:00.000Z",
    "date_end":"2018-08-31T00:00:00.000Z",
    "email":"me@example.com",
    "location":{
        "type":"Point",
        "coordinates": [-90.44065060000003,31.5790588]
    },
    "location_name":"Brookhaven, MS 39601, USA",
    "distance":150,
    "unsubscribed":false,
    "unsubscribed_at":null
}

在位置字段上还有一个2dspehere索引。

在创建新事件时,我需要能够查询集合中所有date_startdate_end范围(包含事件日期)且范围为包含事件的位置。

给出如下所示的事件:

{
    start_date: "2018-08-01",
    latitude: 30.6954595,
    longitude: -88.174414
}

我希望上面的文档将被返回,因为该事件的开始日期在给定范围内,并且该事件位于所需位置的150英里(准确地说是147英里)之内。

还要注意的是事件本身在MongoDB中不存在。它们是在单独的系统中创建和管理的,我们将查询该系统的API定期获取新创建的事件。

这是我对mongodb的地理空间方面的首次尝试,因此我不确定该如何处理。我一直在使用$geoWithin$centerSphere运算符,但似乎无法使它们正常工作。

我正在尝试从MongoDB中进行单个查询,还是需要例如提取所有符合数据范围标准的文档,并在应用程序中迭代这些文档以查看其范围?包含活动的位置?

我想我已经包括了所有相关信息,但是如果需要更多信息,请问一下,我会更新问题。

谢谢!

1 个答案:

答案 0 :(得分:0)

在仔细阅读并阅读了MongoDB的文档后,我认为我已经找到了该问题的正确答案,并认为我会与以后会遇到此问题的任何人分享。

因此,解决此问题的最佳方法似乎是利用MongoDB的聚合框架。

要获得我需要的答案,我必须设置一个三阶段聚合管道。

第一步是使用$geoNear阶段(documentation)。这使我可以从外部数据源传递相关事件的坐标以及事件的日期。使用这些值,我创建了一个如下所示的舞台:

{
    $geoNear: {
        near: { type: "Point", coordinates: [event-longitude-goes-here,event-latitude-goes-here]},
        distanceField: "calculated_distance" /** This field is appended to the returned documents */,
        distanceMultiplier: 0.000621371 /** This converts meters to miles */,
        maxDistance: 804672 /** this is 500 miles expressed in meters */,
        query: {
            /** This lets us do a preliminary filter of the documents so we don't do so many distance calculations */
            confirmed: true,
            unsubscribed: false,
            date_start: {$lte: Date('event-start-date-here')},
            date_end: {$gte: Date('event-start-date-here-again')}
        },
        spherical: true
    }
}

这给了我一组文档,它们的date_startdate_end范围包含事件开始日期,并且距事件坐标的距离为500英里或更短。请注意,使用500英里的最大距离是因为这是用户可以请求警报的最大距离,因此没有理由将文档退回更远的地方。

接下来,我需要添加一个$project阶段(documentation),以便仅提取所需的字段。此阶段还使我可以比较文档本身中的两个不同字段。据我所知,在即将到来的$match阶段中,不允许比较两个文档字段。

{
    $project: {
        _id: 1,
        email: 1,
        location_name: 1,
        calculated_distance: 1,
        distance: 1,
        ofInterest: {$gte: ["$distance", "$calculated_distance"]}
    }
}

投影中的ofInterest字段为从$geoNear阶段返回的文档添加了一个新字段。如果$distance大于或等于$calculated_distance,则此字段为true,否则为false

最后,我创建一个$match阶段(documentation),该阶段会提取所有$ofInterest字段为true的文档。

{
    $match: {
        ofInterest: true
    }
}

生成的文档集合将仅包含那些符合日期条件且其距离值大于用户到事件的距离的文档。

完整的aggregate通话看起来像这样:

db.collection.aggregate([
{
    $geoNear: {
        near: { type: "Point", coordinates: [-88.174414,30.6954595]},
        distanceField: "calculated_distance",
        distanceMultiplier: 0.000621371,
        maxDistance: 804672,
        query: {
            confirmed: true,
            unsubscribed: false,
            date_start: {$lte: Date('2018-07-30')},
            date_end: {$gte: Date('2018-07-30')}
        },
        spherical: true
    }
},
{
    $project: {
        _id: 1,
        email: 1,
        location_name: 1,
        calculated_distance: 1,
        distance: 1,
        ofInterest: {$gte: ["$distance", "$calculated_distance"]}
    }
},
{
    $match: {
        ofInterest: true
    }
}]);

得出这个答案要比我最初想象的要复杂得多,但是一旦您了解发生了什么,我认为这很简单。