$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")
    ...