从$ geoNear选择中排除文档

时间:2016-03-10 06:34:55

标签: node.js mongodb mongodb-query aggregation-framework

我有两个收藏集CardsSwipes。用户可以请求卡片列表并在其上滑动。一旦用户刷卡,我就不希望该卡再次出现在列表中,即我只想要用户不刷卡。

这样做的一种方法是使用$nin。但是我担心使用$nin进行查询的性能,因为数组会随着时间的推移而增加。将$nin与1000个项目一起使用并不是一个好主意。

有没有其他方法可以执行此查询?

db.Cards.aggregate([
    {
        $geoNear: {
            near: {
                type: "Point",
                coordinates: [ 77.209, 28.6139 ]
            },
            maxDistance: 100,
            distanceField: "distance",
            spherical: true
        }
    },
    {
        $match: {
            _id: { $nin: [ <array of ~1000 items> ] }
        }
    },
    {
        $sort: { createdAt: -1 }
    },
    {
        $limit: 10
    }
]);

$geoNear管道会将文档限制为不超过500,然后我想应用$ nin来获取尚未刷过的文档。此外,我不确定管道的顺序是否在聚合方法中很重要。

我建议如何优化此查询。

1 个答案:

答案 0 :(得分:1)

$geoNear汇总管道阶段有一个&#34;查询&#34;选项:

db.Cards.aggregate([
    {
        $geoNear: {
            near: {
                type: "Point",
                coordinates: [ 77.209, 28.6139 ]
            },
            maxDistance: 100,
            distanceField: "distance",
            spherical: true,
            query: { "_id": { "$nin": [ <array of ~1000 items> ] } }
        }
    },
    {
        $sort: { distance:1, createdAt: -1 }
    },
    {
        $limit: 10
    }
]);

这将合并在&#34; near&#34;操作也直接从选择中排除这些文件。这节省了相当多的处理时间,因为它不需要额外的执行阶段来处理它。

在做任何类型的&#34;附近时,它也没有多大意义。操作不先按最近距离排序。

至于基本概念,你真的没有选择。像$geoNear$nearSphere这样的操作需要索引来执行查询。您可以使用$lookup模拟联接,但由于您必须这样做&#34;之前&#34; $geoNear然后它不能在第一个管道阶段,它必须在那里。

即使使用$geoWitin和&#34; Polygon&#34;模拟半径实际上不会返回距离&#34;投影,所以它不会找到&#34;最近的&#34;对于限制结果的条款。

您可以从$nin条件中优化此方法的唯一方法是使用地理位置数据直接在集合中包含其他集合中的必要数据。然后可以使用相同的&#34;查询&#34;一次指定所有查询条件。选项。

不同行为的示例

既然你似乎在这里忽略了这一点,那么其他人可能也是如此,所以一个实际的例子可能是恰当的。为此,我将使用dataset readily available from the MongoDB site

按照这些导入说明,您将在餐馆集合上创建适当的索引:

db.restaurants.createIndex({ "address.coord": "2dsphere" })

然后让我们获得最接近某个位置的前5个结果:

var exclude = db.restaurants.find({
  "address.coord": {
    "$nearSphere": {
      "$geometry": {
        "type": "Point",
        "coordinates": [
          -73.9829239,
          40.6580753
        ]
      }
    }
  }
},{ "_id": 1 }).limit(5).toArray().map((el) => { return el._id })

exclude变量应包含此内容,这是来自这5个最近文档的_id值:

[
        ObjectId("56e21e330c502e7d3727b133"),
        ObjectId("56e21e330c502e7d3727b977"),
        ObjectId("56e21e330c502e7d3727d02b"),
        ObjectId("56e21e340c502e7d37281121"),
        ObjectId("56e21e330c502e7d3727d45d")
]

然后我们首先演示使用与initail管道阶段之后运行$match$nin的方法相同的方法:

db.restaurants.aggregate([
  { "$geoNear": {
    "near": {
      "type": "Point",
      "coordinates": [
        -73.9829239,
        40.6580753
      ]
    },
    "spherical": true,
    "distanceField": "distance",
    "limit": 10
  }},
  { "$match": { "_id": { "$nin": exclude } } },
  { "$project": { "distance": 1 }}
])

我想在这里展示的是,尽管&#34;附近&#34;正在寻找最近的10个,结果将排除前5个然后只返回下5个:

{ "_id" : ObjectId("56e21e340c502e7d3727e0cd"), "distance" : 87.43379780572778 }
{ "_id" : ObjectId("56e21e340c502e7d3727de6b"), "distance" : 88.18009275622559 }
{ "_id" : ObjectId("56e21e340c502e7d3727fd53"), "distance" : 110.40877485624807 }
{ "_id" : ObjectId("56e21e330c502e7d3727d317"), "distance" : 125.18596173260741 }
{ "_id" : ObjectId("56e21e340c502e7d3727da21"), "distance" : 142.62225533420892 }

相比之下,当我们&#34;包括&#34; &#34;查询&#34;选项,最接近5的那些已经从结果中排除。因此,您实际上得到了您要求的10个结果:

db.restaurants.aggregate([
  { "$geoNear": {
    "near": {
      "type": "Point",
      "coordinates": [
        -73.9829239,
        40.6580753
      ]
    },
    "spherical": true,
    "distanceField": "distance",
    "query": { "_id": { "$nin": exclude }},
    "limit": 10
  }},
  { "$project": { "distance": 1 }}
])

取得适当的结果:

{ "_id" : ObjectId("56e21e340c502e7d3727e0cd"), "distance" : 87.43379780572778 }
{ "_id" : ObjectId("56e21e340c502e7d3727de6b"), "distance" : 88.18009275622559 }
{ "_id" : ObjectId("56e21e340c502e7d3727fd53"), "distance" : 110.40877485624807 }
{ "_id" : ObjectId("56e21e330c502e7d3727d317"), "distance" : 125.18596173260741 }
{ "_id" : ObjectId("56e21e340c502e7d3727da21"), "distance" : 142.62225533420892 }
{ "_id" : ObjectId("56e21e340c502e7d3727d8e1"), "distance" : 155.15302440129824 }
{ "_id" : ObjectId("56e21e340c502e7d37280491"), "distance" : 161.1217883163846 }
{ "_id" : ObjectId("56e21e340c502e7d37280c71"), "distance" : 198.54060208357487 }
{ "_id" : ObjectId("56e21e340c502e7d3727ebd1"), "distance" : 231.1351850943389 }
{ "_id" : ObjectId("56e21e340c502e7d3727f55d"), "distance" : 273.94030274423943 }

所以这与$geoNear发出的以下查询相同,但不投影"distance"字段和值:

db.restaurants.find({
  "_id": { "$nin": exclude },
  "address.coord": {
    "$nearSphere": {
      "$geometry": {
        "type": "Point",
        "coordinates": [
          -73.9829239,
          40.6580753
        ]
      }
    }
  }
},{ "_id": 1 }).limit(10).toArray().map((el) => { return el._id })

Which returns:

[
        ObjectId("56e21e340c502e7d3727e0cd"),
        ObjectId("56e21e340c502e7d3727de6b"),
        ObjectId("56e21e340c502e7d3727fd53"),
        ObjectId("56e21e330c502e7d3727d317"),
        ObjectId("56e21e340c502e7d3727da21"),
        ObjectId("56e21e340c502e7d3727d8e1"),
        ObjectId("56e21e340c502e7d37280491"),
        ObjectId("56e21e340c502e7d37280c71"),
        ObjectId("56e21e340c502e7d3727ebd1"),
        ObjectId("56e21e340c502e7d3727f55d")
]

所以&#34;查询&#34;选项允许&#34;组合&#34;条件,与获取结果完全不同,然后在事后删除要排除的结果。

"limit": 10选项的模拟是存在的,因为$geoNear返回的内容存在真实限制,当过滤掉大型列表时,您会遇到真正的风险从查询中删除** all *结果,这显然是不可取的。

所以为了得到&#34;最接近这些项目的东西&#34; ,那么你希望它成为&#34;同一个查询的一部分&#34;在选择中,而不是从结果中剥离它们&#34;&#34;你进行了查询选择。