$lookup на ObjectId в массиве
каков синтаксис для выполнения $ lookup в поле, которое является массивом ObjectIds, а не только одним ObjectId?
Пример Документа Порядок:
{
_id: ObjectId("..."),
products: [
ObjectId("..<Car ObjectId>.."),
ObjectId("..<Bike ObjectId>..")
]
}
Не Работает Запрос:
db.orders.aggregate([
{
$lookup:
{
from: "products",
localField: "products",
foreignField: "_id",
as: "productObjects"
}
}
])
Желаемого Результата
{
_id: ObjectId("..."),
products: [
ObjectId("..<Car ObjectId>.."),
ObjectId("..<Bike ObjectId>..")
],
productObjects: [
{<Car Object>},
{<Bike Object>}
],
}
5 ответов
на $lookup
этап конвейера агрегации не будет работать непосредственно с массивом. Основная цель Дизайна - " левое соединение "как" один ко многим "тип соединения ( или действительно" поиск") по возможным связанным данным. Но значение должно быть сингулярным, а не массивом.
поэтому вы должны" де-нормализовать " контент сначала до выполнения $lookup
операции для того, чтобы это работало. И это значит использовать $unwind
:
db.orders.aggregate([
// Unwind the source
{ "$unwind": "$products" },
// Do the lookup matching
{ "$lookup": {
"from": "products",
"localField": "products",
"foreignField": "_id",
"as": "productObjects"
}},
// Unwind the result arrays ( likely one or none )
{ "$unwind": "$productObjects" },
// Group back to arrays
{ "$group": {
"_id": "$_id",
"products": { "$push": "$products" },
"productObjects": { "$push": "$productObjects" }
}}
])
после $lookup
соответствует каждому члену массива результатом является сам массив, поэтому вы $unwind
и снова $group
до $push
новые массивы для конечного результата.
обратите внимание, что любые совпадения "левого соединения", которые не найдены, создадут пустой массив для" productObjects "на данном продукте и, таким образом, отрицают документ для элемента" product", когда второй $unwind
is называемый.
хотя прямое приложение к массиву было бы неплохо, именно так это в настоящее время работает, сопоставляя сингулярное значение с возможными многими.
As $lookup
в основном очень новый, он в настоящее время работает, как было бы знакомо тем, кто знаком с мангуста как "версия бедняков".populate()
метод, предлагаемый там. Разница в том, что $lookup
предлагает" серверную "обработку " соединения", а не на клиенте и что некоторые из "зрелости" в $lookup
в настоящее время отсутствует из чего .populate()
предложения (например, интерполяция поиска непосредственно в массиве ).
это фактически назначенная проблема для улучшения сервер-22881, так что, если повезет, это попадет в следующий выпуск или вскоре после него.
как принцип проектирования, ваша текущая структура ни хороша, ни плоха, но просто подвержена накладным расходам при создании любого "соединения". Как таковой, основное положение принцип MongoDB в inception применяется, где, если вы" можете "жить с данными," предварительно Соединенными " в одной коллекции, то лучше всего это сделать.
еще одно, что можно сказать о $lookup
как общий принцип, заключается в том, что намерение "присоединиться" здесь состоит в том, чтобы работать наоборот, чем показано здесь. Поэтому вместо того, чтобы хранить "связанные идентификаторы" других документов в "Родительском" документе, общий принцип, который работает лучше всего, заключается в том, что "связанные документы" содержат ссылка на "родителя".
так $lookup
можно сказать, что " лучше всего работать "с" дизайном отношений", который является обратным тому, как что-то вроде мангуста .populate()
выполняет соединения на стороне клиента. Вместо этого, идентифицируя " один "внутри каждого" многих", вы просто вытаскиваете связанные элементы без необходимости $unwind
первый массив.
на $lookup
этап конвейера агрегации теперь работает непосредственно с массивом (на
Версия 3.3.4).
посмотреть: поиск между локальным (множественным)массивом значений и внешним (одиночным) значением
использовать $unwind вы получите первый объект вместо массива объектов
запрос:
db.getCollection('vehicles').aggregate([
{
$match: {
status: "AVAILABLE",
vehicleTypeId: {
$in: Array.from(newSet(d.vehicleTypeIds))
}
}
},
{
$lookup: {
from: "servicelocations",
localField: "locationId",
foreignField: "serviceLocationId",
as: "locations"
}
},
{
$unwind: "$locations"
}
]);
результат:
{
"_id" : ObjectId("59c3983a647101ec58ddcf90"),
"vehicleId" : "45680",
"regionId" : 1.0,
"vehicleTypeId" : "10TONBOX",
"locationId" : "100",
"description" : "Isuzu/2003-10 Ton/Box",
"deviceId" : "",
"earliestStart" : 36000.0,
"latestArrival" : 54000.0,
"status" : "AVAILABLE",
"accountId" : 1.0,
"locations" : {
"_id" : ObjectId("59c3afeab7799c90ebb3291f"),
"serviceLocationId" : "100",
"regionId" : 1.0,
"zoneId" : "DXBZONE1",
"description" : "Masafi Park Al Quoz",
"locationPriority" : 1.0,
"accountTypeId" : 0.0,
"locationType" : "DEPOT",
"location" : {
"makani" : "",
"lat" : 25.123091,
"lng" : 55.21082
},
"deliveryDays" : "MTWRFSU",
"timeWindow" : {
"timeWindowTypeId" : "1"
},
"address1" : "",
"address2" : "",
"phone" : "",
"city" : "",
"county" : "",
"state" : "",
"country" : "",
"zipcode" : "",
"imageUrl" : "",
"contact" : {
"name" : "",
"email" : ""
},
"status" : "",
"createdBy" : "",
"updatedBy" : "",
"updateDate" : "",
"accountId" : 1.0,
"serviceTimeTypeId" : "1"
}
}
{
"_id" : ObjectId("59c3983a647101ec58ddcf91"),
"vehicleId" : "81765",
"regionId" : 1.0,
"vehicleTypeId" : "10TONBOX",
"locationId" : "100",
"description" : "Hino/2004-10 Ton/Box",
"deviceId" : "",
"earliestStart" : 36000.0,
"latestArrival" : 54000.0,
"status" : "AVAILABLE",
"accountId" : 1.0,
"locations" : {
"_id" : ObjectId("59c3afeab7799c90ebb3291f"),
"serviceLocationId" : "100",
"regionId" : 1.0,
"zoneId" : "DXBZONE1",
"description" : "Masafi Park Al Quoz",
"locationPriority" : 1.0,
"accountTypeId" : 0.0,
"locationType" : "DEPOT",
"location" : {
"makani" : "",
"lat" : 25.123091,
"lng" : 55.21082
},
"deliveryDays" : "MTWRFSU",
"timeWindow" : {
"timeWindowTypeId" : "1"
},
"address1" : "",
"address2" : "",
"phone" : "",
"city" : "",
"county" : "",
"state" : "",
"country" : "",
"zipcode" : "",
"imageUrl" : "",
"contact" : {
"name" : "",
"email" : ""
},
"status" : "",
"createdBy" : "",
"updatedBy" : "",
"updateDate" : "",
"accountId" : 1.0,
"serviceTimeTypeId" : "1"
}
}
вы также можете использовать этап конвейера для проверки массива
вот пример использования python (извините, я змеиные люди).
db.products.aggregate([
{'$loookup':
{'from': 'products',
'let': {'pid': '$products'},
'pipeline': [
{'$match': {'expr': {'$in': ['$_id', '$$pid']}}}
# Additional stages here
],
'as':'productObjects'
}}
])
улов здесь должен соответствовать всем объектам в массиве object_id (foreign '_id' то есть в местных "продуктах").
вы также можете очистить или проецировать внешние записи с дополнительными этапами.
агрегация с $lookup
и в последующем $group
довольно громоздко, поэтому, если (и это среда, если) вы используете node & Mongoose или вспомогательную библиотеку с некоторыми подсказками в схеме, вы можете использовать .populate()
чтобы получить эти документы:
var mongoose = require("mongoose"),
Schema = mongoose.Schema;
var productSchema = Schema({ ... });
var orderSchema = Schema({
_id : Number,
products: [ { type: Schema.Types.ObjectId, ref: "Product" } ]
});
var Product = mongoose.model("Product", productSchema);
var Order = mongoose.model("Order", orderSchema);
...
Order
.find(...)
.populate("products")
...