Mongodb - агрегатная функция вложенных объектов - PullRequest
2 голосов
/ 01 октября 2019

Нам нужно рассчитать минимальный ограничивающий прямоугольник (MBR) для геопространственных данных. В оракуле у нас есть функция SDO_AGGR_MBR, есть ли в MongoDB какая-либо подобная функция.

"coord" : {
    "type" : "Polygon",
    "coordinates" : [ 
        [ 
            [ 
                25.5377574375611, 
                42.8545750237221
            ], 
            [ 
                47.7803203666229, 
                42.8545750237221
            ], 
            [ 
                47.7803203661319, 
                52.0987759993153
            ], 
            [ 
                25.5377574370701, 
                52.0987759993153
            ], 
            [ 
                25.5377574375611, 
                42.8545750237221
            ]
        ]
    ]
}

У нас есть данные геометрии, как указано выше, но длина координат может отличаться. Итак, нам нужно найти minx, miny, maxx и maxy по этим данным.

Ответы [ 2 ]

1 голос
/ 01 октября 2019

Наиболее эффективным способом является использование операторов $map и $reduce вместе с $let. Это позволяет обрабатывать каждый документ, манипулируя массивом inline , а затем используя операторы $min и $max для получения ограничивающих значений:

db.collection.aggregate([
  { "$replaceRoot": {  
    "newRoot": {
      "$let": {
        "vars": {
          "m": {
            "$map": {
              "input": {
                "$reduce": {
                  "input": "$coord.coordinates",
                  "initialValue": [],
                  "in": {
                    "$concatArrays": [ "$$value", "$$this"]
                  }
                }
              },
              "in": {
                "x": { "$arrayElemAt": [ "$$this", 0 ] },
                "y": { "$arrayElemAt": [ "$$this", 1 ] }
              }
            }
          }
        },
        "in": {
          "_id": "$_id",
          "coord": "$coord",
          "minX": { "$min": "$$m.x" },
          "minY": { "$min": "$$m.y" },
          "maxX": { "$max": "$$m.x" },
          "maxY": { "$max": "$$m.y" }
        }
      }
    }
  }}
])

И вывод будет выглядеть следующим образом:

{
        "_id" : ObjectId("5d9330e95994eb7018f59218"),
        "coord" : {
                "type" : "Polygon",
                "coordinates" : [
                        [
                                [
                                        25.5377574375611,
                                        42.8545750237221
                                ],
                                [
                                        47.7803203666229,
                                        42.8545750237221
                                ],
                                [
                                        47.7803203661319,
                                        52.0987759993153
                                ],
                                [
                                        25.5377574370701,
                                        52.0987759993153
                                ],
                                [
                                        25.5377574375611,
                                        42.8545750237221
                                ]
                        ]
                ]
        },
        "minX" : 25.5377574370701,
        "minY" : 42.8545750237221,
        "maxX" : 47.7803203666229,
        "maxY" : 52.0987759993153
}

Обратите внимание на использование этапа агрегации конвейера $replaceRoot, так как это позволит использовать вложенные выражения с $let для предоставления глобальных переменных документу, который можно использовать в любом выходном свойстве.

$reduce здесьв основном служит для функции сглаживания массива из стандартной формы GeoJSON в виде просто массива координатных пар без дополнительного ограничивающего массива.

Затем он передает входные данные в $map, который использует $arrayElemAt, чтобы переназначить каждую пару координат в объект с клавишами x и y. Это значительно упрощает фактическое выполнение или вывод раздела $let.

Примечание : альтернативаподход к использованию $arrayElemAt против каждого ключа в $map вполне может заключаться в использовании $zip и $arrayToObject:

   "in": {
        "$arrayToObject": { "$zip": { "inputs": [ ["x","y"], "$$this" ] } }
   }

Он имеет тот же принцип в общем выводе, но использует преимущество $zip, создавая "парные" массивы, что также является допустимым вводом для $arrayToObject для создания окончательной формы object .

В заключительной части теперь мы в основном имеем массив объектов с именованнымклавиши x и y. MongoDB позволяет удобным способом remap просто значения для именованных ключей с нотацией, такой как "$$m.x", где выражение "$$m относится к именованной переменной $let и являетсянаш массив объектов , а часть .x, конечно, просто означает только значения x. Это базовое сокращение для самого оператора $map, которое подходит для этого особого случая использования.

Эти массивы значений для определенных свойств теперь могут применяться к $min и $max операторов, и вот как вы получаете min и max координаты для ограничивающего прямоугольника.


Примечаниечто встроенных операторов для массивов всегда следует предпочитать $unwind.

Этап конвейера агрегации $unwind былстарый вводный способ работы с элементами массива, по существу сведя их в отдельные документы.

Хотя это необходимо, когда вы действительно хотите сгруппировать для значения как ключа, который приходитиз массива большинство операций, которые в действительности не нуждаются в этом (например, в этом), могут выполняться с использованием более современных подходов.

Использование $unwind на самом деле огромная потеря производительности из-за его функциипо сути, это репликация всего содержимого родительского документа поля, содержащего массив, в его собственный новый документ. В частности, в больших наборах данных это очень негативно сказывается на производительности из-за значительного увеличения количества операций ввода-вывода и использования памяти.

Основной урок заключается в том, что если это необходимо для выполняемой операции (а здесь нет)тогда вам не следует использовать $unwind в конвейере агрегации. Это может выглядеть проще для понимания, но ваш код на самом деле наносит ущерб системе, в которой он работает, включая его.


Подход альтернативного клиента

Обратите внимание, что есливам на самом деле не нужны эти результаты для какой-либо дальнейшей обработки агрегации, тогда, вероятно, будет намного чище кодировать в клиенте при обработке каждого документа.

Например, вот простая версия JavaScript для оболочки:

db.collection.find().map(({ _id, coord }) =>
  (({ coordinates }) =>
    ({
      _id,
      coord,
      ...(m => 
        ({
          minX: Math.min(...m.map(({ x }) => x)),
          minY: Math.min(...m.map(({ y }) => y)),
          maxX: Math.max(...m.map(({ x }) => x)),
          maxY: Math.max(...m.map(({ y }) => y))
        })
      )(
        ((c) => c.reduce((o,e) => [ ...o, ...e ],[]).map(([x,y]) => ({ x, y })) )(coordinates)
      )
  })
  )(coord)
)

Она имеет точно такой же вывод и не так громоздка, как операторы оператора BSON, требуемые для конвейера агрегации.

1 голос
/ 01 октября 2019

Не думаю, что для этого есть встроенная функция. Но вы можете просто сделать следующее:

db.collection.aggregate([
  {
    $unwind: "$coordinates"
  },
  {
    $unwind: "$coordinates"
  },
  {
    $group: {
      _id: null,
      minX: {
        $min: { $arrayElemAt: [ "$coordinates", 0 ] }
      },
      maxX: {
        $max: { $arrayElemAt: [ "$coordinates", 0 ] }
      },
      minY: {
        $min: { $arrayElemAt: [ "$coordinates", 1 ] }
      },
      maxY: {
        $max: { $arrayElemAt: [ "$coordinates", 1 ] }
      }
    }
  }
])

Где сначала развернуть массив coordinates с $unwind (дважды для дополнительного блока [ ]), так что aggregate pipelineможно итерировать на нем. Затем мы просто используем $group с _id: null, что является специальной операцией для оценки минимальных / максимальных значений для всех элементов массива.

, которая будетполучить ответ на ваш запрос;

[
  {
    "_id": null,
    "maxX": 47.7803203666229,
    "maxY": 52.0987759993153,
    "minX": 25.5377574370701,
    "minY": 42.8545750237221
  }
]

проверить на mongoplayground

...