Наиболее эффективным способом является использование операторов $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, требуемые для конвейера агрегации.