mongodb Mongo聚合将$sort与$geoNear结合使用

chhkpiq4  于 2023-02-11  发布在  Go
关注(0)|答案(2)|浏览(158)

上下文:

我试图建立一个体系结构,显示POI,可以在不同的已知位置随着时间的推移。
我有两个系列,
波伊斯

{
  _id: ObjectId,
  name: string
}

地点

_id: ObjectId,
  point: {
    type: 'Point',
    coordinates: Array<number>
  },
  poi: ObjectId // Reference to Poi

用例:

我试着建立一个查询

  • 在输入中采用中心坐标+半径
  • 并返回半径范围内的匹配Pois
  • 仅显示最近的位置
  • 按距离排序

理想情况下,具有以下输出:

[
  {
    _id: ObjectId(AE54232),
    name: 'Some poi',
    location: {
      _id: ObjectId(BFE5423),
      point: {
        type: 'Point',
        coordinates: [3, 50]
      },
      distance: 3
    }
  }
]

尝试

仔细阅读文档后,我使用了以下组合:

// Keep only locations within radius,
      // output 'distance'
      // and sort by distance
      {
        $geoNear: {
          near: nearCenter,
          key: 'point',
          distanceField: 'distance',
          maxDistance: nearRadius,
          spherical: true,
        },
      },
      // Keep only first (assumed 'nearest')
      // location of each poi
      {
        $group: {
          _id: '$poi',
          location: {
            $first: '$$ROOT'
          }
        }
      },
      // Retrieve poi
      {
        $lookup: {
          from: 'pois',
          localField: '_id',
          foreignField: '_id',
          as: 'poi',
        },
      },
      // Flatten poi
      {
        $unwind: {
          path: '$poi',
        },
      },
      // Push poi at the root,
      // and put location inside 'location'
      {
        $replaceRoot: {
          newRoot: {
            $mergeObjects: [
              "$poi",
              { location: "$location" },
            ]
          },
        }
      },

所以总结一下:

  • $geoNear
  • $first(by poi)
  • $lookup(poi)
  • $unwind(poi)
  • $replaceRoot(poi { location })

麻烦

我遇到了一个奇怪的行为,而查询基本上是有效的;除非其未按距离排序:poi和它们的location以不稳定和非确定性的顺序出现!
我试着一个接一个地评论每一步,显然这是导致" Shuffle "的$first。这是令人惊讶的,因为docs声明:
$geoNear
从指定点最近到最远的顺序输出文档。
$first
返回对一组文档中的第一个文档应用表达式所得到的值。仅当文档按定义的顺序时才有意义。

修复尝试

我的想法是$first需要实际的$sort,而不是隐式的$geoNear排序;所以我试着在中间插入一个$sort,就像这样:

{
        $sort: {
          'distance': 1,
        },
      },

中间是这样:

  • $geoNear
  • $sort(distance)〈==此处
  • $first(by poi)
  • $lookup(poi)
  • $unwind(poi)
  • $replaceRoot(poi { location })

但它给了我完全相同的结果!
唯一起作用的是像这样在最后添加一个$sort

{
        $sort: {
          'location.distance': 1,
        },
      },
  • $geoNear
  • x1米20英寸1x
  • $lookup(poi)
  • $unwind(poi)
  • $replaceRoot(poi { location })
  • $sort(location.distance)〈==此处

但我担心它在大型数据集上可能会出现性能问题

问题

有什么方法可以实现这个逻辑吗

  • filter $geoNear(保持距离)
  • $按引用文档分组,仅保留"最近的"

而不丢失$geoNear订单?

q7solyqu

q7solyqu1#

为了扩展@尼姆罗德serok的公认答案
如果每个poi可以有几个位置,则将它们分组可能会更改顺序,因此分组后的文档不再按距离排序
我添加了一个关于“为什么”的解释(太长了,无法评论)。

声明

poi上的$geoNear$group$first)不一定会导致按距离排序的poi
原因很简单,但MongoDb doc对此却有点说不清楚:
$first
返回对一组文档中的第一个文档应用表达式所得到的值。仅当文档按定义的顺序排列时才有意义。
并不意味着“组的顺序将保持一致”;这意味着每个组的$first的属性将是一致的,只有当它在input中排序时。
关键在文档页面中间的“备注”中:
尽管$sort阶段将已排序的文档作为输入传递到$group和$setWindowFields阶段,但不保证这些阶段在其自己的输出中保持排序顺序。
这基本上意味着对于$first分辨率,输入的顺序是受尊重的;但是组本身的顺序不一致

示例案例

假设这是$geoNear的结果

- Location [id: 1, distance: 3, poi: 1]
- Location [id: 2, distance: 4, poi: 2]
- Location [id: 3, distance: 5, poi: 1]
- Location [id: 4, distance: 6, poi: 3]

$group($first(poi))保证 * 位置1* 将被保留并且 * 位置3* 将被丢弃;但是不保证位置1 将在位置2* 之前输出。
以下结果是合法的:

- Location [id: 2, distance: 4, poi: 2]
- Location [id: 1, distance: 3, poi: 1]
- Location [id: 4, distance: 6, poi: 3]

这是天性使然。

zpjtge22

zpjtge222#

如果每个poi可以有几个货位,那么分组可能会改变顺序,分组后的单据不再按distance排序,可以通过分组后按distance排序来解决:

{
        $geoNear: {
          near: nearCenter,
          key: 'point',
          distanceField: 'distance',
          maxDistance: nearRadius,
          spherical: true,
        },
      },
      // at this point you have all locations matching the criteria, sorted by `distance`
      // Keep only first (assumed 'nearest')
      // location of each poi
      {
        $group: {
          _id: '$poi',
          location: {
            $first: '$$ROOT'
          }
        }
      },
      // at this point you have one location and its distance from `nearCenter per each `poi`. The grouping can change the order of documents 
      {
        $lookup: {
          from: 'pois',
          localField: '_id',
          foreignField: '_id',
          as: 'poi',
        },
      },
      // until here you retrieved the `poi` as `poi`
      {$sort: {distance: -1}}
      // now the `poi`s are sorted by distance
      {
        $replaceRoot: {
          newRoot: {
            $mergeObjects: [
              {$first: "$poi"},
              { location: "$location" },
            ]
          },
        }
      }
      // Now the answer is formatted (no need to $unwind since you have only one item in the array)

相关问题